From c53caaf6504a64f682a12f62b22e1902ad10f791 Mon Sep 17 00:00:00 2001 From: heheer Date: Wed, 7 Jan 2026 16:27:31 +0800 Subject: [PATCH 1/9] fix v4.14.5 bugs --- document/content/docs/upgrading/4-14/4145.mdx | 6 +++ document/data/doc-last-modified.json | 4 +- .../global/openapi/core/chat/controler/api.ts | 5 +- packages/service/core/chat/controller.ts | 54 ++++++++++--------- .../workflow/dispatch/tools/runUpdateVar.ts | 13 +---- .../core/chat/components/AIResponseBox.tsx | 16 ++++-- .../Flow/nodes/NodeCode/Copilot.tsx | 41 ++++++++------ .../Flow/nodes/NodeCode/index.tsx | 4 +- .../app/src/pages/api/v2/chat/completions.ts | 17 ++++-- 9 files changed, 94 insertions(+), 66 deletions(-) diff --git a/document/content/docs/upgrading/4-14/4145.mdx b/document/content/docs/upgrading/4-14/4145.mdx index 494087bb7208..3d9d990074c0 100644 --- a/document/content/docs/upgrading/4-14/4145.mdx +++ b/document/content/docs/upgrading/4-14/4145.mdx @@ -36,5 +36,11 @@ description: 'FastGPT V4.14.5 更新说明' 5. 加载默认模型时,maxTokens 字段未赋值,导致模型最大响应值配置为空。 6. S3 文件清理队列因网络稳定问题出现阻塞,导致删除任务不再执行。 7. 对话日志接口适配 mongo4.x 语法。 +8. 变量更新节点将文件 URL 字符串数组错误转换为对象数组。 +9. 多个表单输入节点共享 sessionStorage 导致默认值不显示。 +10. 代码运行节点切换语言后,AI 仍使用旧语言生成代码。 +11. 多个自定义反馈节点并发写入触发数据库写入冲突。 +12. 交互节点后续的自定义反馈节点写入失败。 + ## 插件 diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json index 315e4521f8f9..4c9c1630ab1f 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-06T18:19:42+08:00", + "document/content/docs/toc.mdx": "2026-01-06T13:25:46+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", @@ -204,4 +204,4 @@ "document/content/docs/use-cases/external-integration/openapi.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/use-cases/external-integration/wecom.mdx": "2025-12-10T20:07:05+08:00", "document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00" -} \ No newline at end of file +} diff --git a/packages/global/openapi/core/chat/controler/api.ts b/packages/global/openapi/core/chat/controler/api.ts index 76d1b4314d04..9eb891584f18 100644 --- a/packages/global/openapi/core/chat/controler/api.ts +++ b/packages/global/openapi/core/chat/controler/api.ts @@ -8,7 +8,10 @@ export const InitChatQuerySchema = z .object({ appId: ObjectIdSchema.describe('应用ID'), chatId: z.string().min(1).describe('对话ID'), - loadCustomFeedbacks: z.coerce.boolean().optional().describe('是否加载自定义反馈') + loadCustomFeedbacks: z + .union([z.boolean(), z.string().transform((val) => val === 'true')]) + .optional() + .describe('是否加载自定义反馈') }) .meta({ example: { diff --git a/packages/service/core/chat/controller.ts b/packages/service/core/chat/controller.ts index 1ef39b10c033..f48e2051769a 100644 --- a/packages/service/core/chat/controller.ts +++ b/packages/service/core/chat/controller.ts @@ -194,6 +194,9 @@ export async function getChatItems({ return { histories, total, hasMorePrev, hasMoreNext }; } +// Maintain a write queue per document to avoid concurrent write conflicts +const customFeedbackQueues = new Map>(); + export const addCustomFeedbacks = async ({ appId, chatId, @@ -207,32 +210,33 @@ export const addCustomFeedbacks = async ({ }) => { if (!chatId || !dataId) return; - try { - await mongoSessionRun(async (session) => { - // Add custom feedbacks to ChatItem - await MongoChatItem.updateOne( - { - appId, - chatId, - dataId - }, - { - $push: { customFeedbacks: { $each: feedbacks } } - }, - { session } - ); - - // Update ChatLog feedback statistics - await updateChatFeedbackCount({ - appId, - chatId, - session - }); + const queueKey = `${chatId}_${dataId}`; + const previousTask = customFeedbackQueues.get(queueKey) || Promise.resolve(); + + const task = previousTask + .then(() => + mongoSessionRun(async (session) => { + await MongoChatItem.updateOne( + { appId, chatId, dataId }, + { $push: { customFeedbacks: { $each: feedbacks } } }, + { session } + ); + + await updateChatFeedbackCount({ appId, chatId, session }); + }) + ) + .catch((error) => { + addLog.error('addCustomFeedbacks error', error); + throw error; + }) + .finally(() => { + if (customFeedbackQueues.get(queueKey) === task) { + customFeedbackQueues.delete(queueKey); + } }); - } catch (error) { - addLog.error('addCustomFeedbacks error', error); - throw error; - } + + customFeedbackQueues.set(queueKey, task); + await task; }; /** diff --git a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts index cb16b4c0d5fc..0196c89da693 100644 --- a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts +++ b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts @@ -14,8 +14,6 @@ import { type ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/ import { runtimeSystemVar2StoreType } from '../utils'; import { isValidReferenceValue } from '@fastgpt/global/core/workflow/utils'; import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils'; -import { parseUrlToFileType } from '@fastgpt/global/common/file/tools'; -import { z } from 'zod'; type Props = ModuleDispatchProps<{ [NodeInputKeyEnum.updateList]: TUpdateListItem[]; @@ -36,7 +34,6 @@ export const dispatchUpdateVariable = async (props: Props): Promise => const { updateList } = params; const nodeIds = runtimeNodes.map((node) => node.nodeId); - const urlSchema = z.string().url(); const result = updateList.map((item) => { const variable = item.variable; @@ -66,19 +63,11 @@ export const dispatchUpdateVariable = async (props: Props): Promise => return valueTypeFormat(val, item.valueType); } else { - const val = getReferenceVariableValue({ + return getReferenceVariableValue({ value: item.value, variables, nodes: runtimeNodes }); - - if ( - Array.isArray(val) && - val.every((url) => typeof url === 'string' && urlSchema.safeParse(url).success) - ) { - return val.map((url) => parseUrlToFileType(url)).filter(Boolean); - } - return val; } })(); diff --git a/projects/app/src/components/core/chat/components/AIResponseBox.tsx b/projects/app/src/components/core/chat/components/AIResponseBox.tsx index 1a05f0692f4f..2438267629ef 100644 --- a/projects/app/src/components/core/chat/components/AIResponseBox.tsx +++ b/projects/app/src/components/core/chat/components/AIResponseBox.tsx @@ -225,9 +225,15 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ }) { const { t } = useTranslation(); + // 使用 entryNodeIds 生成唯一的 storage key,避免不同表单节点共用同一个缓存 + const uniqueStorageKey = useMemo(() => { + const nodeIds = interactive.entryNodeIds?.join('-') || 'unknown'; + return `${chatItemDataId}_${nodeIds}`; + }, [chatItemDataId, interactive.entryNodeIds]); + const defaultValues = useMemo(() => { if (interactive.type === 'userInput') { - return interactive.params.inputForm?.reduce((acc: Record, item, index) => { + return interactive.params.inputForm?.reduce((acc: Record, item) => { // 使用 ?? 运算符,只有 undefined 或 null 时才使用 defaultValue acc[item.key] = item.value ?? item.defaultValue; return acc; @@ -239,7 +245,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ const handleFormSubmit = useCallback( (data: Record) => { const finalData: Record = {}; - interactive.params.inputForm?.forEach((item, index) => { + interactive.params.inputForm?.forEach((item) => { if (item.key in data) { finalData[item.key] = data[item.key]; } @@ -266,7 +272,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ } } }); - sessionStorage.setItem(`interactiveForm_${chatItemDataId}`, JSON.stringify(dataToSave)); + sessionStorage.setItem(`interactiveForm_${uniqueStorageKey}`, JSON.stringify(dataToSave)); } onSendPrompt({ @@ -274,7 +280,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ isInteractivePrompt: true }); }, - [interactive.params.inputForm, chatItemDataId] + [interactive.params.inputForm, uniqueStorageKey] ); return ( @@ -282,7 +288,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({ (