File size: 1,924 Bytes
afe44d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
export type ProgressState =
  | { phase: "idle" }
  | {
      phase: "running";
      kind: "single" | "dialog";
      turn: number;
      total: number;
      elapsedS: number;
    }
  | { phase: "done"; elapsedS: number }
  | { phase: "error"; message: string };

type ProgressEvent = {
  type: "start" | "tick" | "turn_complete" | "done" | "error";
  elapsed_s: number;
  kind?: "single" | "dialog";
  turn?: number;
  total_turns?: number;
  message?: string;
  seed_used?: number | null;
};

export function subscribeProgress(
  onState: (s: ProgressState) => void,
): () => void {
  const es = new EventSource("/api/progress");
  let doneTimer: number | null = null;

  es.onmessage = (m: MessageEvent) => {
    if (doneTimer !== null) {
      window.clearTimeout(doneTimer);
      doneTimer = null;
    }
    let evt: ProgressEvent;
    try {
      evt = JSON.parse(m.data) as ProgressEvent;
    } catch {
      return;
    }
    if (evt.type === "start" || evt.type === "tick" || evt.type === "turn_complete") {
      onState({
        phase: "running",
        kind: (evt.kind ?? "single"),
        turn: evt.turn ?? 0,
        total: evt.total_turns ?? 1,
        elapsedS: evt.elapsed_s ?? 0,
      });
      return;
    }
    if (evt.type === "done") {
      onState({ phase: "done", elapsedS: evt.elapsed_s });
      doneTimer = window.setTimeout(() => onState({ phase: "idle" }), 1000);
      return;
    }
    if (evt.type === "error") {
      onState({ phase: "error", message: evt.message ?? "Generation failed" });
    }
  };

  return () => {
    if (doneTimer !== null) window.clearTimeout(doneTimer);
    es.close();
  };
}

import { useEffect, useState } from "react";

export function useProgress(): ProgressState {
  const [state, setState] = useState<ProgressState>({ phase: "idle" });
  useEffect(() => {
    const close = subscribeProgress(setState);
    return close;
  }, []);
  return state;
}