116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
import { useEffect } from "react";
|
|
|
|
/**
|
|
* Sandbox streaming performance guard (write_file live preview).
|
|
*
|
|
* Default ON. Rollback without a rebuild:
|
|
* 1. Append `?sandboxStreamPerf=0` to the page URL and refresh
|
|
* e.g. `/#/page/workspace/chats/new?sandboxStreamPerf=0`
|
|
* 2. Or in the console:
|
|
* localStorage.setItem("deerflow.sandbox-stream-perf", "0"); location.reload();
|
|
*
|
|
* Restore the optimized path:
|
|
* localStorage.removeItem("deerflow.sandbox-stream-perf") and drop the query, then refresh.
|
|
*/
|
|
export const SANDBOX_STREAM_PERF_STORAGE_KEY = "deerflow.sandbox-stream-perf";
|
|
export const SANDBOX_STREAM_PERF_QUERY = "sandboxStreamPerf";
|
|
/** Aligns with roundtable write_file preview (~12fps). */
|
|
export const SANDBOX_STREAM_FLUSH_MS = 80;
|
|
export const SANDBOX_LEGACY_SCROLL_INTERVAL_MS = 180;
|
|
|
|
function parseFlag(raw: string | null | undefined): boolean | null {
|
|
if (raw == null) return null;
|
|
const value = raw.trim().toLowerCase();
|
|
if (value === "0" || value === "off" || value === "false" || value === "no") {
|
|
return false;
|
|
}
|
|
if (value === "1" || value === "on" || value === "true" || value === "yes") {
|
|
return true;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function queryValue(search: string, key: string): string | null {
|
|
if (!search) return null;
|
|
const normalized = search.startsWith("?") ? search.slice(1) : search;
|
|
return new URLSearchParams(normalized).get(key);
|
|
}
|
|
|
|
export function resolveSandboxStreamPerfEnabled(input: {
|
|
search?: string;
|
|
hash?: string;
|
|
storageValue?: string | null;
|
|
}): boolean {
|
|
const searchFlag = parseFlag(queryValue(input.search ?? "", SANDBOX_STREAM_PERF_QUERY));
|
|
if (searchFlag != null) return searchFlag;
|
|
|
|
const hash = input.hash ?? "";
|
|
const hashQueryIndex = hash.indexOf("?");
|
|
if (hashQueryIndex >= 0) {
|
|
const hashFlag = parseFlag(
|
|
queryValue(hash.slice(hashQueryIndex), SANDBOX_STREAM_PERF_QUERY),
|
|
);
|
|
if (hashFlag != null) return hashFlag;
|
|
}
|
|
|
|
const storedFlag = parseFlag(input.storageValue);
|
|
if (storedFlag != null) return storedFlag;
|
|
return true;
|
|
}
|
|
|
|
export function isSandboxStreamPerfEnabled(): boolean {
|
|
if (typeof window === "undefined") return true;
|
|
let storageValue: string | null = null;
|
|
try {
|
|
storageValue = window.localStorage.getItem(SANDBOX_STREAM_PERF_STORAGE_KEY);
|
|
} catch {
|
|
storageValue = null;
|
|
}
|
|
return resolveSandboxStreamPerfEnabled({
|
|
search: window.location.search,
|
|
hash: window.location.hash,
|
|
storageValue,
|
|
});
|
|
}
|
|
|
|
export function useSandboxFollowScroll({
|
|
follow,
|
|
scrollToBottom,
|
|
contentKey,
|
|
viewMode,
|
|
}: {
|
|
follow: boolean;
|
|
scrollToBottom: (behavior?: ScrollBehavior) => boolean;
|
|
contentKey: string;
|
|
viewMode: string;
|
|
}) {
|
|
const perfEnabled = isSandboxStreamPerfEnabled();
|
|
|
|
useEffect(() => {
|
|
if (!perfEnabled || !follow) return;
|
|
let raf = 0;
|
|
raf = requestAnimationFrame(() => {
|
|
raf = 0;
|
|
scrollToBottom("auto");
|
|
});
|
|
return () => {
|
|
if (raf) cancelAnimationFrame(raf);
|
|
};
|
|
}, [perfEnabled, follow, contentKey, viewMode, scrollToBottom]);
|
|
|
|
useEffect(() => {
|
|
if (perfEnabled || !follow) return;
|
|
const tick = () => {
|
|
scrollToBottom("auto");
|
|
};
|
|
tick();
|
|
const intervalId = window.setInterval(tick, SANDBOX_LEGACY_SCROLL_INTERVAL_MS);
|
|
return () => window.clearInterval(intervalId);
|
|
}, [perfEnabled, follow, scrollToBottom]);
|
|
|
|
useEffect(() => {
|
|
if (perfEnabled || !follow) return;
|
|
scrollToBottom("auto");
|
|
}, [perfEnabled, follow, contentKey, viewMode, scrollToBottom]);
|
|
}
|