SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.8 KB · 107 lines typescript
Raw Blame History
1"use client";2import * as React from "react";34/**5 * Fake streaming engine for the marketing mocks — purely client-side, nothing is sent anywhere.6 * Drives one or more scripted texts character by character with a per-stream delay and speed,7 * and records wall-clock timings so the mock metadata (tok/s, TTFT) is internally consistent.8 */9export interface FakeStream {10  text: string;11  /** characters per tick (~35 ms) */12  speed: number;13  /** ms before the first token */14  delay: number;15}1617export interface FakeStreamState {18  /** Characters revealed per stream. */19  progress: number[];20  /** ms since the first token of stream i (frozen when it completes). */21  elapsedFor: (i: number) => number;22  /** Time to first token per stream (its scripted delay). */23  ttftFor: (i: number) => number;24  done: (i: number) => boolean;25  allDone: boolean;26}2728export function useFakeStreams(streams: FakeStream[], active: boolean, replayKey: number, instant: boolean): FakeStreamState {29  const [progress, setProgress] = React.useState<number[]>(() => streams.map(() => 0));30  const [elapsed, setElapsed] = React.useState(0);31  const [finishedAt, setFinishedAt] = React.useState<(number | null)[]>(() => streams.map(() => null));3233  React.useEffect(() => {34    if (!active) return;35    if (instant) {36      // Reduced motion: show the finished state right away.37      const t = setTimeout(() => {38        setProgress(streams.map((s) => s.text.length));39        setFinishedAt(streams.map((s) => s.delay + 2400));40        setElapsed(2400);41      }, 0);42      return () => clearTimeout(t);43    }44    const started = performance.now();45    let cancelled = false;46    let raf = 0;47    let last = started;48    const finished: (number | null)[] = streams.map(() => null);49    const tick = (now: number) => {50      if (cancelled) return;51      const sinceStart = now - started;52      if (now - last >= 35) {53        last = now;54        setElapsed(sinceStart);55        setProgress((prev) => {56          let done = true;57          const next = prev.map((p, i) => {58            const s = streams[i];59            if (sinceStart < s.delay) {60              done = false;61              return 0;62            }63            if (p >= s.text.length) return p;64            const jitter = Math.random() < 0.15 ? 0 : s.speed;65            const n = Math.min(s.text.length, p + jitter);66            if (n < s.text.length) done = false;67            else if (finished[i] === null) finished[i] = sinceStart;68            return n;69          });70          if (done) cancelled = true;71          return next;72        });73        setFinishedAt([...finished]);74      }75      if (!cancelled) raf = requestAnimationFrame(tick);76    };77    const start = setTimeout(() => {78      setProgress(streams.map(() => 0));79      setFinishedAt(streams.map(() => null));80      setElapsed(0);81      raf = requestAnimationFrame(tick);82    }, 250);83    return () => {84      cancelled = true;85      clearTimeout(start);86      cancelAnimationFrame(raf);87    };88    // replayKey intentionally restarts the effect; `streams` are module constants.89    // eslint-disable-next-line react-hooks/exhaustive-deps90  }, [active, instant, replayKey]);9192  const elapsedFor = (i: number) => Math.max(0, (finishedAt[i] ?? elapsed) - streams[i].delay);93  const ttftFor = (i: number) => streams[i].delay;94  const done = (i: number) => progress[i] >= streams[i].text.length;95  const allDone = streams.every((s, i) => progress[i] >= s.text.length);96  return { progress, elapsedFor, ttftFor, done, allDone };97}9899export function approxTokens(chars: number) {100  return Math.round(chars / 4);101}102103/** Which stream finishes first given the scripted delays and speeds (35 ms per tick). */104export function fastestIndex(streams: FakeStream[]): number {105  return streams.map((s, i) => [s.delay + (s.text.length / s.speed) * 35, i] as const).sort((a, b) => a[0] - b[0])[0][1];106}107