Commit 0aebb734 by Archer Committed by GitHub

fix: share field filter (#6905)

* fix: share field filter

* perf: review
parent f4e09007
{
"recommendations": [
"dbaeumer.vscode-eslint",
"alexcvzz.vscode-sqlite"
]
}
\ No newline at end of file
}
......@@ -2,7 +2,22 @@
"editor.formatOnSave": true,
"editor.mouseWheelZoom": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"prettier.prettierPath": "node_modules/prettier",
"eslint.useFlatConfig": true,
"eslint.workingDirectories": [
{
"mode": "auto"
}
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"i18n-ally.localesPaths": [
"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 @@
"title": "4.14.x",
"description": "",
"pages": [
"41419",
"41418",
"41417",
"41416",
......
......@@ -2,6 +2,7 @@
"title": "4.14.x",
"description": "",
"pages": [
"41419",
"41418",
"41417",
"41416",
......
......@@ -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/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/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/4143](/en/self-host/upgrading/4-14/4143)
- [/en/self-host/upgrading/4-14/4144](/en/self-host/upgrading/4-14/4144)
......
......@@ -128,6 +128,7 @@ description: FastGPT 文档目录
- [/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/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/4143](/self-host/upgrading/4-14/4143)
- [/self-host/upgrading/4-14/4144](/self-host/upgrading/4-14/4144)
......
......@@ -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/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-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.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}`;
// Concat 2 -> 1, and sort by role
export const concatHistories = (histories1: ChatItemMiniType[], histories2: ChatItemMiniType[]) => {
const newHistories = [...histories1, ...histories2];
return newHistories.sort((a, b) => {
return newHistories.sort((a) => {
if (a.obj === ChatRoleEnum.System) {
return -1;
}
......@@ -110,7 +110,6 @@ export const getChatTitleFromChatMessage = (
message?: ChatItemMiniType,
defaultValue = '新对话'
) => {
// @ts-ignore
const textMsg = message?.value.find((item) => 'text' in item && item.text);
if (textMsg?.text?.content) {
......@@ -185,32 +184,32 @@ export const filterPublicNodeResponseData = ({
[FlowNodeTypeEnum.agent]: true,
[FlowNodeTypeEnum.pluginOutput]: 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
? {
quoteList: true,
moduleType: true,
pluginOutput: true,
runningTime: true
...commonFields
}
: {
moduleType: true,
pluginOutput: true,
runningTime: true
};
: commonFields;
return nodeRespones
.filter((item) => publicNodeMap[item.moduleType])
.map((item) => {
const obj: DispatchNodeResponseType = {};
for (let key in item) {
for (const key in item) {
if (key === 'toolDetail' || key === 'pluginDetail') {
// @ts-ignore
obj[key] = filterPublicNodeResponseData({ nodeRespones: item[key], responseDetail });
} else if (filedMap[key]) {
// @ts-ignore
// @ts-expect-error Dynamic public field copy is constrained by filedMap.
obj[key] = item[key];
}
}
......
......@@ -7,6 +7,7 @@ import {
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import type { ChatItemMiniType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { SANDBOX_TOOL_NAME } from '@fastgpt/global/core/ai/sandbox/constants';
import {
concatHistories,
getChatTitleFromChatMessage,
......@@ -180,6 +181,33 @@ describe('filterPublicNodeResponseData', () => {
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', () => {
......
......@@ -149,15 +149,17 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
}
// 注入文件到沙盒里
await injectSandboxFiles({
appId: workflowProps.runningAppInfo.id,
userId: workflowProps.uid,
chatId: workflowProps.chatId,
files: currentInputFiles.map((file) => ({
path: file.sandboxPath!,
url: file.url
}))
});
if (currentInputFiles.length > 0) {
await injectSandboxFiles({
appId: workflowProps.runningAppInfo.id,
userId: workflowProps.uid,
chatId: workflowProps.chatId,
files: currentInputFiles.map((file) => ({
path: file.sandboxPath!,
url: file.url
}))
});
}
}
const getToolInfo = (name: string) => {
......@@ -317,7 +319,7 @@ export const runToolCall = async (props: DispatchToolModuleProps): Promise<Respo
} else if (toolInfo.type === 'file') {
const { ids } = ReadFileToolParamsSchema.parse(parseJsonArgs(call.function.arguments));
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,
teamId: workflowProps.runningUserInfo.teamId,
tmbId: workflowProps.runningUserInfo.tmbId,
......
Subproject commit 26ee89803dc551ad77bd34ac8fdfd6103c1b626c
Subproject commit f1062d7ebf01ddb991f0c2dfead9f6d581eaed65
......@@ -88,10 +88,8 @@ const ResponseTags = ({
outLinkAuthData
});
useSize(quoteListRef);
const quoteIsOverflow = quoteListRef.current
? quoteListRef.current.scrollHeight > (isPc ? 50 : 55)
: true;
const quoteListSize = useSize(quoteListRef);
const quoteIsOverflow = quoteListSize ? quoteListSize.height >= (isPc ? 50 : 55) : true;
const citationRenderList: CitationRenderItem[] = useMemo(() => {
if (!isShowCite) return [];
......@@ -136,7 +134,8 @@ const ResponseTags = ({
return [...datasetItems, ...linkItems];
}, [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 : (
<>
......
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import SandboxEditorModal from '@/pageComponents/chat/SandboxEditor/modal';
import type { IconButtonProps } from '@chakra-ui/react';
import { IconButton } from '@chakra-ui/react';
......@@ -62,8 +62,8 @@ export const useSandboxEditor = ({
* 职责:负责 checkSandboxExist 的网络同步及 SandboxEntryIcon 的显示控制。
* 同步模式:
* 1. 历史记录(ChatRecordContext):useMemo 派生,无副作用。
* 2. chatId 切换:渲染周期利用 useRef 确认 ID 变化并同步重置状态,防止 UI 闪烁
* 3. 网络请求:单一 useEffect,在参数变化时触发 1 次
* 2. 网络请求:单一 useEffect,在参数变化时触发 1 次
* 3. API 结果已返回时以 API 为准;未返回前才使用历史记录兜底
*/
export const useSandboxStatus = ({
appId,
......@@ -75,13 +75,11 @@ export const useSandboxStatus = ({
outLinkAuthData?: OutLinkChatAuthProps;
}) => {
const { t } = useTranslation();
const [apiSandboxExists, setApiSandboxExists] = useState(false);
const lastChatIdRef = useRef(chatId);
if (lastChatIdRef.current !== chatId) {
lastChatIdRef.current = chatId;
setApiSandboxExists(false);
}
const [apiSandboxStatus, setApiSandboxStatus] = useState({
appId: '',
chatId: '',
exists: false
});
const chatRecords = useContextSelector(ChatRecordContext, (v) => {
return v.chatRecords;
......@@ -97,11 +95,11 @@ export const useSandboxStatus = ({
}, [chatRecords, isChatRecordsLoaded]);
useEffect(() => {
if (!chatId) return;
if (!appId || !chatId) return;
let cancelled = false;
checkSandboxExist({ appId, chatId, outLinkAuthData })
.then((result) => {
if (!cancelled) setApiSandboxExists(result.exists);
if (!cancelled) setApiSandboxStatus({ appId, chatId, exists: result.exists });
})
.catch((error) => {
console.error('Failed to check sandbox status:', error);
......@@ -109,10 +107,21 @@ export const useSandboxStatus = ({
return () => {
cancelled = true;
};
}, [appId, chatId]);
}, [appId, chatId, outLinkAuthData]);
const apiSandboxExists =
apiSandboxStatus.appId === appId &&
apiSandboxStatus.chatId === chatId &&
apiSandboxStatus.exists;
const sandboxExists = hasSandboxInHistory || apiSandboxExists;
const setSandboxExists = useCallback(
(exists: boolean) => {
setApiSandboxStatus({ appId, chatId, exists });
},
[appId, chatId]
);
const SandboxEntryIcon = useCallback(
({
onOpen,
......@@ -138,7 +147,7 @@ export const useSandboxStatus = ({
return {
sandboxExists,
setSandboxExists: setApiSandboxExists,
setSandboxExists,
SandboxEntryIcon
};
};
......@@ -25,18 +25,21 @@ const ToolMenu = ({
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
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: 顶层单例,负责网络同步与入口图标显示
const { sandboxExists, setSandboxExists, SandboxEntryIcon } = useSandboxStatus({
appId: chatData.appId,
appId: isShareAuthReady ? currentAppId : '',
chatId,
outLinkAuthData
});
// UI Hook: 负责弹窗渲染
const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({
appId: chatData.appId,
appId: currentAppId,
chatId,
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