techfreakworm commited on
Commit
95a2dcc
·
unverified ·
1 Parent(s): e3066e0

feat(web): ProgressBar — determinate (Dialog) and indeterminate (Single)

Browse files
web/src/components/ProgressBar.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useProgress } from "@/lib/progress";
2
+
3
+ function fmt(s: number): string {
4
+ const m = Math.floor(s / 60);
5
+ const sec = Math.floor(s % 60);
6
+ return `${m}:${sec.toString().padStart(2, "0")}`;
7
+ }
8
+
9
+ export default function ProgressBar() {
10
+ const state = useProgress();
11
+ if (state.phase === "idle") return null;
12
+
13
+ if (state.phase === "error") {
14
+ return (
15
+ <div className="border-b border-red-900/40 bg-red-950/30 px-8 py-2.5">
16
+ <span className="label-mono text-red-400">progress error</span>
17
+ <span className="ml-3 text-sm text-red-300/90">{state.message}</span>
18
+ </div>
19
+ );
20
+ }
21
+
22
+ const isRunning = state.phase === "running";
23
+ const isDialog = isRunning && state.kind === "dialog";
24
+ const fill =
25
+ state.phase === "done"
26
+ ? 1
27
+ : isDialog && state.total > 0
28
+ ? state.turn / state.total
29
+ : null;
30
+
31
+ const elapsedS = state.phase === "running" ? state.elapsedS : state.phase === "done" ? state.elapsedS : 0;
32
+ const label =
33
+ state.phase === "done"
34
+ ? `done · ${fmt(elapsedS)}`
35
+ : isDialog
36
+ ? `Turn ${state.turn} of ${state.total} · ${fmt(elapsedS)}`
37
+ : `Generating · ${fmt(elapsedS)}`;
38
+
39
+ return (
40
+ <div className="border-b border-[hsl(var(--ember))]/30 bg-[hsl(var(--ember))]/10 px-8 py-2">
41
+ <div className="flex items-center gap-4">
42
+ <span className="label-mono text-[hsl(var(--ember))] whitespace-nowrap">
43
+ {label}
44
+ </span>
45
+ <div className="flex-1 h-1 bg-[hsl(var(--ember))]/20 rounded-sm overflow-hidden">
46
+ {fill === null ? (
47
+ <div className="h-full w-1/3 bg-[hsl(var(--ember))] animate-progress-stripe" />
48
+ ) : (
49
+ <div
50
+ className="h-full bg-[hsl(var(--ember))] transition-[width] duration-200 ease-linear"
51
+ style={{ width: `${fill * 100}%` }}
52
+ />
53
+ )}
54
+ </div>
55
+ </div>
56
+ </div>
57
+ );
58
+ }
web/src/pages/Studio.tsx CHANGED
@@ -16,6 +16,7 @@ import LoadingBanner from "@/components/LoadingBanner";
16
  import ModelPicker from "@/components/ModelPicker";
17
  import ModeToggle, { type Mode } from "@/components/ModeToggle";
18
  import ParamsPanel from "@/components/ParamsPanel";
 
19
  import TagBar from "@/components/TagBar";
20
  import VoiceComposer from "@/components/VoiceComposer";
21
  import VoiceLibrary from "@/components/VoiceLibrary";
@@ -203,6 +204,7 @@ export default function Studio() {
203
  visible={loadingModel}
204
  message="Loading model — first activation can take 30–60s"
205
  />
 
206
  {err && (
207
  <div className="border-b border-red-900/40 bg-red-950/30 px-8 py-2.5">
208
  <span className="label-mono text-red-400">error</span>
 
16
  import ModelPicker from "@/components/ModelPicker";
17
  import ModeToggle, { type Mode } from "@/components/ModeToggle";
18
  import ParamsPanel from "@/components/ParamsPanel";
19
+ import ProgressBar from "@/components/ProgressBar";
20
  import TagBar from "@/components/TagBar";
21
  import VoiceComposer from "@/components/VoiceComposer";
22
  import VoiceLibrary from "@/components/VoiceLibrary";
 
204
  visible={loadingModel}
205
  message="Loading model — first activation can take 30–60s"
206
  />
207
+ <ProgressBar />
208
  {err && (
209
  <div className="border-b border-red-900/40 bg-red-950/30 px-8 py-2.5">
210
  <span className="label-mono text-red-400">error</span>