fix: prevent agent workspace loading deadlock
This commit is contained in:
parent
bd98282bc5
commit
54c6b4a74e
@ -60,6 +60,7 @@ import dynamic from "next/dynamic";
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
type ChangeEvent,
|
||||
type ComponentType,
|
||||
startTransition,
|
||||
useCallback,
|
||||
useDeferredValue,
|
||||
@ -71,6 +72,7 @@ import {
|
||||
import { toast } from "sonner";
|
||||
import useSWR from "swr";
|
||||
// 深色主题组件 V2 (新版布局)
|
||||
import type { AgentGenerationWorkspaceDarkV2Props } from "@/components/agents/AgentGenerationWorkspaceDarkV2";
|
||||
import { type AgentWorkflowInputs } from "@/components/agents/AgentWorkflowPanelNew";
|
||||
import { CanvasToolbarIcon } from "@/components/canvas/CanvasToolbarIcon";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@ -320,21 +322,153 @@ const AgentCanvasTopNavDark = dynamic(
|
||||
}
|
||||
);
|
||||
|
||||
const AgentGenerationWorkspaceDarkV2 = dynamic(
|
||||
() =>
|
||||
import("@/components/agents/AgentGenerationWorkspaceDarkV2").then(
|
||||
(module) => module.AgentGenerationWorkspaceDarkV2
|
||||
),
|
||||
{
|
||||
loading: () => (
|
||||
<CanvasLazyPanelLoader
|
||||
className="min-h-dvh bg-[#050607] text-white/65"
|
||||
label="正在加载风格化工作台..."
|
||||
/>
|
||||
),
|
||||
ssr: false,
|
||||
const AGENT_WORKSPACE_LOAD_TIMEOUT_MS = 15_000;
|
||||
|
||||
let cachedAgentGenerationWorkspace:
|
||||
| ComponentType<AgentGenerationWorkspaceDarkV2Props>
|
||||
| null = null;
|
||||
let agentGenerationWorkspaceLoadPromise:
|
||||
| Promise<ComponentType<AgentGenerationWorkspaceDarkV2Props>>
|
||||
| null = null;
|
||||
|
||||
function loadAgentGenerationWorkspaceDarkV2() {
|
||||
if (cachedAgentGenerationWorkspace) {
|
||||
return Promise.resolve(cachedAgentGenerationWorkspace);
|
||||
}
|
||||
);
|
||||
|
||||
if (!agentGenerationWorkspaceLoadPromise) {
|
||||
agentGenerationWorkspaceLoadPromise = import(
|
||||
"@/components/agents/AgentGenerationWorkspaceDarkV2"
|
||||
)
|
||||
.then((module) => {
|
||||
cachedAgentGenerationWorkspace = module.AgentGenerationWorkspaceDarkV2;
|
||||
return cachedAgentGenerationWorkspace;
|
||||
})
|
||||
.catch((error) => {
|
||||
agentGenerationWorkspaceLoadPromise = null;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
return agentGenerationWorkspaceLoadPromise;
|
||||
}
|
||||
|
||||
function resetAgentGenerationWorkspaceLoader() {
|
||||
cachedAgentGenerationWorkspace = null;
|
||||
agentGenerationWorkspaceLoadPromise = null;
|
||||
}
|
||||
|
||||
function AgentGenerationWorkspaceDarkV2(
|
||||
props: AgentGenerationWorkspaceDarkV2Props
|
||||
) {
|
||||
const [WorkspaceComponent, setWorkspaceComponent] = useState<
|
||||
ComponentType<AgentGenerationWorkspaceDarkV2Props> | null
|
||||
>(() => cachedAgentGenerationWorkspace);
|
||||
const [loadError, setLoadError] = useState<Error | null>(null);
|
||||
const [showRecovery, setShowRecovery] = useState(false);
|
||||
const [retryToken, setRetryToken] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (WorkspaceComponent) return;
|
||||
|
||||
let cancelled = false;
|
||||
setLoadError(null);
|
||||
setShowRecovery(false);
|
||||
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
if (!cancelled) {
|
||||
setShowRecovery(true);
|
||||
}
|
||||
}, AGENT_WORKSPACE_LOAD_TIMEOUT_MS);
|
||||
|
||||
loadAgentGenerationWorkspaceDarkV2()
|
||||
.then((component) => {
|
||||
if (cancelled) return;
|
||||
setWorkspaceComponent(() => component);
|
||||
setLoadError(null);
|
||||
setShowRecovery(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (cancelled) return;
|
||||
const normalizedError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
setLoadError(normalizedError);
|
||||
setShowRecovery(true);
|
||||
})
|
||||
.finally(() => {
|
||||
window.clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}, [WorkspaceComponent, retryToken]);
|
||||
|
||||
if (WorkspaceComponent) {
|
||||
return <WorkspaceComponent {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-dvh flex-col items-center justify-center gap-5 bg-[#050607] px-6 text-white/70">
|
||||
<CanvasLazyPanelLoader
|
||||
className="min-h-0 bg-transparent text-white/65"
|
||||
label={
|
||||
loadError
|
||||
? "风格化工作台加载失败"
|
||||
: showRecovery
|
||||
? "风格化工作台加载较慢"
|
||||
: "正在加载风格化工作台..."
|
||||
}
|
||||
/>
|
||||
{showRecovery ? (
|
||||
<div className="w-full max-w-[520px] rounded-[28px] border border-white/10 bg-white/[0.04] p-5 text-center shadow-[0_20px_70px_rgba(0,0,0,0.32)]">
|
||||
<p className="text-sm text-white/70 leading-6">
|
||||
工作台资源还没有完成加载,可能是浏览器缓存了旧版本资源或当前网络较慢。可以先重试加载,仍不恢复时刷新页面即可继续当前项目。
|
||||
</p>
|
||||
{loadError?.message ? (
|
||||
<p className="mt-3 truncate rounded-2xl bg-black/30 px-3 py-2 text-white/35 text-xs">
|
||||
{loadError.message}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="mt-5 flex flex-wrap items-center justify-center gap-3">
|
||||
<Button
|
||||
className="rounded-full bg-emerald-400 px-5 text-black hover:bg-emerald-300"
|
||||
onClick={() => {
|
||||
resetAgentGenerationWorkspaceLoader();
|
||||
setWorkspaceComponent(null);
|
||||
setLoadError(null);
|
||||
setShowRecovery(false);
|
||||
setRetryToken((value) => value + 1);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
重新加载工作台
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-full border-white/15 bg-white/[0.06] px-5 text-white hover:bg-white/[0.10]"
|
||||
onClick={() => window.location.reload()}
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
刷新页面
|
||||
</Button>
|
||||
{props.onOpenCanvasEditor ? (
|
||||
<Button
|
||||
className="rounded-full border-white/15 bg-transparent px-5 text-white/70 hover:bg-white/[0.08]"
|
||||
onClick={props.onOpenCanvasEditor}
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
返回画布
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const AgentProjectListSheet = dynamic(
|
||||
() =>
|
||||
@ -1909,6 +2043,14 @@ export default function CanvasEditorLegacyPage({ params }: PageProps) {
|
||||
[siteSettingsData?.canvasToolbarConfig]
|
||||
);
|
||||
const isAgentMode = isAgentWorkspaceEnabled && requestedAgentMode;
|
||||
useEffect(() => {
|
||||
if (!isAgentMode) return;
|
||||
|
||||
void loadAgentGenerationWorkspaceDarkV2().catch((error) => {
|
||||
console.warn("[Agent Workspace] preload failed:", error);
|
||||
});
|
||||
}, [isAgentMode]);
|
||||
|
||||
const hasLoadedCurrentAgentProject =
|
||||
!requestedAgentMode ||
|
||||
!agentProjectId ||
|
||||
|
||||
@ -91,32 +91,47 @@ const CHUNK_ERROR_RECOVERY_SCRIPT = `\
|
||||
}
|
||||
}
|
||||
|
||||
var hasReloaded = safeGetSessionStorage('chunk-reload-attempted');
|
||||
var RECOVERY_KEY = 'chunk-reload-attempted-v2';
|
||||
var lastReloadAt = Number(safeGetSessionStorage(RECOVERY_KEY) || '0') || 0;
|
||||
|
||||
function shouldAutoRecover(message) {
|
||||
if (!message) return false;
|
||||
var normalized = String(message).toLowerCase();
|
||||
return (
|
||||
message.includes('Failed to load chunk') ||
|
||||
message.includes('Loading chunk') ||
|
||||
message.includes('/_next/static/chunks/') ||
|
||||
message.includes('Failed to find Server Action') ||
|
||||
message.includes('Server Components render') ||
|
||||
message.includes('Application error: a client-side exception')
|
||||
normalized.indexOf('chunkloaderror') !== -1 ||
|
||||
normalized.indexOf('failed to load chunk') !== -1 ||
|
||||
normalized.indexOf('loading chunk') !== -1 ||
|
||||
normalized.indexOf('failed to fetch dynamically imported module') !== -1 ||
|
||||
normalized.indexOf('error loading dynamically imported module') !== -1 ||
|
||||
normalized.indexOf('importing a module script failed') !== -1 ||
|
||||
normalized.indexOf('module script load failed') !== -1 ||
|
||||
normalized.indexOf('/_next/static/chunks/') !== -1 ||
|
||||
normalized.indexOf('failed to find server action') !== -1 ||
|
||||
normalized.indexOf('server components render') !== -1 ||
|
||||
normalized.indexOf('application error: a client-side exception') !== -1
|
||||
);
|
||||
}
|
||||
|
||||
function triggerAutoRecover(reason) {
|
||||
if (hasReloaded) return;
|
||||
var now = Date.now();
|
||||
if (lastReloadAt && now - lastReloadAt < 30000) return;
|
||||
lastReloadAt = now;
|
||||
console.warn('[RuntimeRecover] 检测到前端运行时异常,自动刷新页面...', reason || '');
|
||||
safeSetSessionStorage('chunk-reload-attempted', 'true');
|
||||
safeSetSessionStorage(RECOVERY_KEY, String(now));
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 800);
|
||||
}
|
||||
|
||||
window.addEventListener('error', function(event) {
|
||||
if (shouldAutoRecover(event && event.message)) {
|
||||
triggerAutoRecover(event && event.message);
|
||||
var message = '';
|
||||
if (event && event.message) message += event.message;
|
||||
if (event && event.filename) message += ' ' + event.filename;
|
||||
if (event && event.error && event.error.message) {
|
||||
message += ' ' + event.error.message;
|
||||
}
|
||||
if (shouldAutoRecover(message)) {
|
||||
triggerAutoRecover(message);
|
||||
}
|
||||
}, true);
|
||||
|
||||
@ -130,11 +145,11 @@ const CHUNK_ERROR_RECOVERY_SCRIPT = `\
|
||||
}
|
||||
});
|
||||
|
||||
// 页面加载成功后清除标记
|
||||
// 页面稳定一段时间后清除标记,避免旧资源异常无限刷新。
|
||||
window.addEventListener('load', function() {
|
||||
setTimeout(function() {
|
||||
safeRemoveSessionStorage('chunk-reload-attempted');
|
||||
}, 2000);
|
||||
safeRemoveSessionStorage(RECOVERY_KEY);
|
||||
}, 120000);
|
||||
});
|
||||
})();`;
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ import {
|
||||
normalizeAgentGenerationProjectPersistedState,
|
||||
} from "@/lib/agents/generation-project-state";
|
||||
|
||||
interface AgentGenerationWorkspaceDarkV2Props {
|
||||
export interface AgentGenerationWorkspaceDarkV2Props {
|
||||
chatId: string;
|
||||
projectId: string;
|
||||
selectedAgentPresetId?: string | null;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user