DanielWalnut 459dd78707
perf(frontend): bound delivery, bundles, and long-running UI work (#4622)
* docs: design frontend performance remediation

* docs: plan frontend performance remediation

* test(frontend): add route asset performance budgets

* perf(nginx): compress textual responses safely

* perf(frontend): lazy load case study media

* perf(frontend): bound static demo file tracing

* perf(frontend): restore static locale boundaries

* perf(frontend): defer closed workspace panels

* perf(frontend): split editors and deduplicate highlighting

* perf(frontend): index incremental message derivation

* perf(frontend): stabilize paged history cache policy

* perf(frontend): bound streaming markdown renders

* perf(frontend): virtualize message history

* perf(frontend): bound and virtualize chat lists

* perf(frontend): suspend inactive decorative animation

* perf(browser): stream latest frames as binary

* perf(artifacts): stream bounded text previews

* docs: finalize performance runtime boundaries

* style(backend): apply test formatting

* fix(frontend): keep translation functions client-side

* perf(frontend): defer decorative animation bundles

* test(frontend): lock optimized route budgets

* fix: harden frontend performance boundaries

* test(frontend): update i18n provider fixture

* fix(frontend): preserve sidebar pagination position

* style(backend): format artifact range test
2026-08-01 22:19:59 +08:00

58 lines
1.9 KiB
TypeScript

import { throwGatewayApiError } from "@/core/api/errors";
import { fetch } from "@/core/api/fetcher";
import { getBackendBaseURL } from "@/core/config";
export interface BrowserNavigateResult {
screenshot: string | null;
url: string;
title: string;
}
export async function navigateBrowser(
threadId: string,
url: string,
): Promise<BrowserNavigateResult> {
const response = await fetch(
`${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/browser/navigate`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url }),
},
);
if (!response.ok) {
await throwGatewayApiError(
response,
`Failed to navigate browser: ${response.statusText}`,
);
}
return response.json();
}
/**
* Build the WebSocket URL for the live browser stream.
*
* Uses the configured backend base URL when present (split-origin dev/prod),
* otherwise falls back to the current same-origin host (nginx proxies the
* upgrade in the unified deployment).
*
* When ``seedUrl`` is provided, the server aligns the live page to it when the
* current page is blank or points at a different URL — so reconnecting to a
* stale session lands on the page the user expects instead of a white
* about:blank screen or a leftover page. The seed is SSRF-screened server-side.
*/
export function browserStreamURL(threadId: string, seedUrl?: string): string {
const base = getBackendBaseURL();
const origin =
base && base.length > 0
? base
: typeof window !== "undefined"
? window.location.origin
: "";
const wsOrigin = origin.replace(/^http/i, "ws");
const query = new URLSearchParams();
query.set("frame_format", "binary");
if (seedUrl) query.set("seed", seedUrl);
return `${wsOrigin}/api/threads/${encodeURIComponent(threadId)}/browser/stream?${query.toString()}`;
}