Commit 0aebb734 by Archer Committed by GitHub

fix: share field filter (#6905)

* fix: share field filter

* perf: review
parent f4e09007
{ {
"recommendations": [ "recommendations": [
"dbaeumer.vscode-eslint",
"alexcvzz.vscode-sqlite" "alexcvzz.vscode-sqlite"
] ]
} }
...@@ -2,7 +2,22 @@ ...@@ -2,7 +2,22 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.mouseWheelZoom": true, "editor.mouseWheelZoom": true,
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"prettier.prettierPath": "node_modules/prettier", "prettier.prettierPath": "node_modules/prettier",
"eslint.useFlatConfig": true,
"eslint.workingDirectories": [
{
"mode": "auto"
}
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"packages/web/i18n", "packages/web/i18n",
], ],
......
---
title: 'V4.14.19'
description: 'FastGPT V4.14.19 release notes'
---
## Upgrade Guide
### 1. Update image tags
- Update the fastgpt-app image tag (FastGPT main service): v4.14.19
- Update the fastgpt-pro image tag (FastGPT commercial edition): v4.14.19
## ⚙️ Improvements
1. Improved browser compatibility for lower kernel versions.
## 🐛 Fixes
1. Fixed an issue where form inputs did not filter out icons for file-type fields, causing oversized request bodies.
2. Fixed an issue where shared links did not correctly show the sandbox file entry.
---
title: 'V4.14.19'
description: 'FastGPT V4.14.19 更新说明'
---
## 升级指南
### 1. 更新镜像 tag
- 更新 fastgpt-app(fastgpt 主服务) 镜像 tag: v4.14.19
- 更新 fastgpt-pro(fastgpt 商业版) 镜像 tag: v4.14.19
## ⚙️ 优化
1. 兼容更低版本内核的浏览器。
## 🐛 修复
1. 表单输入,文件类型时,未过滤掉 icon,导致请求体过大。
2. 分享链接,未正确展示虚拟机文件入口。
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
"title": "4.14.x", "title": "4.14.x",
"description": "", "description": "",
"pages": [ "pages": [
"41419",
"41418", "41418",
"41417", "41417",
"41416", "41416",
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
"title": "4.14.x", "title": "4.14.x",
"description": "", "description": "",
"pages": [ "pages": [
"41419",
"41418", "41418",
"41417", "41417",
"41416", "41416",
......
...@@ -126,6 +126,7 @@ description: FastGPT Toc ...@@ -126,6 +126,7 @@ description: FastGPT Toc
- [/en/self-host/upgrading/4-14/41414](/en/self-host/upgrading/4-14/41414) - [/en/self-host/upgrading/4-14/41414](/en/self-host/upgrading/4-14/41414)
- [/en/self-host/upgrading/4-14/41415](/en/self-host/upgrading/4-14/41415) - [/en/self-host/upgrading/4-14/41415](/en/self-host/upgrading/4-14/41415)
- [/en/self-host/upgrading/4-14/41416](/en/self-host/upgrading/4-14/41416) - [/en/self-host/upgrading/4-14/41416](/en/self-host/upgrading/4-14/41416)
- [/en/self-host/upgrading/4-14/41419](/en/self-host/upgrading/4-14/41419)
- [/en/self-host/upgrading/4-14/4142](/en/self-host/upgrading/4-14/4142) - [/en/self-host/upgrading/4-14/4142](/en/self-host/upgrading/4-14/4142)
- [/en/self-host/upgrading/4-14/4143](/en/self-host/upgrading/4-14/4143) - [/en/self-host/upgrading/4-14/4143](/en/self-host/upgrading/4-14/4143)
- [/en/self-host/upgrading/4-14/4144](/en/self-host/upgrading/4-14/4144) - [/en/self-host/upgrading/4-14/4144](/en/self-host/upgrading/4-14/4144)
......
...@@ -128,6 +128,7 @@ description: FastGPT 文档目录 ...@@ -128,6 +128,7 @@ description: FastGPT 文档目录
- [/self-host/upgrading/4-14/41416](/self-host/upgrading/4-14/41416) - [/self-host/upgrading/4-14/41416](/self-host/upgrading/4-14/41416)
- [/self-host/upgrading/4-14/41417](/self-host/upgrading/4-14/41417) - [/self-host/upgrading/4-14/41417](/self-host/upgrading/4-14/41417)
- [/self-host/upgrading/4-14/41418](/self-host/upgrading/4-14/41418) - [/self-host/upgrading/4-14/41418](/self-host/upgrading/4-14/41418)
- [/self-host/upgrading/4-14/41419](/self-host/upgrading/4-14/41419)
- [/self-host/upgrading/4-14/4142](/self-host/upgrading/4-14/4142) - [/self-host/upgrading/4-14/4142](/self-host/upgrading/4-14/4142)
- [/self-host/upgrading/4-14/4143](/self-host/upgrading/4-14/4143) - [/self-host/upgrading/4-14/4143](/self-host/upgrading/4-14/4143)
- [/self-host/upgrading/4-14/4144](/self-host/upgrading/4-14/4144) - [/self-host/upgrading/4-14/4144](/self-host/upgrading/4-14/4144)
......
...@@ -269,7 +269,7 @@ ...@@ -269,7 +269,7 @@
"content/self-host/upgrading/4-14/41481.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/4-14/41481.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4149.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/4-14/4149.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4149.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/4-14/4149.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-15/4150.mdx": "2026-05-09T11:28:39+08:00", "content/self-host/upgrading/4-15/4150.mdx": "2026-05-09T14:27:39+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
......
...@@ -98,7 +98,7 @@ ${stepText}`; ...@@ -98,7 +98,7 @@ ${stepText}`;
// Concat 2 -> 1, and sort by role // Concat 2 -> 1, and sort by role
export const concatHistories = (histories1: ChatItemMiniType[], histories2: ChatItemMiniType[]) => { export const concatHistories = (histories1: ChatItemMiniType[], histories2: ChatItemMiniType[]) => {
const newHistories = [...histories1, ...histories2]; const newHistories = [...histories1, ...histories2];
return newHistories.sort((a, b) => { return newHistories.sort((a) => {
if (a.obj === ChatRoleEnum.System) { if (a.obj === ChatRoleEnum.System) {
return -1; return -1;
} }
...@@ -110,7 +110,6 @@ export const getChatTitleFromChatMessage = ( ...@@ -110,7 +110,6 @@ export const getChatTitleFromChatMessage = (
message?: ChatItemMiniType, message?: ChatItemMiniType,
defaultValue = '新对话' defaultValue = '新对话'
) => { ) => {
// @ts-ignore
const textMsg = message?.value.find((item) => 'text' in item && item.text); const textMsg = message?.value.find((item) => 'text' in item && item.text);
if (textMsg?.text?.content) { if (textMsg?.text?.content) {
...@@ -185,32 +184,32 @@ export const filterPublicNodeResponseData = ({ ...@@ -185,32 +184,32 @@ export const filterPublicNodeResponseData = ({
[FlowNodeTypeEnum.agent]: true, [FlowNodeTypeEnum.agent]: true,
[FlowNodeTypeEnum.pluginOutput]: true, [FlowNodeTypeEnum.pluginOutput]: true,
[FlowNodeTypeEnum.runApp]: true, [FlowNodeTypeEnum.runApp]: true,
[FlowNodeTypeEnum.toolCall]: true [FlowNodeTypeEnum.toolCall]: true,
[FlowNodeTypeEnum.tool]: true
}; };
const commonFields = {
moduleType: true,
pluginOutput: true,
runningTime: true,
toolId: true
};
const filedMap: Record<string, boolean> = responseDetail const filedMap: Record<string, boolean> = responseDetail
? { ? {
quoteList: true, quoteList: true,
moduleType: true, ...commonFields
pluginOutput: true,
runningTime: true
} }
: { : commonFields;
moduleType: true,
pluginOutput: true,
runningTime: true
};
return nodeRespones return nodeRespones
.filter((item) => publicNodeMap[item.moduleType]) .filter((item) => publicNodeMap[item.moduleType])
.map((item) => { .map((item) => {
const obj: DispatchNodeResponseType = {}; const obj: DispatchNodeResponseType = {};
for (let key in item) { for (const key in item) {
if (key === 'toolDetail' || key === 'pluginDetail') { if (key === 'toolDetail' || key === 'pluginDetail') {
// @ts-ignore
obj[key] = filterPublicNodeResponseData({ nodeRespones: item[key], responseDetail }); obj[key] = filterPublicNodeResponseData({ nodeRespones: item[key], responseDetail });
} else if (filedMap[key]) { } else if (filedMap[key]) {
// @ts-ignore // @ts-expect-error Dynamic public field copy is constrained by filedMap.
obj[key] = item[key]; obj[key] = item[key];
} }
} }
......
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import type { ChatItemMiniType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type'; import type { ChatItemMiniType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { SANDBOX_TOOL_NAME } from '@fastgpt/global/core/ai/sandbox/constants';
import { import {
concatHistories, concatHistories,
getChatTitleFromChatMessage, getChatTitleFromChatMessage,
...@@ -180,6 +181,33 @@ describe('filterPublicNodeResponseData', () => { ...@@ -180,6 +181,33 @@ describe('filterPublicNodeResponseData', () => {
expect(result[0].quoteList).toBeDefined(); expect(result[0].quoteList).toBeDefined();
}); });
it('should keep tool node type and toolId without exposing tool details', () => {
const nodeResponses: ChatHistoryItemResType[] = [
{
id: '1',
nodeId: 'node1',
moduleName: 'Sandbox',
moduleType: FlowNodeTypeEnum.tool,
runningTime: 0.8,
toolId: SANDBOX_TOOL_NAME,
toolInput: {
command: 'ls'
},
toolRes: 'file.txt'
}
];
const result = filterPublicNodeResponseData({ nodeRespones: nodeResponses });
expect(result).toEqual([
{
moduleType: FlowNodeTypeEnum.tool,
runningTime: 0.8,
toolId: SANDBOX_TOOL_NAME
}
]);
});
}); });
describe('removeEmptyUserInput', () => { describe('removeEmptyUserInput', () => {
......
...@@ -149,6 +149,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -149,6 +149,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
} }
// 注入文件到沙盒里 // 注入文件到沙盒里
if (currentInputFiles.length > 0) {
await injectSandboxFiles({ await injectSandboxFiles({
appId: workflowProps.runningAppInfo.id, appId: workflowProps.runningAppInfo.id,
userId: workflowProps.uid, userId: workflowProps.uid,
...@@ -159,6 +160,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -159,6 +160,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
})) }))
}); });
} }
}
const getToolInfo = (name: string) => { const getToolInfo = (name: string) => {
if (name === ReadFileTooData.id) { if (name === ReadFileTooData.id) {
...@@ -317,7 +319,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo ...@@ -317,7 +319,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
} else if (toolInfo.type === 'file') { } else if (toolInfo.type === 'file') {
const { ids } = ReadFileToolParamsSchema.parse(parseJsonArgs(call.function.arguments)); const { ids } = ReadFileToolParamsSchema.parse(parseJsonArgs(call.function.arguments));
const { response, usages, flowResponse } = await dispatchReadFileTool({ const { response, usages, flowResponse } = await dispatchReadFileTool({
files: ids.map((id) => ({ id, url: allFiles.get(id)?.url! })), files: ids.map((id) => ({ id, url: allFiles.get(id)?.url ?? '' })),
toolCallId: call.id, toolCallId: call.id,
teamId: workflowProps.runningUserInfo.teamId, teamId: workflowProps.runningUserInfo.teamId,
tmbId: workflowProps.runningUserInfo.tmbId, tmbId: workflowProps.runningUserInfo.tmbId,
......
Subproject commit 26ee89803dc551ad77bd34ac8fdfd6103c1b626c Subproject commit f1062d7ebf01ddb991f0c2dfead9f6d581eaed65
...@@ -88,10 +88,8 @@ const ResponseTags = ({ ...@@ -88,10 +88,8 @@ const ResponseTags = ({
outLinkAuthData outLinkAuthData
}); });
useSize(quoteListRef); const quoteListSize = useSize(quoteListRef);
const quoteIsOverflow = quoteListRef.current const quoteIsOverflow = quoteListSize ? quoteListSize.height >= (isPc ? 50 : 55) : true;
? quoteListRef.current.scrollHeight > (isPc ? 50 : 55)
: true;
const citationRenderList: CitationRenderItem[] = useMemo(() => { const citationRenderList: CitationRenderItem[] = useMemo(() => {
if (!isShowCite) return []; if (!isShowCite) return [];
...@@ -136,7 +134,8 @@ const ResponseTags = ({ ...@@ -136,7 +134,8 @@ const ResponseTags = ({
return [...datasetItems, ...linkItems]; return [...datasetItems, ...linkItems];
}, [quoteList, toolCiteLinks, onOpenCiteModal, isShowCite]); }, [quoteList, toolCiteLinks, onOpenCiteModal, isShowCite]);
const notEmptyTags = notSharePage || quoteList.length > 0 || (isPc && durationSeconds > 0); const notEmptyTags =
notSharePage || quoteList.length > 0 || useAgentSandbox || (isPc && durationSeconds > 0);
return !showTags ? null : ( return !showTags ? null : (
<> <>
......
import { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import SandboxEditorModal from '@/pageComponents/chat/SandboxEditor/modal'; import SandboxEditorModal from '@/pageComponents/chat/SandboxEditor/modal';
import type { IconButtonProps } from '@chakra-ui/react'; import type { IconButtonProps } from '@chakra-ui/react';
import { IconButton } from '@chakra-ui/react'; import { IconButton } from '@chakra-ui/react';
...@@ -62,8 +62,8 @@ export const useSandboxEditor = ({ ...@@ -62,8 +62,8 @@ export const useSandboxEditor = ({
* 职责:负责 checkSandboxExist 的网络同步及 SandboxEntryIcon 的显示控制。 * 职责:负责 checkSandboxExist 的网络同步及 SandboxEntryIcon 的显示控制。
* 同步模式: * 同步模式:
* 1. 历史记录(ChatRecordContext):useMemo 派生,无副作用。 * 1. 历史记录(ChatRecordContext):useMemo 派生,无副作用。
* 2. chatId 切换:渲染周期利用 useRef 确认 ID 变化并同步重置状态,防止 UI 闪烁 * 2. 网络请求:单一 useEffect,在参数变化时触发 1 次
* 3. 网络请求:单一 useEffect,在参数变化时触发 1 次 * 3. API 结果已返回时以 API 为准;未返回前才使用历史记录兜底
*/ */
export const useSandboxStatus = ({ export const useSandboxStatus = ({
appId, appId,
...@@ -75,13 +75,11 @@ export const useSandboxStatus = ({ ...@@ -75,13 +75,11 @@ export const useSandboxStatus = ({
outLinkAuthData?: OutLinkChatAuthProps; outLinkAuthData?: OutLinkChatAuthProps;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [apiSandboxExists, setApiSandboxExists] = useState(false); const [apiSandboxStatus, setApiSandboxStatus] = useState({
const lastChatIdRef = useRef(chatId); appId: '',
chatId: '',
if (lastChatIdRef.current !== chatId) { exists: false
lastChatIdRef.current = chatId; });
setApiSandboxExists(false);
}
const chatRecords = useContextSelector(ChatRecordContext, (v) => { const chatRecords = useContextSelector(ChatRecordContext, (v) => {
return v.chatRecords; return v.chatRecords;
...@@ -97,11 +95,11 @@ export const useSandboxStatus = ({ ...@@ -97,11 +95,11 @@ export const useSandboxStatus = ({
}, [chatRecords, isChatRecordsLoaded]); }, [chatRecords, isChatRecordsLoaded]);
useEffect(() => { useEffect(() => {
if (!chatId) return; if (!appId || !chatId) return;
let cancelled = false; let cancelled = false;
checkSandboxExist({ appId, chatId, outLinkAuthData }) checkSandboxExist({ appId, chatId, outLinkAuthData })
.then((result) => { .then((result) => {
if (!cancelled) setApiSandboxExists(result.exists); if (!cancelled) setApiSandboxStatus({ appId, chatId, exists: result.exists });
}) })
.catch((error) => { .catch((error) => {
console.error('Failed to check sandbox status:', error); console.error('Failed to check sandbox status:', error);
...@@ -109,10 +107,21 @@ export const useSandboxStatus = ({ ...@@ -109,10 +107,21 @@ export const useSandboxStatus = ({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [appId, chatId]); }, [appId, chatId, outLinkAuthData]);
const apiSandboxExists =
apiSandboxStatus.appId === appId &&
apiSandboxStatus.chatId === chatId &&
apiSandboxStatus.exists;
const sandboxExists = hasSandboxInHistory || apiSandboxExists; const sandboxExists = hasSandboxInHistory || apiSandboxExists;
const setSandboxExists = useCallback(
(exists: boolean) => {
setApiSandboxStatus({ appId, chatId, exists });
},
[appId, chatId]
);
const SandboxEntryIcon = useCallback( const SandboxEntryIcon = useCallback(
({ ({
onOpen, onOpen,
...@@ -138,7 +147,7 @@ export const useSandboxStatus = ({ ...@@ -138,7 +147,7 @@ export const useSandboxStatus = ({
return { return {
sandboxExists, sandboxExists,
setSandboxExists: setApiSandboxExists, setSandboxExists,
SandboxEntryIcon SandboxEntryIcon
}; };
}; };
...@@ -25,18 +25,21 @@ const ToolMenu = ({ ...@@ -25,18 +25,21 @@ const ToolMenu = ({
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId); const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
const { chatId, outLinkAuthData } = useChatStore(); const { chatId, outLinkAuthData, appId, source } = useChatStore();
const currentAppId = chatData.appId || appId;
const isShareAuthReady =
source !== 'share' || (!!outLinkAuthData.shareId && !!outLinkAuthData.outLinkUid);
// Status Hook: 顶层单例,负责网络同步与入口图标显示 // Status Hook: 顶层单例,负责网络同步与入口图标显示
const { sandboxExists, setSandboxExists, SandboxEntryIcon } = useSandboxStatus({ const { sandboxExists, setSandboxExists, SandboxEntryIcon } = useSandboxStatus({
appId: chatData.appId, appId: isShareAuthReady ? currentAppId : '',
chatId, chatId,
outLinkAuthData outLinkAuthData
}); });
// UI Hook: 负责弹窗渲染 // UI Hook: 负责弹窗渲染
const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({ const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({
appId: chatData.appId, appId: currentAppId,
chatId, chatId,
outLinkAuthData outLinkAuthData
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment