From 8450a44d3556d3f77bb18fdeddb2c7adac142bca Mon Sep 17 00:00:00 2001 From: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:10:54 +0800 Subject: [PATCH] V4.14.5.1 dev (#6290) * chore: cherry pick some commits from v4.14.6-dev (#6287) * fix: custom domain limitation (#6265) * fix: system secret (#6259) * fix: system secret * chore: update docs * chore: docs * fix password variable & datetime picker (#6276) * fix password variable & datetime picker * doc * chore: cherry pick some commits from v4.14.6-dev (#6287) * fix: custom domain limitation (#6265) * fix: system secret (#6259) * fix: system secret * chore: update docs * chore: docs * doc * chore: docs --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: Finley Ge * perf: extname computed (#6285) * perf: extname computed * chore: handle hash or query flags --------- Co-authored-by: Finley Ge * chore: docs (#6291) --------- Co-authored-by: heheer Co-authored-by: Archer <545436317@qq.com> --- document/content/docs/toc.mdx | 1 + .../content/docs/upgrading/4-14/41451.mdx | 48 +++++ document/content/docs/upgrading/4-14/4146.mdx | 25 +++ document/data/doc-last-modified.json | 7 +- packages/global/common/string/tools.ts | 16 +- packages/service/core/app/tool/controller.ts | 1 - .../common/DateTimePicker/index.tsx | 102 ++++++---- projects/app/package.json | 2 +- .../core/app/formRender/LabelAndForm.tsx | 11 +- .../nodes/NodePluginIO/InputTypeConfig.tsx | 10 +- .../templates/DynamicInputs/index.tsx | 2 +- .../src/pages/account/customDomain/index.tsx | 8 +- .../app/src/pages/api/admin/initv41451.ts | 101 +++++++++ .../api/core/plugin/admin/tool/update.ts | 3 +- .../cases/global/common/string/string.test.ts | 191 ++++++++++++++++++ 15 files changed, 464 insertions(+), 64 deletions(-) create mode 100644 document/content/docs/upgrading/4-14/41451.mdx create mode 100644 projects/app/src/pages/api/admin/initv41451.ts create mode 100644 test/cases/global/common/string/string.test.ts diff --git a/document/content/docs/toc.mdx b/document/content/docs/toc.mdx index 60fae94d84..376d11d533 100644 --- a/document/content/docs/toc.mdx +++ b/document/content/docs/toc.mdx @@ -119,6 +119,7 @@ description: FastGPT 文档目录 - [/docs/upgrading/4-14/4143](/docs/upgrading/4-14/4143) - [/docs/upgrading/4-14/4144](/docs/upgrading/4-14/4144) - [/docs/upgrading/4-14/4145](/docs/upgrading/4-14/4145) +- [/docs/upgrading/4-14/41451](/docs/upgrading/4-14/41451) - [/docs/upgrading/4-14/4146](/docs/upgrading/4-14/4146) - [/docs/upgrading/4-8/40](/docs/upgrading/4-8/40) - [/docs/upgrading/4-8/41](/docs/upgrading/4-8/41) diff --git a/document/content/docs/upgrading/4-14/41451.mdx b/document/content/docs/upgrading/4-14/41451.mdx new file mode 100644 index 0000000000..6d46e74dc5 --- /dev/null +++ b/document/content/docs/upgrading/4-14/41451.mdx @@ -0,0 +1,48 @@ +--- +title: 'V4.14.5.1' +description: 'FastGPT V4.14.5.1 更新说明' +--- + + +## 🚀 新增内容 + +1. Markdown 表格支持导出 csv。 + +### 1. 更新镜像: + +- 更新 FastGPT 镜像tag: v4.14.5.1 +- 更新 FastGPT 商业版镜像tag: v4.14.5.1 +- 更新 fastgpt-plugin 镜像 tag: v0.4.0 +- mcp_server 无需更新 +- Sandbox 无需更新 +- AIProxy 无需更新 +- mongo 无需更新 + +### 2. 执行升级脚本 + +从任意终端,发起 1 个 HTTP 请求。其中 `{{rootkey}}` 替换成环境变量里的 `rootkey`;`{{host}}` 替换成**FastGPT 域名**。 + +```bash +curl --location --request POST 'https://{{host}}/api/admin/initv41451' \ +--header 'rootkey: {{rootkey}}' \ +--header 'Content-Type: application/json' +``` + +1. 迁移系统工具的系统密钥配置 + +## ⚙️ 优化 + +1. 工作流触摸板移动时,遇到输入框后会被强制阻拦。 +2. 工作流粘贴节点,精确按鼠标位置粘贴。 +3. 精确移除请求 LLM 时多余的系统字段,避免部分模型接口报错。 +4. 使用 path.extname 从 URL 获取文件扩展名 + +## 🐛 修复 + +1. 系统工具工具集设置系统密钥后,子工具无法读取到设置的系统密钥 +2. 密码类型的全局变量,必填规则校验错误。 +3. 时间类型的全局变量,选择月份被遮挡。 +4. 手动复制弹窗,换行丢失。 +5. 未传入文件上传类型变量,对话接口报错。 + +## 插件 diff --git a/document/content/docs/upgrading/4-14/4146.mdx b/document/content/docs/upgrading/4-14/4146.mdx index 257baea13c..c1d16b2904 100644 --- a/document/content/docs/upgrading/4-14/4146.mdx +++ b/document/content/docs/upgrading/4-14/4146.mdx @@ -7,6 +7,29 @@ description: 'FastGPT V4.14.6 更新说明' ## 🚀 新增内容 1. Markdown 表格支持导出 csv。 +2. 系统工具可配置自定义的分类属性 + +### 1. 更新镜像: + +- 更新 FastGPT 镜像tag: v4.14.6 +- 更新 FastGPT 商业版镜像tag: v4.14.6 +- 更新 fastgpt-plugin 镜像 tag: v0.4.0 +- mcp_server 无需更新 +- Sandbox 无需更新 +- AIProxy 无需更新 +- mongo 5.x 版本修改成 5.0.32 版本,解决 CVE-2025-14847 漏洞。直接修改镜像 tag 成 `5.0.32`。 + +### 2. 执行升级脚本 + +从任意终端,发起 1 个 HTTP 请求。其中 `{{rootkey}}` 替换成环境变量里的 `rootkey`;`{{host}}` 替换成**FastGPT 域名**。 + +```bash +curl --location --request POST 'https://{{host}}/api/admin/initv4146' \ +--header 'rootkey: {{rootkey}}' \ +--header 'Content-Type: application/json' +``` + +1. 迁移系统工具的系统密钥配置 ## ⚙️ 优化 @@ -16,4 +39,6 @@ description: 'FastGPT V4.14.6 更新说明' ## 🐛 修复 +1. 系统工具工具集设置系统密钥后,子工具无法读取到设置的系统密钥 + ## 插件 diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index 855c7fc3b3..fdb2a9fdba 100644 --- a/document/data/doc-last-modified.json +++ b/document/data/doc-last-modified.json @@ -104,7 +104,7 @@ "document/content/docs/protocol/terms.en.mdx": "2025-12-15T23:36:54+08:00", "document/content/docs/protocol/terms.mdx": "2025-12-15T23:36:54+08:00", "document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00", - "document/content/docs/toc.mdx": "2026-01-11T21:09:27+08:00", + "document/content/docs/toc.mdx": "2026-01-19T15:56:02+08:00", "document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+08:00", "document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00", @@ -122,8 +122,9 @@ "document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00", "document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00", "document/content/docs/upgrading/4-14/4144.mdx": "2025-12-16T14:56:04+08:00", - "document/content/docs/upgrading/4-14/4145.mdx": "2026-01-13T19:11:02+08:00", - "document/content/docs/upgrading/4-14/4146.mdx": "2026-01-12T21:07:44+08:00", + "document/content/docs/upgrading/4-14/4145.mdx": "2026-01-18T23:59:15+08:00", + "document/content/docs/upgrading/4-14/41451.mdx": "2026-01-19T16:10:06+08:00", + "document/content/docs/upgrading/4-14/4146.mdx": "2026-01-19T15:56:02+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/packages/global/common/string/tools.ts b/packages/global/common/string/tools.ts index fd306e7ff2..63747bbe12 100644 --- a/packages/global/common/string/tools.ts +++ b/packages/global/common/string/tools.ts @@ -1,5 +1,6 @@ import crypto from 'crypto'; import { customAlphabet } from 'nanoid'; +import path from 'path'; /* check string is a web link */ export function strIsLink(str?: string) { @@ -193,13 +194,14 @@ export const sliceStrStartEnd = (str: string, start: number, end: number) => { => pdf */ export const parseFileExtensionFromUrl = (url = '') => { - // Remove query params - const urlWithoutQuery = url.split('?')[0]; - // Get file name - const fileName = urlWithoutQuery.split('/').pop() || ''; - // Get file extension - const extension = fileName.split('.').pop(); - return (extension || '').toLowerCase(); + // Remove query params and hash first + const urlWithoutQuery = url.split('?')[0].split('#')[0]; + const extension = path.extname(urlWithoutQuery); + // path.extname returns '.ext' or '' + if (extension.startsWith('.')) { + return extension.slice(1).toLowerCase(); + } + return ''; }; export const formatNumberWithUnit = (num: number, locale: string = 'zh-CN'): string => { diff --git a/packages/service/core/app/tool/controller.ts b/packages/service/core/app/tool/controller.ts index d2ed14e9d3..a48723fc32 100644 --- a/packages/service/core/app/tool/controller.ts +++ b/packages/service/core/app/tool/controller.ts @@ -633,7 +633,6 @@ export const refreshSystemTools = async (): Promise = defaultInstalled: dbPluginConfig?.defaultInstalled ?? false, inputList: item?.secretInputConfig, hasSystemSecret: !!dbPluginConfig?.inputListVal, - originCost: dbPluginConfig?.originCost ?? 0, currentCost: dbPluginConfig?.currentCost ?? 0, systemKeyCost: dbPluginConfig?.systemKeyCost ?? 0, diff --git a/packages/web/components/common/DateTimePicker/index.tsx b/packages/web/components/common/DateTimePicker/index.tsx index be6784f8b0..e32d005937 100644 --- a/packages/web/components/common/DateTimePicker/index.tsx +++ b/packages/web/components/common/DateTimePicker/index.tsx @@ -1,6 +1,6 @@ import React, { useState, useMemo, useRef, useEffect } from 'react'; import type { BoxProps } from '@chakra-ui/react'; -import { Box, Card, Flex, useOutsideClick } from '@chakra-ui/react'; +import { Box, Card, Flex, Portal, useOutsideClick } from '@chakra-ui/react'; import { format } from 'date-fns'; import type { Matcher } from 'react-day-picker'; import { DayPicker } from 'react-day-picker'; @@ -22,33 +22,60 @@ const DateTimePicker = ({ selectedDateTime?: Date; disabled?: Matcher[]; } & Omit) => { - const OutRangeRef = useRef(null); + const containerRef = useRef(null); + const popoverRef = useRef(null); const [selectedDate, setSelectedDate] = useState( selectedDateTime || defaultDate ); const [showSelected, setShowSelected] = useState(false); + const [position, setPosition] = useState({ top: 0, left: 0 }); useEffect(() => { setSelectedDate(selectedDateTime); }, [selectedDateTime]); + useEffect(() => { + if (showSelected && containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + if (popPosition === 'top') { + setPosition({ + top: rect.top - 4, + left: rect.left + }); + } else { + setPosition({ + top: rect.bottom + 4, + left: rect.left + }); + } + } + }, [showSelected, popPosition]); + + // 点击外部关闭 + useEffect(() => { + if (!showSelected) return; + const handleClick = (e: MouseEvent) => { + if ( + containerRef.current?.contains(e.target as Node) || + popoverRef.current?.contains(e.target as Node) + ) { + return; + } + setShowSelected(false); + }; + document.addEventListener('mousedown', handleClick); + return () => document.removeEventListener('mousedown', handleClick); + }, [showSelected]); + const formatSelected = useMemo(() => { if (selectedDate) { - const dateStr = format(selectedDate, 'y/MM/dd'); - return dateStr; + return format(selectedDate, 'y/MM/dd'); } return ''; }, [selectedDate]); - useOutsideClick({ - ref: OutRangeRef, - handler: () => { - setShowSelected(false); - } - }); - return ( - + {showSelected && ( - - { - setSelectedDate(date); - onChange?.(date); - setShowSelected(false); + + - + > + { + setSelectedDate(date); + onChange?.(date); + setShowSelected(false); + }} + /> + + )} ); diff --git a/projects/app/package.json b/projects/app/package.json index 44b7617ccf..800fbe1cd4 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.14.5", + "version": "4.14.5.1", "private": false, "scripts": { "dev": "npm run build:workers && next dev", diff --git a/projects/app/src/components/core/app/formRender/LabelAndForm.tsx b/projects/app/src/components/core/app/formRender/LabelAndForm.tsx index 8a24513b7b..83c9d0970e 100644 --- a/projects/app/src/components/core/app/formRender/LabelAndForm.tsx +++ b/projects/app/src/components/core/app/formRender/LabelAndForm.tsx @@ -66,14 +66,17 @@ const LabelAndFormRender = ({ rules={{ validate: (value) => { if (typeof value === 'number' || typeof value === 'boolean') return true; - if (inputType === InputTypeEnum.password && props.minLength) { - if (!value || typeof value !== 'object' || !value.value) return false; - if (value.value.length < props.minLength) { + if (!required) return true; + // 密码类型特殊处理:已加密的密码格式为 { value: '', secret: 'xxx' } + if (inputType === InputTypeEnum.password) { + const hasValue = value && typeof value === 'object' && (value.value || value.secret); + if (!hasValue) return false; + // 有 minLength 要求且正在输入新值时,检查长度 + if (props.minLength && value.value && value.value.length < props.minLength) { return t(`common:min_length`, { minLenth: props.minLength }); } return true; } - if (!required) return true; if (inputType === InputTypeEnum.fileSelect) { if (!value || !Array.isArray(value) || value.length === 0) { diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx index 2d61ad17e1..f4cfc4b505 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx @@ -485,7 +485,7 @@ const InputTypeConfig = ({ setValue('timeRangeStart', date); }} popPosition="top" - timeGranularity={timeGranularity} + timeGranularity={timeGranularity || 'day'} maxDate={timeRangeEnd ? new Date(timeRangeEnd) : undefined} /> @@ -499,7 +499,7 @@ const InputTypeConfig = ({ setValue('timeRangeEnd', date); }} popPosition="top" - timeGranularity={timeGranularity} + timeGranularity={timeGranularity || 'day'} minDate={timeRangeStart ? new Date(timeRangeStart) : undefined} /> @@ -623,7 +623,7 @@ const InputTypeConfig = ({ setValue('defaultValue', date); }} popPosition="top" - timeGranularity={timeGranularity} + timeGranularity={timeGranularity || 'day'} minDate={timeRangeStart ? new Date(timeRangeStart) : undefined} maxDate={timeRangeEnd ? new Date(timeRangeEnd) : undefined} /> @@ -640,7 +640,7 @@ const InputTypeConfig = ({ setValue('defaultValue', [date, timeRangeEndDefault]); }} popPosition="top" - timeGranularity={timeGranularity} + timeGranularity={timeGranularity || 'day'} minDate={timeRangeStart ? new Date(timeRangeStart) : undefined} maxDate={ timeRangeEndDefault && timeRangeEnd @@ -668,7 +668,7 @@ const InputTypeConfig = ({ setValue('defaultValue', [timeRangeStartDefault, date]); }} popPosition="top" - timeGranularity={timeGranularity} + timeGranularity={timeGranularity || 'day'} minDate={ timeRangeStartDefault && timeRangeStart ? new Date( diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx index 46705e3c54..3bb0d05a0c 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx @@ -237,7 +237,7 @@ const Reference = ({ borderLeftColor: 'transparent', borderRightColor: 'transparent', isDisabled: isEmptyItem, - minW: '240px', + w: '240px', _hover: { borderColor: 'blue.300' } diff --git a/projects/app/src/pages/account/customDomain/index.tsx b/projects/app/src/pages/account/customDomain/index.tsx index 031e4fe238..04f75b20a0 100644 --- a/projects/app/src/pages/account/customDomain/index.tsx +++ b/projects/app/src/pages/account/customDomain/index.tsx @@ -74,11 +74,11 @@ const CustomDomain = () => { const [editDomain, setEditDomain] = useState(undefined); // 检查用户是否有 advanced 套餐 - const isAdvancedPlan = useMemo(() => { + const isSupportCustomDomain = useMemo(() => { const plan = teamPlanStatus?.standard; if (!plan) return false; - return plan.customDomain && plan.customDomain > 0; + return !!(plan.customDomain && plan.customDomain > 0); }, [teamPlanStatus?.standard]); return ( @@ -100,7 +100,7 @@ const CustomDomain = () => { @@ -185,7 +185,7 @@ const CustomDomain = () => { > {t('account:upgrade_to_use_custom_domain')}