diff --git a/packages/global/core/chat/type.ts b/packages/global/core/chat/type.ts
index 64dec5cf8238..6388c1489445 100644
--- a/packages/global/core/chat/type.ts
+++ b/packages/global/core/chat/type.ts
@@ -169,7 +169,7 @@ export type AIChatItemType = {
citeCollectionIds?: string[];
/**
- * @deprecated 不再存储在 chatItemSchema 里,分别存储到 chatItemResponseSchema
+ * 不再存储在 chatItemSchema 里,分别存储到 chatItemResponseSchema
*/
[DispatchNodeResponseKeyEnum.nodeResponse]?: ChatHistoryItemResType[];
};
diff --git a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx
index 88abb461fe43..77bcfeda6251 100644
--- a/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx
+++ b/projects/app/src/pageComponents/app/detail/Edit/ChatAgent/ChatTest.tsx
@@ -24,7 +24,7 @@ import type { HelperBotRefType } from '@/components/core/chat/HelperBot/context'
import { HelperBotTypeEnum } from '@fastgpt/global/core/chat/helperBot/type';
import { loadGeneratedTools } from './utils';
import { systemSubInfo } from '@fastgpt/global/core/workflow/node/agent/constants';
-import { useSandboxEditor } from '@/pageComponents/chat/SandboxEditor/hook';
+import { useSandboxEditor, useSandboxStatus } from '@/pageComponents/chat/SandboxEditor/hook';
type Props = {
appForm: AppFormEditFormType;
@@ -50,8 +50,12 @@ const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props
edges: appDetail.edges || []
});
- // Sandbox state
- const { SandboxEditorModal, SandboxEntryIcon } = useSandboxEditor({
+ // Sandbox: Status Hook 负责网络同步,UI Hook 负责弹窗渲染
+ const { SandboxEntryIcon } = useSandboxStatus({
+ appId: appDetail._id,
+ chatId
+ });
+ const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({
appId: appDetail._id,
chatId
});
@@ -126,7 +130,7 @@ const ChatTest = ({ appForm, setAppForm, setRenderEdit, form2WorkflowFn }: Props
)}
-
+
{
edges: appDetail.edges || []
});
- // Sandbox state
- const { SandboxEditorModal, SandboxEntryIcon, setSandboxExists } = useSandboxEditor({
+ // Sandbox: Status Hook 负责网络同步,UI Hook 负责弹窗渲染
+ const { SandboxEntryIcon, setSandboxExists } = useSandboxStatus({
+ appId: appDetail._id,
+ chatId
+ });
+ const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({
appId: appDetail._id,
chatId
});
@@ -81,7 +85,7 @@ const ChatTest = ({ appForm, setRenderEdit, form2WorkflowFn }: Props) => {
{!isVariableVisible && }
-
+
import('@/components/core/chat/ChatContainer/PluginRunBox'));
@@ -87,11 +87,9 @@ const DetailLogsModal = ({
const chatModels = chat?.app?.chatModels;
const isPlugin = chat?.app.type === AppTypeEnum.workflowTool;
- // Sandbox state
- const { SandboxEditorModal, SandboxEntryIcon } = useSandboxEditor({
- appId,
- chatId
- });
+ // Sandbox: Status Hook 负责网络同步,UI Hook 负责弹窗渲染
+ const { SandboxEntryIcon } = useSandboxStatus({ appId, chatId });
+ const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({ appId, chatId });
return (
<>
@@ -176,7 +174,7 @@ const DetailLogsModal = ({
>
)}
-
+
const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
- // Sandbox state
- const { SandboxEditorModal, SandboxEntryIcon } = useSandboxEditor({
+ // Sandbox: Status Hook 负责网络同步,UI Hook 负责弹窗渲染
+ const { SandboxEntryIcon } = useSandboxStatus({
+ appId: appDetail._id,
+ chatId
+ });
+ const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({
appId: appDetail._id,
chatId
});
@@ -143,7 +147,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose, chatId }: Props) =>
{!isVariableVisible && }
-
+
void;
}) => {
- const { t } = useTranslation();
- // Sandbox state
const [sandboxModalOpen, setSandboxModalOpen] = useState(false);
- const [sandboxExists, setSandboxExists] = useState(false);
-
- // 检查沙盒是否存在
- const checkSandboxStatus = useCallback(async () => {
- try {
- const result = await checkSandboxExist({ appId, chatId, outLinkAuthData });
- setSandboxExists(result.exists);
- } catch (error) {
- console.error('Failed to check sandbox status:', error);
- }
- }, [appId, chatId, outLinkAuthData]);
-
- // 组件挂载时检查
- useInterval(checkSandboxStatus, 10000, {
- immediate: true
- });
const onOpenSandboxModal = useCallback(() => {
setSandboxModalOpen(true);
@@ -44,11 +35,10 @@ export const useSandboxEditor = ({
const onCloseSandboxModal = useCallback(() => {
setSandboxModalOpen(false);
- // 关闭后重新检查状态
- checkSandboxStatus();
- }, [checkSandboxStatus]);
+ afterClose?.();
+ }, [afterClose]);
- const Dom = useCallback(() => {
+ const SandboxEditorModalDom = useCallback(() => {
return sandboxModalOpen ? (
{
+ const { t } = useTranslation();
+ const [apiSandboxExists, setApiSandboxExists] = useState(false);
+ const lastChatIdRef = useRef(chatId);
+
+ if (lastChatIdRef.current !== chatId) {
+ lastChatIdRef.current = chatId;
+ setApiSandboxExists(false);
+ }
+
+ const chatRecords = useContextSelector(ChatRecordContext, (v) => {
+ return v.chatRecords;
+ });
+ const isChatRecordsLoaded = useContextSelector(ChatRecordContext, (v) => v.isChatRecordsLoaded);
+
+ const hasSandboxInHistory = useMemo(() => {
+ if (!isChatRecordsLoaded) return false;
+ return chatRecords.some((record) => {
+ const enriched = addStatisticalDataToHistoryItem(record);
+ return enriched.useAgentSandbox === true;
+ });
+ }, [chatRecords, isChatRecordsLoaded]);
+
+ useEffect(() => {
+ if (!chatId) return;
+ let cancelled = false;
+ checkSandboxExist({ appId, chatId, outLinkAuthData })
+ .then((result) => {
+ if (!cancelled) setApiSandboxExists(result.exists);
+ })
+ .catch((error) => {
+ console.error('Failed to check sandbox status:', error);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [appId, chatId]);
+
+ const sandboxExists = hasSandboxInHistory || apiSandboxExists;
+
const SandboxEntryIcon = useCallback(
- (props: Omit) => {
- // 只有沙盒存在时才显示图标
+ ({
+ onOpen,
+ ...props
+ }: Omit & { onOpen: () => void }) => {
if (!sandboxExists) return null;
return (
@@ -70,23 +126,19 @@ export const useSandboxEditor = ({
variant={'whiteBase'}
size={'smSquare'}
icon={}
- onClick={onOpenSandboxModal}
+ onClick={onOpen}
{...props}
aria-label="Sandbox Entry"
/>
);
},
- [sandboxExists, t, onOpenSandboxModal]
+ [sandboxExists, t]
);
return {
sandboxExists,
- setSandboxExists,
- checkSandboxStatus,
- SandboxEntryIcon,
- SandboxEditorModal: Dom,
- onOpenSandboxModal,
- onCloseSandboxModal
+ setSandboxExists: setApiSandboxExists,
+ SandboxEntryIcon
};
};
diff --git a/projects/app/src/pageComponents/chat/ToolMenu.tsx b/projects/app/src/pageComponents/chat/ToolMenu.tsx
index f5885aad68b8..81ab9184891b 100644
--- a/projects/app/src/pageComponents/chat/ToolMenu.tsx
+++ b/projects/app/src/pageComponents/chat/ToolMenu.tsx
@@ -8,7 +8,7 @@ import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
-import { useSandboxEditor } from './SandboxEditor/hook';
+import { useSandboxEditor, useSandboxStatus } from './SandboxEditor/hook';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
@@ -25,16 +25,17 @@ const ToolMenu = ({
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
- const { chatId, appId, setChatId, outLinkAuthData } = useChatStore();
+ const { chatId, outLinkAuthData } = useChatStore();
- // Sandbox state
- const {
- SandboxEditorModal,
- SandboxEntryIcon,
- setSandboxExists,
- sandboxExists,
- onOpenSandboxModal
- } = useSandboxEditor({
+ // Status Hook: 顶层单例,负责网络同步与入口图标显示
+ const { sandboxExists, setSandboxExists, SandboxEntryIcon } = useSandboxStatus({
+ appId: chatData.appId,
+ chatId,
+ outLinkAuthData
+ });
+
+ // UI Hook: 负责弹窗渲染
+ const { SandboxEditorModal, onOpenSandboxModal } = useSandboxEditor({
appId: chatData.appId,
chatId,
outLinkAuthData
@@ -42,7 +43,7 @@ const ToolMenu = ({
return (
<>
- {isPc && }
+ {isPc && }