V4.14.9 features (#6602)

* fix: image read and json error (Agent) (#6502)

* fix:
1.image read
2.JSON parsing error

* dataset cite and pause

* perf: plancall second parse

* add test

---------

Co-authored-by: archer <545436317@qq.com>

* master message

* remove invalid code

* fix: sandbox download file

* update lock

* sub set

* i18n

* perf: system forbid sandbox

* fix: i18n; next config

* fix: authchat uid

* update i18n

* perf: check exists

* stop in tool

* stop in tool

* fix: chat

* update action

* doc

* deploy doc

---------

Co-authored-by: YeYuheng <57035043+YYH211@users.noreply.github.com>
This commit is contained in:
Archer
2026-03-22 17:58:45 +08:00
committed by GitHub
parent 05bb197990
commit f7b64f25b1
52 changed files with 1989 additions and 669 deletions
@@ -4,7 +4,6 @@ import { Controller, useForm, type UseFormHandleSubmit } from 'react-hook-form';
import Markdown from '@/components/Markdown';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import type {
AgentPlanCheckInteractive,
UserInputInteractive,
UserSelectInteractive,
UserSelectOptionItemType
@@ -526,8 +526,8 @@ const InputTypeConfig = ({
valueType === WorkflowIOValueTypeEnum.number)) && (
<MyNumberInput
value={defaultValue}
min={min}
max={max}
min={min ? min : undefined}
max={max ? max : undefined}
onChange={(e) => {
// @ts-ignore
setValue('defaultValue', e ?? '');
@@ -147,16 +147,8 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
async () => {
if (!activeFile) return;
// 直接下载文件内容,不压缩
const blob = new Blob([activeFile.content], { type: 'text/plain;charset=utf-8' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = activeFile.name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
// 通过服务端下载接口获取原始文件,支持二进制文件(图片等)
await downloadSandbox({ appId, chatId, outLinkAuthData, path: activeFile.path });
},
{ manual: true }
);
@@ -470,7 +462,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
<MyIcon name="common/searchLight" w="16px" color="myGray.500" />
</InputLeftElement>
<Input
placeholder={t('app:sandbox.search_files')}
placeholder={t('chat:sandbox_search_files')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
bg="white"
@@ -725,7 +717,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
) : filteredTree.length > 0 ? (
<Center h="full">
<VStack spacing={3}>
<EmptyTip text={t('app:sandbox.select_file')} mt={0} />
<EmptyTip text={t('chat:sandbox_select_file_edit')} mt={0} />
</VStack>
</Center>
) : null}
@@ -734,7 +726,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
) : !loadingRoot ? (
<Center h="full" w={'full'}>
<VStack spacing={3}>
<EmptyTip text={t('app:sandbox.no_file')} mt={0} />
<EmptyTip text={t('chat:sandbox_no_file')} mt={0} />
</VStack>
</Center>
) : null}
@@ -23,7 +23,7 @@ async function handler(
const { appId, chatId, outLinkAuthData } = body;
// 统一鉴权
await authChatCrud({
const { uid } = await authChatCrud({
req,
authToken: true,
authApiKey: true,
@@ -33,10 +33,14 @@ async function handler(
});
// 检查沙盒是否存在
const sandboxInstance = await MongoSandboxInstance.findOne({
appId,
chatId
}).lean();
const sandboxInstance = await MongoSandboxInstance.findOne(
{
appId,
userId: uid,
chatId
},
'_id'
).lean();
return {
exists: !!sandboxInstance
@@ -37,9 +37,10 @@ async function handler(req: ApiRequestProps, res: NextApiResponse): Promise<void
await sandbox.ensureAvailable();
// 检查路径类型
const entries = await sandbox.provider.listDirectory(path);
const isDirectory = entries.length > 0 || path.endsWith('/');
// 通过 getFileInfo 准确判断路径是文件还是目录
const fileInfoMap = await sandbox.provider.getFileInfo([path]);
const fileInfo = fileInfoMap.get(path);
const isDirectory = fileInfo?.isDirectory ?? path.endsWith('/');
if (isDirectory) {
// 下载目录为 ZIP
@@ -55,8 +55,8 @@ export async function authChatCrud({
chatId?: string;
}): Promise<{
teamId: string;
tmbId: string;
uid: string;
tmbId: string; // 本轮鉴权的 uid
uid: string; // chat 里的实际的 uidoutlinkUid??tmbId)
chat?: ChatSchemaType;
showCite: boolean;
showRunningStatus: boolean;
@@ -68,7 +68,7 @@ export async function authChatCrud({
if (spaceTeamId && teamToken) {
const { uid, tmbId } = await authTeamSpaceToken({ teamId: spaceTeamId, teamToken });
if (!chatId)
if (!chatId) {
return {
teamId: spaceTeamId,
tmbId,
@@ -76,6 +76,7 @@ export async function authChatCrud({
...defaultResponseShow,
authType: AuthUserTypeEnum.teamDomain
};
}
const chat = await MongoChat.findOne({ appId, chatId }).lean();
if (!chat) {
@@ -188,7 +189,7 @@ export async function authChatCrud({
teamId,
tmbId,
chat,
uid: tmbId,
uid: chat.outLinkUid ?? chat.tmbId,
...defaultResponseShow,
authType
};
@@ -199,7 +200,7 @@ export async function authChatCrud({
teamId,
tmbId,
chat,
uid: tmbId,
uid: chat.outLinkUid ?? chat.tmbId,
...defaultResponseShow,
authType
};