mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-11-10 06:23:28 +09:00
fix(ruvocal): keep hidden-tab streams responsive
This commit is contained in:
1 parent
6b8f0d0b5a
commit
40affb83fe
3 files changed
+43
-4
No files matched your search
@@ -129,6 +129,30 @@ describe("smoothStreamUpdates", () => {
|
||||
expect(delays[0]).toBeGreaterThanOrEqual(delays.at(-1) ?? 0);
|
||||
});
|
||||
|
||||
it("drains stream chunks without timer pacing while the page is hidden", async () => {
|
||||
const source: MessageUpdate[] = [
|
||||
{ type: MessageUpdateType.Stream, token: "one two three four five " },
|
||||
];
|
||||
const delays: number[] = [];
|
||||
|
||||
const updates = await collect(
|
||||
smoothStreamUpdates(fromArray(source), {
|
||||
minDelayMs: 5,
|
||||
maxDelayMs: 80,
|
||||
_internal: {
|
||||
sleep: async (ms: number) => {
|
||||
delays.push(ms);
|
||||
},
|
||||
isPageHidden: () => true,
|
||||
detectChunk: (buffer) => /\S+\s+/.exec(buffer)?.[0] ?? null,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(streamText(updates)).toBe("one two three four five ");
|
||||
expect(delays).toEqual([]);
|
||||
});
|
||||
|
||||
it("handles CJK text correctly", async () => {
|
||||
const source: MessageUpdate[] = [{ type: MessageUpdateType.Stream, token: "你好,世界!" }];
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ type SmoothStreamConfig = {
|
||||
now?: () => number;
|
||||
sleep?: (ms: number) => Promise<void>;
|
||||
detectChunk?: ChunkDetector;
|
||||
isPageHidden?: () => boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -168,7 +169,12 @@ export async function* smoothStreamUpdates(
|
||||
maxDelayMs = 80,
|
||||
minRateCharsPerMs = 0.3,
|
||||
maxBufferedMs = 400,
|
||||
_internal: { now = () => performance.now(), sleep = defaultSleep, detectChunk } = {},
|
||||
_internal: {
|
||||
now = () => performance.now(),
|
||||
sleep = defaultSleep,
|
||||
detectChunk,
|
||||
isPageHidden = defaultIsPageHidden,
|
||||
} = {},
|
||||
}: SmoothStreamConfig = {}
|
||||
): AsyncGenerator<MessageUpdate> {
|
||||
const chunkDetector = detectChunk ?? createWordChunkDetector();
|
||||
@@ -251,7 +257,7 @@ export async function* smoothStreamUpdates(
|
||||
const effectiveMinDelayMs = underBacklogPressure ? 0 : minDelayMs;
|
||||
const delayMs = Math.round(Math.max(effectiveMinDelayMs, Math.min(maxDelayMs, rawDelay)));
|
||||
|
||||
if (delayMs > 0) {
|
||||
if (delayMs > 0 && !isPageHidden()) {
|
||||
await sleep(delayMs);
|
||||
}
|
||||
}
|
||||
@@ -318,6 +324,8 @@ export const isMessageToolProgressUpdate = (
|
||||
|
||||
const defaultSleep = (ms: number): Promise<void> =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms));
|
||||
const defaultIsPageHidden = (): boolean =>
|
||||
typeof document !== "undefined" && document.visibilityState === "hidden";
|
||||
const waitForEvent = (eventTarget: EventTarget, eventName: string) =>
|
||||
new Promise<boolean>((resolve) =>
|
||||
eventTarget.addEventListener(eventName, () => resolve(true), { once: true })
|
||||
|
||||
@@ -249,6 +249,7 @@
|
||||
// Initialize lastUpdateTime outside the loop to persist between updates
|
||||
let lastUpdateTime = new Date();
|
||||
let frameFlushScheduled = false;
|
||||
let frameFlushTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const flushBuffer = (currentTime: Date) => {
|
||||
if (buffer.length === 0) return;
|
||||
@@ -261,14 +262,20 @@
|
||||
if (frameFlushScheduled) return;
|
||||
frameFlushScheduled = true;
|
||||
const flush = () => {
|
||||
if (!frameFlushScheduled) return;
|
||||
frameFlushScheduled = false;
|
||||
if (frameFlushTimeout !== undefined) {
|
||||
clearTimeout(frameFlushTimeout);
|
||||
frameFlushTimeout = undefined;
|
||||
}
|
||||
flushBuffer(new Date());
|
||||
};
|
||||
if (typeof requestAnimationFrame === "function") {
|
||||
requestAnimationFrame(flush);
|
||||
} else {
|
||||
setTimeout(flush, 0);
|
||||
}
|
||||
// requestAnimationFrame is suspended in background tabs. Keep a
|
||||
// bounded fallback so buffered tokens still reach the DOM there.
|
||||
frameFlushTimeout = setTimeout(flush, 300);
|
||||
};
|
||||
|
||||
for await (const update of messageUpdatesIterator) {
|
||||
|
||||
Reference in new issue
Block a user