diff --git a/deploy/docker/cn/docker-compose.milvus.yml b/deploy/docker/cn/docker-compose.milvus.yml index 4d19ecaf2..d72d708ff 100644 --- a/deploy/docker/cn/docker-compose.milvus.yml +++ b/deploy/docker/cn/docker-compose.milvus.yml @@ -197,6 +197,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/docker/cn/docker-compose.oceanbase.yml b/deploy/docker/cn/docker-compose.oceanbase.yml index 740d06bed..b16b9f58f 100644 --- a/deploy/docker/cn/docker-compose.oceanbase.yml +++ b/deploy/docker/cn/docker-compose.oceanbase.yml @@ -172,6 +172,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/docker/cn/docker-compose.pg.yml b/deploy/docker/cn/docker-compose.pg.yml index 1b2baf82f..04575c9e8 100644 --- a/deploy/docker/cn/docker-compose.pg.yml +++ b/deploy/docker/cn/docker-compose.pg.yml @@ -154,6 +154,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/docker/cn/docker-compose.zilliz.yml b/deploy/docker/cn/docker-compose.zilliz.yml index a4a2e5d32..b21f851f6 100644 --- a/deploy/docker/cn/docker-compose.zilliz.yml +++ b/deploy/docker/cn/docker-compose.zilliz.yml @@ -137,6 +137,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/docker/global/docker-compose.milvus.yml b/deploy/docker/global/docker-compose.milvus.yml index e459bcec8..2cc8bf337 100644 --- a/deploy/docker/global/docker-compose.milvus.yml +++ b/deploy/docker/global/docker-compose.milvus.yml @@ -197,6 +197,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/docker/global/docker-compose.oceanbase.yml b/deploy/docker/global/docker-compose.oceanbase.yml index 4c0675d34..1e3ba6134 100644 --- a/deploy/docker/global/docker-compose.oceanbase.yml +++ b/deploy/docker/global/docker-compose.oceanbase.yml @@ -172,6 +172,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/docker/global/docker-compose.pg.yml b/deploy/docker/global/docker-compose.pg.yml index 314b5f72e..0c71286b1 100644 --- a/deploy/docker/global/docker-compose.pg.yml +++ b/deploy/docker/global/docker-compose.pg.yml @@ -154,6 +154,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/docker/global/docker-compose.ziliiz.yml b/deploy/docker/global/docker-compose.ziliiz.yml index 22a43ea25..d16ba0fa2 100644 --- a/deploy/docker/global/docker-compose.ziliiz.yml +++ b/deploy/docker/global/docker-compose.ziliiz.yml @@ -137,6 +137,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/deploy/templates/docker-compose.prod.yml b/deploy/templates/docker-compose.prod.yml index c5d7afb8f..16987dcdd 100644 --- a/deploy/templates/docker-compose.prod.yml +++ b/deploy/templates/docker-compose.prod.yml @@ -136,6 +136,8 @@ ${{vec.db}} WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json @@ -182,6 +184,10 @@ ${{vec.db}} <<: *x-share-db-config AUTH_TOKEN: *x-plugin-auth-token S3_BUCKET: fastgpt-plugins + # 工具网络请求,最大请求和响应体 + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 + # 最大 API 请求体大小 + MAX_API_SIZE: 10 depends_on: fastgpt-minio: condition: service_healthy diff --git a/document/content/docs/upgrading/4-13/4131.mdx b/document/content/docs/upgrading/4-13/4131.mdx index 3693dd090..eb4f929bf 100644 --- a/document/content/docs/upgrading/4-13/4131.mdx +++ b/document/content/docs/upgrading/4-13/4131.mdx @@ -7,9 +7,12 @@ description: 'FastGPT V4.13.1 更新说明' ## 🚀 新增内容 +1. 增加对 HTTP 请求响应大小限制。 ## ⚙️ 优化 +1. 复制应用时,将头像复制一份,避免使用相同的图片链接,导致其中一个应用头像更新后,另一个应用头像丢失。 +2. Markdown 解析器适配 windows 路径,避免 \ 被认为转义符。 ## 🐛 修复 @@ -17,6 +20,11 @@ description: 'FastGPT V4.13.1 更新说明' 2. 交互节点响应后,未更新对话记录统计数据。 3. prompt 编辑器,弹窗中的默认值存在显示异常。 4. 表单输入,变量名包含.符号时,无法正常输入值。 +5. 调用子工作流,自动流知识库引用无法在分享链接中显示。 ## 🔨 插件更新 +1. base64 解码工具,可以转化成文本和图片。 +2. 墨迹天气工具。 +3. 必优 PPT 生成工具。 +4. 可配置最大请求体大小,以及内部网络请求最大响应大小,避免响应体过大,导致内存溢出。 \ No newline at end of file diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index a5ca51293..ab242a5f4 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -28,7 +28,7 @@ "document/content/docs/introduction/development/modelConfig/ai-proxy.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/introduction/development/modelConfig/intro.mdx": "2025-08-12T22:22:18+08:00", "document/content/docs/introduction/development/modelConfig/one-api.mdx": "2025-07-23T21:35:03+08:00", - "document/content/docs/introduction/development/modelConfig/ppio.mdx": "2025-08-05T23:20:39+08:00", + "document/content/docs/introduction/development/modelConfig/ppio.mdx": "2025-09-29T11:52:39+08:00", "document/content/docs/introduction/development/modelConfig/siliconCloud.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/introduction/development/openapi/app.mdx": "2025-09-26T13:18:51+08:00", "document/content/docs/introduction/development/openapi/chat.mdx": "2025-09-29T11:34:11+08:00", @@ -39,7 +39,7 @@ "document/content/docs/introduction/development/proxy/http_proxy.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/proxy/nginx.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/quick-start.mdx": "2025-09-29T11:34:11+08:00", - "document/content/docs/introduction/development/sealos.mdx": "2025-08-05T23:20:39+08:00", + "document/content/docs/introduction/development/sealos.mdx": "2025-09-29T11:52:39+08:00", "document/content/docs/introduction/development/signoz.mdx": "2025-09-17T22:29:56+08:00", "document/content/docs/introduction/guide/DialogBoxes/htmlRendering.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/DialogBoxes/quoteList.mdx": "2025-07-23T21:35:03+08:00", @@ -112,7 +112,7 @@ "document/content/docs/upgrading/4-12/4123.mdx": "2025-09-07T20:55:14+08:00", "document/content/docs/upgrading/4-12/4124.mdx": "2025-09-17T22:29:56+08:00", "document/content/docs/upgrading/4-13/4130.mdx": "2025-09-26T13:32:15+08:00", - "document/content/docs/upgrading/4-13/4131.mdx": "2025-09-27T19:43:37+08:00", + "document/content/docs/upgrading/4-13/4131.mdx": "2025-09-30T11:21:41+08:00", "document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00", diff --git a/document/public/deploy/docker/cn/docker-compose.milvus.yml b/document/public/deploy/docker/cn/docker-compose.milvus.yml index 4d19ecaf2..d72d708ff 100644 --- a/document/public/deploy/docker/cn/docker-compose.milvus.yml +++ b/document/public/deploy/docker/cn/docker-compose.milvus.yml @@ -197,6 +197,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/document/public/deploy/docker/cn/docker-compose.oceanbase.yml b/document/public/deploy/docker/cn/docker-compose.oceanbase.yml index 740d06bed..b16b9f58f 100644 --- a/document/public/deploy/docker/cn/docker-compose.oceanbase.yml +++ b/document/public/deploy/docker/cn/docker-compose.oceanbase.yml @@ -172,6 +172,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/document/public/deploy/docker/cn/docker-compose.pg.yml b/document/public/deploy/docker/cn/docker-compose.pg.yml index 1b2baf82f..04575c9e8 100644 --- a/document/public/deploy/docker/cn/docker-compose.pg.yml +++ b/document/public/deploy/docker/cn/docker-compose.pg.yml @@ -154,6 +154,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/document/public/deploy/docker/cn/docker-compose.zilliz.yml b/document/public/deploy/docker/cn/docker-compose.zilliz.yml index a4a2e5d32..b21f851f6 100644 --- a/document/public/deploy/docker/cn/docker-compose.zilliz.yml +++ b/document/public/deploy/docker/cn/docker-compose.zilliz.yml @@ -137,6 +137,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/document/public/deploy/docker/global/docker-compose.milvus.yml b/document/public/deploy/docker/global/docker-compose.milvus.yml index e459bcec8..2cc8bf337 100644 --- a/document/public/deploy/docker/global/docker-compose.milvus.yml +++ b/document/public/deploy/docker/global/docker-compose.milvus.yml @@ -197,6 +197,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/document/public/deploy/docker/global/docker-compose.oceanbase.yml b/document/public/deploy/docker/global/docker-compose.oceanbase.yml index 4c0675d34..1e3ba6134 100644 --- a/document/public/deploy/docker/global/docker-compose.oceanbase.yml +++ b/document/public/deploy/docker/global/docker-compose.oceanbase.yml @@ -172,6 +172,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/document/public/deploy/docker/global/docker-compose.pg.yml b/document/public/deploy/docker/global/docker-compose.pg.yml index 314b5f72e..0c71286b1 100644 --- a/document/public/deploy/docker/global/docker-compose.pg.yml +++ b/document/public/deploy/docker/global/docker-compose.pg.yml @@ -154,6 +154,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/document/public/deploy/docker/global/docker-compose.ziliiz.yml b/document/public/deploy/docker/global/docker-compose.ziliiz.yml index 22a43ea25..d16ba0fa2 100644 --- a/document/public/deploy/docker/global/docker-compose.ziliiz.yml +++ b/document/public/deploy/docker/global/docker-compose.ziliiz.yml @@ -137,6 +137,8 @@ services: WORKFLOW_MAX_LOOP_TIMES: 100 # 对话文件过期天数 CHAT_FILE_EXPIRE_TIME: 7 + # 服务器接收请求,最大大小,单位 MB + SERVICE_REQUEST_MAX_CONTENT_LENGTH: 10 volumes: - ./config.json:/app/data/config.json diff --git a/packages/global/common/middle/tracks/constants.ts b/packages/global/common/middle/tracks/constants.ts index 385f3022e..e2bbb2ea1 100644 --- a/packages/global/common/middle/tracks/constants.ts +++ b/packages/global/common/middle/tracks/constants.ts @@ -4,5 +4,6 @@ export enum TrackEnum { useAppTemplate = 'useAppTemplate', createDataset = 'createDataset', appNodes = 'appNodes', - runSystemTool = 'runSystemTool' + runSystemTool = 'runSystemTool', + datasetSearch = 'datasetSearch' } diff --git a/packages/global/common/string/swagger.ts b/packages/global/common/string/swagger.ts index ccdfaccde..8b9b24f71 100644 --- a/packages/global/common/string/swagger.ts +++ b/packages/global/common/string/swagger.ts @@ -3,13 +3,3 @@ import SwaggerParser from '@apidevtools/swagger-parser'; export const loadOpenAPISchemaFromUrl = async (url: string) => { return SwaggerParser.bundle(url); }; - -export const checkOpenAPISchemaValid = async (str: string) => { - try { - const res = await SwaggerParser.validate(JSON.parse(str)); - console.log(res); - return !!res; - } catch (error) { - return false; - } -}; diff --git a/packages/global/core/chat/utils.ts b/packages/global/core/chat/utils.ts index a3c4efd05..e6fcfb530 100644 --- a/packages/global/core/chat/utils.ts +++ b/packages/global/core/chat/utils.ts @@ -92,10 +92,13 @@ export const filterPublicNodeResponseData = ({ responseDetail?: boolean; }) => { const publicNodeMap: Record = { + [FlowNodeTypeEnum.appModule]: true, [FlowNodeTypeEnum.pluginModule]: true, [FlowNodeTypeEnum.datasetSearchNode]: true, [FlowNodeTypeEnum.agent]: true, - [FlowNodeTypeEnum.pluginOutput]: true + [FlowNodeTypeEnum.pluginOutput]: true, + + [FlowNodeTypeEnum.runApp]: true }; const filedMap: Record = responseDetail diff --git a/packages/global/core/workflow/node/constant.ts b/packages/global/core/workflow/node/constant.ts index c04aeda37..27e5eedd1 100644 --- a/packages/global/core/workflow/node/constant.ts +++ b/packages/global/core/workflow/node/constant.ts @@ -134,9 +134,6 @@ export enum FlowNodeTypeEnum { classifyQuestion = 'classifyQuestion', contentExtract = 'contentExtract', httpRequest468 = 'httpRequest468', - runApp = 'app', - appModule = 'appModule', - pluginModule = 'pluginModule', pluginInput = 'pluginInput', pluginOutput = 'pluginOutput', queryExtension = 'cfr', @@ -156,7 +153,12 @@ export enum FlowNodeTypeEnum { loopEnd = 'loopEnd', formInput = 'formInput', tool = 'tool', - toolSet = 'toolSet' + toolSet = 'toolSet', + + // child: + appModule = 'appModule', + pluginModule = 'pluginModule', + runApp = 'app' } // node IO value type diff --git a/packages/global/core/workflow/utils.ts b/packages/global/core/workflow/utils.ts index b469c6da5..e262060a3 100644 --- a/packages/global/core/workflow/utils.ts +++ b/packages/global/core/workflow/utils.ts @@ -276,7 +276,7 @@ export const appData2FlowNodeIO = ({ description: '', valueType: WorkflowIOValueTypeEnum.any, required: item.required, - list: item.enums?.map((enumItem) => ({ + list: (item.list || item.enums)?.map((enumItem) => ({ label: enumItem.value, value: enumItem.value })) diff --git a/packages/service/common/cache/index.ts b/packages/service/common/cache/index.ts index 546e649e5..fb66539bb 100644 --- a/packages/service/common/cache/index.ts +++ b/packages/service/common/cache/index.ts @@ -3,6 +3,7 @@ import { getGlobalRedisConnection } from '../../common/redis'; import type { SystemCacheKeyEnum } from './type'; import { randomUUID } from 'node:crypto'; import { initCache } from './init'; +import { isProduction } from '@fastgpt/global/common/system/constants'; const cachePrefix = `VERSION_KEY:`; @@ -48,13 +49,15 @@ export const getCachedData = async (key: T, id?: s const versionKey = await getVersionKey(key, id); const isDisableCache = process.env.DISABLE_CACHE === 'true'; + const item = global.systemCache[key]; + // 命中缓存 - if (global.systemCache[key].versionKey === versionKey && !isDisableCache) { - return global.systemCache[key].data; + if ((isProduction || !item.devRefresh) && item.versionKey === versionKey && !isDisableCache) { + return item.data; } - const refreshedData = await global.systemCache[key].refreshFunc(); - global.systemCache[key].data = refreshedData; - global.systemCache[key].versionKey = versionKey; - return global.systemCache[key].data; + const refreshedData = await item.refreshFunc(); + item.data = refreshedData; + item.versionKey = versionKey; + return item.data; }; diff --git a/packages/service/common/cache/init.ts b/packages/service/common/cache/init.ts index a8a2696ac..bcc25ad86 100644 --- a/packages/service/common/cache/init.ts +++ b/packages/service/common/cache/init.ts @@ -6,7 +6,8 @@ export const initCache = () => { [SystemCacheKeyEnum.systemTool]: { versionKey: '', data: [], - refreshFunc: refreshSystemTools + refreshFunc: refreshSystemTools, + devRefresh: true }, [SystemCacheKeyEnum.modelPermission]: { versionKey: '', diff --git a/packages/service/common/cache/type.ts b/packages/service/common/cache/type.ts index 6fbcfe42a..5aae88139 100644 --- a/packages/service/common/cache/type.ts +++ b/packages/service/common/cache/type.ts @@ -15,6 +15,7 @@ type SystemCacheType = { versionKey: string; data: SystemCacheDataType[K]; refreshFunc: () => Promise; + devRefresh?: boolean; }; }; diff --git a/packages/service/common/file/image/controller.ts b/packages/service/common/file/image/controller.ts index a271905d4..a661a9b77 100644 --- a/packages/service/common/file/image/controller.ts +++ b/packages/service/common/file/image/controller.ts @@ -55,6 +55,46 @@ export async function uploadMongoImg({ return `${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`; } +export const copyImage = async ({ + teamId, + imageUrl, + session +}: { + teamId: string; + imageUrl: string; + session?: ClientSession; +}) => { + const imageId = getIdFromPath(imageUrl); + if (!imageId) return imageUrl; + + const image = await MongoImage.findOne( + { + _id: imageId, + teamId + }, + undefined, + { + session + } + ); + if (!image) return imageUrl; + + const [newImage] = await MongoImage.create( + [ + { + teamId, + binary: image.binary, + metadata: image.metadata + } + ], + { + session, + ordered: true + } + ); + + return `${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(newImage._id)}.${image.metadata?.mime?.split('/')[1]}`; +}; const getIdFromPath = (path?: string) => { if (!path) return; diff --git a/packages/service/common/middle/tracks/processor.ts b/packages/service/common/middle/tracks/processor.ts new file mode 100644 index 000000000..498f62dab --- /dev/null +++ b/packages/service/common/middle/tracks/processor.ts @@ -0,0 +1,79 @@ +import { delay } from '@fastgpt/global/common/system/utils'; +import { addLog } from '../../system/log'; +import { TrackModel } from './schema'; +import { TrackEnum } from '@fastgpt/global/common/middle/tracks/constants'; + +const batchUpdateTime = Number(process.env.TRACK_BATCH_UPDATE_TIME || 10000); + +const getCurrentTenMinuteBoundary = () => { + const now = new Date(); + const minutes = now.getMinutes(); + const tenMinuteBoundary = Math.floor(minutes / 10) * 10; + + const boundary = new Date(now); + boundary.setMinutes(tenMinuteBoundary, 0, 0); + return boundary; +}; + +export const trackTimerProcess = async () => { + while (true) { + await countTrackTimer(); + await delay(batchUpdateTime); + } +}; + +export const countTrackTimer = async () => { + if (!global.countTrackQueue || global.countTrackQueue.size === 0) { + return; + } + + const queuedItems = Array.from(global.countTrackQueue.values()); + global.countTrackQueue = new Map(); + + try { + const currentBoundary = getCurrentTenMinuteBoundary(); + + const bulkOps = queuedItems + .map(({ event, count, data }) => { + if (event === TrackEnum.datasetSearch) { + const { teamId, datasetId } = data; + + return [ + { + updateOne: { + filter: { + event, + teamId, + createTime: currentBoundary, + 'data.datasetId': datasetId + }, + update: [ + { + $set: { + event, + teamId, + createTime: { $ifNull: ['$createTime', currentBoundary] }, + data: { + datasetId, + count: { $add: [{ $ifNull: ['$data.count', 0] }, count] } + } + } + } + ], + upsert: true + } + } + ]; + } + return []; + }) + .flat(); + + if (bulkOps.length > 0) { + await TrackModel.bulkWrite(bulkOps); + addLog.info('Track timer processing success'); + } + } catch (error) { + addLog.error('Track timer processing error', error); + } +}; diff --git a/packages/service/common/middle/tracks/schema.ts b/packages/service/common/middle/tracks/schema.ts index 2a7286c63..2a34a126f 100644 --- a/packages/service/common/middle/tracks/schema.ts +++ b/packages/service/common/middle/tracks/schema.ts @@ -13,6 +13,15 @@ const TrackSchema = new Schema({ try { TrackSchema.index({ event: 1 }); + + TrackSchema.index( + { event: 1, teamId: 1, 'data.datasetId': 1, createTime: -1 }, + { + partialFilterExpression: { + 'data.datasetId': { $exists: true } + } + } + ); } catch (error) { console.log(error); } diff --git a/packages/service/common/middle/tracks/type.d.ts b/packages/service/common/middle/tracks/type.d.ts new file mode 100644 index 000000000..94ca6d9fe --- /dev/null +++ b/packages/service/common/middle/tracks/type.d.ts @@ -0,0 +1,17 @@ +export type TracksQueueType = { + event: TrackEnum; + data: Record; +}; + +declare global { + var countTrackQueue: + | Map< + string, + { + event: TrackEnum; + count: number; + data: Record; + } + > + | undefined; +} diff --git a/packages/service/common/middle/tracks/utils.ts b/packages/service/common/middle/tracks/utils.ts index 31cedd19b..146131231 100644 --- a/packages/service/common/middle/tracks/utils.ts +++ b/packages/service/common/middle/tracks/utils.ts @@ -25,6 +25,42 @@ const createTrack = ({ event, data }: { event: TrackEnum; data: Record; +}) => { + if (!global.feConfigs?.isPlus) return; + addLog.debug('Push tracks', { + event, + key + }); + + if (!global.countTrackQueue) { + global.countTrackQueue = new Map(); + } + + const value = global.countTrackQueue.get(key); + if (value) { + global.countTrackQueue.set(key, { + ...value, + count: value.count + 1 + }); + } else { + global.countTrackQueue.set(key, { + event, + data, + count: 1 + }); + } +}; + export const pushTrack = { login: (data: PushTrackCommonType & { type: `${OAuthEnum}` | 'password' }) => { return createTrack({ @@ -73,5 +109,18 @@ export const pushTrack = { event: TrackEnum.runSystemTool, data }); + }, + datasetSearch: (data: { teamId: string; datasetIds: string[] }) => { + if (!data.teamId) return; + data.datasetIds.forEach((datasetId) => { + pushCountTrack({ + event: TrackEnum.datasetSearch, + key: `${TrackEnum.datasetSearch}_${datasetId}`, + data: { + teamId: data.teamId, + datasetId + } + }); + }); } }; diff --git a/packages/service/common/system/constants.ts b/packages/service/common/system/constants.ts index 313e9031c..b108dd8dc 100644 --- a/packages/service/common/system/constants.ts +++ b/packages/service/common/system/constants.ts @@ -6,3 +6,6 @@ export const isFastGPTProService = () => !!global.systemConfig; export const isProVersion = () => { return !!global.feConfigs?.isPlus; }; + +export const serviceRequestMaxContentLength = + Number(process.env.SERVICE_REQUEST_MAX_CONTENT_LENGTH || 10) * 1024 * 1024; // 10MB diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index d76fa9689..c4e392874 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -549,6 +549,7 @@ const dbPluginFormat = (item: SystemPluginConfigSchemaType): SystemPluginTemplat /* FastsGPT-Pluign api: */ export const refreshSystemTools = async (): Promise => { const tools = await APIGetSystemToolList(); + // 从数据库里加载插件配置进行替换 const systemToolsArray = await MongoSystemPlugin.find({}).lean(); const systemTools = new Map(systemToolsArray.map((plugin) => [plugin.pluginId, plugin])); @@ -596,7 +597,7 @@ export const refreshSystemTools = async (): Promise dbPluginFormat(item)); const concatTools = [...formatTools, ...dbPlugins]; - concatTools.sort((a, b) => (a.pluginOrder ?? 0) - (b.pluginOrder ?? 0)); + concatTools.sort((a, b) => (a.pluginOrder ?? 999) - (b.pluginOrder ?? 999)); global.systemToolsTypeCache = {}; concatTools.forEach((item) => { diff --git a/packages/service/core/dataset/search/controller.ts b/packages/service/core/dataset/search/controller.ts index ad29c4de9..d0662dd37 100644 --- a/packages/service/core/dataset/search/controller.ts +++ b/packages/service/core/dataset/search/controller.ts @@ -32,10 +32,13 @@ import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { datasetSearchQueryExtension } from './utils'; import type { RerankModelItemType } from '@fastgpt/global/core/ai/model.d'; import { formatDatasetDataValue } from '../data/controller'; +import { pushTrack } from '../../../common/middle/tracks/utils'; export type SearchDatasetDataProps = { histories: ChatItemType[]; teamId: string; + uid?: string; + tmbId?: string; model: string; datasetIds: string[]; reRankQuery: string; @@ -900,6 +903,8 @@ export async function searchDatasetData( // token filter const filterMaxTokensResult = await filterDatasetDataByMaxTokens(scoreFilter, maxTokens); + pushTrack.datasetSearch({ datasetIds, teamId }); + return { searchRes: filterMaxTokensResult, embeddingTokens, diff --git a/packages/service/core/workflow/dispatch/dataset/search.ts b/packages/service/core/workflow/dispatch/dataset/search.ts index 0708c5f18..384efceb6 100644 --- a/packages/service/core/workflow/dispatch/dataset/search.ts +++ b/packages/service/core/workflow/dispatch/dataset/search.ts @@ -52,6 +52,7 @@ export async function dispatchDatasetSearch( const { runningAppInfo: { teamId }, runningUserInfo: { tmbId }, + uid, histories, node, params: { diff --git a/packages/service/core/workflow/dispatch/tools/http468.ts b/packages/service/core/workflow/dispatch/tools/http468.ts index 8df1576f8..f49294939 100644 --- a/packages/service/core/workflow/dispatch/tools/http468.ts +++ b/packages/service/core/workflow/dispatch/tools/http468.ts @@ -27,6 +27,7 @@ import { addLog } from '../../../../common/system/log'; import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools'; import { formatHttpError } from '../utils'; import { isInternalAddress } from '../../../../common/system/utils'; +import { serviceRequestMaxContentLength } from '../../../../common/system/constants'; type PropsArrType = { key: string; @@ -505,6 +506,7 @@ async function fetchData({ const { data: response } = await axios({ method, + maxContentLength: serviceRequestMaxContentLength, baseURL: `http://${SERVICE_LOCAL_HOST}`, url, headers: { diff --git a/packages/web/common/system/utils.ts b/packages/web/common/system/utils.ts index db05a54fb..cb7e7a3e7 100644 --- a/packages/web/common/system/utils.ts +++ b/packages/web/common/system/utils.ts @@ -10,11 +10,10 @@ export const subRoute = process.env.NEXT_PUBLIC_BASE_URL; export const getWebReqUrl = (url: string = '') => { if (!url) return '/'; - const baseUrl = process.env.NEXT_PUBLIC_BASE_URL; - if (!baseUrl) return url; + if (!subRoute) return url; - if (!url.startsWith('/') || url.startsWith(baseUrl)) return url; - return `${baseUrl}${url}`; + if (!url.startsWith('/') || url.startsWith(subRoute)) return url; + return `${subRoute}${url}`; }; export const isMobile = () => { diff --git a/projects/app/.env.template b/projects/app/.env.template index 308209973..fd51a63b3 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -83,6 +83,8 @@ USE_IP_LIMIT=false WORKFLOW_MAX_RUN_TIMES=500 # 循环最大运行次数,避免极端的死循环情况 WORKFLOW_MAX_LOOP_TIMES=50 +# 服务器接收请求,最大大小,单位 MB +SERVICE_REQUEST_MAX_CONTENT_LENGTH=10 # 启用内网 IP 检查 CHECK_INTERNAL_IP=false # 密码错误锁时长:s diff --git a/projects/app/package.json b/projects/app/package.json index efede6268..4908e30cd 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.13.0", + "version": "4.13.1", "private": false, "scripts": { "dev": "next dev", diff --git a/projects/app/src/components/Markdown/utils.ts b/projects/app/src/components/Markdown/utils.ts index 544585bba..75722b5f3 100644 --- a/projects/app/src/components/Markdown/utils.ts +++ b/projects/app/src/components/Markdown/utils.ts @@ -14,6 +14,11 @@ export enum CodeClassNameEnum { } export const mdTextFormat = (text: string) => { + // 处理 Windows 文件路径中的反斜杠,防止被 Markdown 转义:C:\path\file 或 c:\path\file + text = text.replace(/([A-Za-z]:\\[^\s`\[\]()]*)/g, (match) => { + return match.replace(/\\/g, '\\\\'); + }); + // NextChat function - Format latex to $$ const pattern = /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g; text = text.replace(pattern, (match, codeBlock, squareBracket, roundBracket) => { diff --git a/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx b/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx index 56881a504..de086a48c 100644 --- a/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx +++ b/projects/app/src/components/core/chat/components/Interactive/InteractiveComponents.tsx @@ -94,7 +94,6 @@ export const FormInputComponent = React.memo(function FormInputComponent({ inputType={inputType} value={value} onChange={onChange} - placeholder={input.label} isDisabled={submitted} isInvalid={!!error} maxLength={input.maxLength} diff --git a/projects/app/src/instrumentation.ts b/projects/app/src/instrumentation.ts index 0a69f20c9..28e28e766 100644 --- a/projects/app/src/instrumentation.ts +++ b/projects/app/src/instrumentation.ts @@ -20,7 +20,8 @@ export async function register() { { preLoadWorker }, { loadSystemModels }, { connectSignoz }, - { getSystemTools } + { getSystemTools }, + { trackTimerProcess } ] = await Promise.all([ import('@fastgpt/service/common/mongo/init'), import('@fastgpt/service/common/mongo/index'), @@ -34,7 +35,8 @@ export async function register() { import('@fastgpt/service/worker/preload'), import('@fastgpt/service/core/ai/config/utils'), import('@fastgpt/service/common/otel/trace/register'), - import('@fastgpt/service/core/app/plugin/controller') + import('@fastgpt/service/core/app/plugin/controller'), + import('@fastgpt/service/common/middle/tracks/processor') ]); // connect to signoz @@ -61,6 +63,7 @@ export async function register() { startMongoWatch(); startCron(); startTrainingQueue(true); + trackTimerProcess(); console.log('Init system success'); } diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx index 7f370a7a0..88c76c0e1 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/components/NodeTemplates/list.tsx @@ -275,6 +275,13 @@ const NodeTemplateList = ({ }); const currentNode = nodeList.find((node) => node.nodeId === handleParams?.nodeId); + if (templateNode.flowNodeType === FlowNodeTypeEnum.loop && !!currentNode?.parentNodeId) { + toast({ + status: 'warning', + title: t('workflow:can_not_loop') + }); + return; + } const newNode = nodeTemplate2FlowNode({ template: { diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/InputFormEditModal.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/InputFormEditModal.tsx index b7aaa3db1..a05ce2b65 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/InputFormEditModal.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/InputFormEditModal.tsx @@ -166,6 +166,7 @@ const InputFormEditModal = ({ }} onClick={() => { setValue('type', item.value); + setValue('defaultValue', ''); }} > { setValue('renderTypeList', item.value); + setValue('defaultValue', ''); }} > { + const avatar = await copyImage({ + teamId, + imageUrl: app.avatar, + session + }); + + const appId = await onCreateApp({ + parentId: app.parentId, + name: app.name + ' Copy', + intro: app.intro, + avatar, + type: app.type, + modules: app.modules, + edges: app.edges, + chatConfig: app.chatConfig, + teamId: app.teamId, + tmbId, + pluginData: app.pluginData, + session + }); + + return { appId }; }); + (async () => { addAuditLog({ tmbId, diff --git a/projects/app/src/pages/api/core/dataset/searchTest.ts b/projects/app/src/pages/api/core/dataset/searchTest.ts index 6dfb5b771..4174cbd9a 100644 --- a/projects/app/src/pages/api/core/dataset/searchTest.ts +++ b/projects/app/src/pages/api/core/dataset/searchTest.ts @@ -47,7 +47,7 @@ async function handler(req: ApiRequestProps): Promise', '请求错误', err); const isOutlinkPage = { - '/chat/share': true, - '/chat': true, - '/login': true + [`${subRoute}/chat/share`]: true, + [`${subRoute}/chat`]: true, + [`${subRoute}/login`]: true }[window.location.pathname]; const data = err?.response?.data || err; diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index 05ef99765..61f10a3d2 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -87,7 +87,9 @@ export const storeNode2FlowNode = ({ moduleTemplatesFlat.find((template) => template.flowNodeType === storeNode.flowNodeType) || EmptyNode; - const templateInputs = template.inputs.filter((input) => !input.canEdit); + const templateInputs = template.inputs.filter( + (input) => !input.canEdit && input.deprecated !== true + ); const templateOutputs = template.outputs.filter( (output) => output.type !== FlowNodeOutputTypeEnum.dynamic ); diff --git a/projects/app/test/cases/web/workflow/store2flow.deprecated.test.ts b/projects/app/test/cases/web/workflow/store2flow.deprecated.test.ts index a83dc648b..8627ab0d6 100644 --- a/projects/app/test/cases/web/workflow/store2flow.deprecated.test.ts +++ b/projects/app/test/cases/web/workflow/store2flow.deprecated.test.ts @@ -75,7 +75,7 @@ describe('storeNode2FlowNode with deprecated inputs/outputs', () => { const deprecatedInput = result.data.inputs.find((input) => input.key === 'deprecatedInput'); expect(deprecatedInput).toBeDefined(); - expect(deprecatedInput?.deprecated).toBe(true); + expect(deprecatedInput?.deprecated).toBe(undefined); const deprecatedOutput = result.data.outputs.find( (output) => output.key === 'deprecatedOutput' diff --git a/projects/app/test/web/common/api/request.test.ts b/projects/app/test/web/common/api/request.test.ts index 2ee693473..247f870cb 100644 --- a/projects/app/test/web/common/api/request.test.ts +++ b/projects/app/test/web/common/api/request.test.ts @@ -9,8 +9,26 @@ import { import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { TOKEN_ERROR_CODE } from '@fastgpt/global/common/error/errorCode'; +// Mock all required dependencies vi.mock('@fastgpt/web/common/system/utils', () => ({ - getWebReqUrl: vi.fn().mockReturnValue('http://test.com') + getWebReqUrl: vi.fn().mockReturnValue('http://test.com'), + subRoute: '/test-route' // Add subRoute mock +})); + +vi.mock('@/web/support/user/auth', () => ({ + clearToken: vi.fn() +})); + +vi.mock('../system/useSystemStore', () => ({ + useSystemStore: { + getState: vi.fn().mockReturnValue({ + setNotSufficientModalType: vi.fn() + }) + } +})); + +vi.mock('@fastgpt/web/i18n/utils', () => ({ + i18nT: vi.fn().mockReturnValue('Unauthorized token') })); // Mock window.location @@ -99,7 +117,7 @@ describe('request utils', () => { }); it('should handle token error for outlink page', async () => { - mockLocation.pathname = '/chat/share'; + mockLocation.pathname = '/test-route/chat/share'; const err = { response: { data: {