@hugpy/video-intelligence-ui 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/package.json +1 -3
  2. package/src/config.ts +36 -0
  3. package/src/demo/assets/generate/cinema-cavern/movie.mp4 +0 -0
  4. package/src/demo/assets/generate/cinema-cavern/seg_00.mp4 +0 -0
  5. package/src/demo/assets/generate/clips/cat-lounge.mp4 +0 -0
  6. package/src/demo/assets/generate/clips/drone-city.mp4 +0 -0
  7. package/src/demo/assets/generate/clips/paper-sails.mp4 +0 -0
  8. package/src/demo/assets/generate/movie-galaxy/movie.mp4 +0 -0
  9. package/src/demo/assets/generate/movie-galaxy/seg_00.mp4 +0 -0
  10. package/src/demo/assets/generate/movie-galaxy/seg_01.mp4 +0 -0
  11. package/src/demo/assets/generate/movie-galaxy/seg_02.mp4 +0 -0
  12. package/src/demo/assets/generate/movie-galaxy/seg_03.mp4 +0 -0
  13. package/src/demo/assets/generate/movie-medow/movie.mp4 +0 -0
  14. package/src/demo/assets/generate/movie-medow/seg_00.mp4 +0 -0
  15. package/src/demo/assets/generate/movie-medow/seg_01.mp4 +0 -0
  16. package/src/demo/assets/generate/scene-desert/frame_00000.png +0 -0
  17. package/src/demo/assets/generate/scene-desert/frame_00001.png +0 -0
  18. package/src/demo/assets/generate/scene-desert/frame_00002.png +0 -0
  19. package/src/demo/assets/generate/scene-desert/frame_00003.png +0 -0
  20. package/src/demo/assets/generate/scene-desert/frame_00004.png +0 -0
  21. package/src/demo/assets/generate/scene-desert/frame_00005.png +0 -0
  22. package/src/demo/assets/generate/scene-desert/scene.mp4 +0 -0
  23. package/src/demo/assets/generate/scene-vintage/frame_00000.png +0 -0
  24. package/src/demo/assets/generate/scene-vintage/frame_00001.png +0 -0
  25. package/src/demo/assets/generate/scene-vintage/frame_00002.png +0 -0
  26. package/src/demo/assets/generate/scene-vintage/frame_00003.png +0 -0
  27. package/src/demo/assets/generate/scene-vintage/frame_00004.png +0 -0
  28. package/src/demo/assets/generate/scene-vintage/frame_00005.png +0 -0
  29. package/src/demo/assets/generate/scene-vintage/scene.mp4 +0 -0
  30. package/src/demo/assets/generate2/s0.png +0 -0
  31. package/src/demo/assets/generate2/s1.png +0 -0
  32. package/src/demo/assets/generate2/s10.png +0 -0
  33. package/src/demo/assets/generate2/s11.png +0 -0
  34. package/src/demo/assets/generate2/s12.png +0 -0
  35. package/src/demo/assets/generate2/s13.png +0 -0
  36. package/src/demo/assets/generate2/s14.png +0 -0
  37. package/src/demo/assets/generate2/s15.png +0 -0
  38. package/src/demo/assets/generate2/s16.png +0 -0
  39. package/src/demo/assets/generate2/s17.png +0 -0
  40. package/src/demo/assets/generate2/s18.png +0 -0
  41. package/src/demo/assets/generate2/s19.png +0 -0
  42. package/src/demo/assets/generate2/s2.png +0 -0
  43. package/src/demo/assets/generate2/s20.png +0 -0
  44. package/src/demo/assets/generate2/s21.mp4 +0 -0
  45. package/src/demo/assets/generate2/s22.mp4 +0 -0
  46. package/src/demo/assets/generate2/s23.mp4 +0 -0
  47. package/src/demo/assets/generate2/s24.mp4 +0 -0
  48. package/src/demo/assets/generate2/s25.mp4 +0 -0
  49. package/src/demo/assets/generate2/s26.mp4 +0 -0
  50. package/src/demo/assets/generate2/s27.mp4 +0 -0
  51. package/src/demo/assets/generate2/s28.mp4 +0 -0
  52. package/src/demo/assets/generate2/s29.mp4 +0 -0
  53. package/src/demo/assets/generate2/s3.png +0 -0
  54. package/src/demo/assets/generate2/s30.mp4 +0 -0
  55. package/src/demo/assets/generate2/s4.png +0 -0
  56. package/src/demo/assets/generate2/s5.png +0 -0
  57. package/src/demo/assets/generate2/s6.png +0 -0
  58. package/src/demo/assets/generate2/s7.png +0 -0
  59. package/src/demo/assets/generate2/s8.png +0 -0
  60. package/src/demo/assets/generate2/s9.png +0 -0
  61. package/src/demo/demoFetch.ts +126 -3
  62. package/src/demo/fixtures.ts +72 -4
  63. package/src/demo/promptAssist.ts +25 -0
  64. package/src/demo/sampleData.ts +960 -0
  65. package/src/demo/sampleSets.ts +172 -0
  66. package/src/stations/ActiveProcessesStation.tsx +18 -3
  67. package/src/stations/GenerateStation.tsx +1031 -364
  68. package/src/stations/GoalComposer.tsx +9 -1
  69. package/src/stations/ScriptFirstStation.tsx +1081 -0
  70. package/src/stations/ToksPanel.tsx +188 -0
  71. package/src/stations/WorkbenchDrawer.tsx +4 -3
  72. package/src/stations/WorkbenchSidebar.tsx +60 -12
  73. package/src/stations/oracle/DagPanel.tsx +391 -0
  74. package/src/stations/oracle/OracleStation.tsx +304 -0
  75. package/src/stations/oracle/SpatialTab.tsx +185 -0
  76. package/src/stations/oracle/StewardPanel.tsx +165 -0
  77. package/src/stations/oracle/oracle.css +467 -0
  78. package/src/stations/oracle/oracleShared.tsx +160 -0
  79. package/src/stations/oracle/oracleTypes.ts +304 -0
  80. package/src/stations/oracle/useOracle.ts +560 -0
  81. package/src/stations/registry.ts +37 -0
  82. package/src/stations/scriptFirst.css +350 -0
  83. package/src/stations/studio/IdentitiesStation.tsx +219 -292
  84. package/src/stations/studio/IdentityCreatePanel.tsx +390 -0
  85. package/src/stations/studio/StudioGenerateTab.tsx +162 -6
  86. package/src/stations/studio/StudioMovieComposer.tsx +763 -318
  87. package/src/stations/studio/{StudioMovieComposer.tsx.bak-sessions-20260812 → StudioMovieComposer.tsx.bak-declutter-20260813} +350 -48
  88. package/src/stations/studio/StudioPlane.tsx +35 -3
  89. package/src/stations/studio/StudioViewer.tsx +116 -14
  90. package/src/stations/studio/coordinationBadge.tsx +206 -0
  91. package/src/stations/studio/identityUpload.ts +66 -0
  92. package/src/stations/studio/promptCard.tsx +362 -59
  93. package/src/stations/studio/useIdentityCreateJob.ts +184 -0
  94. package/src/stations/studio/useIdentityProfiles.ts +66 -0
  95. package/src/stations/useScriptFirstRun.ts +596 -0
  96. package/src/style/app.css +176 -0
  97. package/src/video/LengthRow.tsx +189 -0
  98. package/src/video/LibraryGroups.tsx +81 -0
  99. package/src/video/UnnamedWarn.tsx +146 -0
  100. package/src/video/coordinationReport.ts +266 -0
  101. package/src/video/jobClock.ts +104 -0
  102. package/src/video/spreadAssist.ts +26 -0
  103. package/src/video/useMediaJobs.ts +26 -0
  104. package/src/video/useOperatorHistory.ts +105 -0
  105. package/src/video/useToks.ts +193 -0
  106. package/src/stations/ActiveProcessesStation.tsx.bak-sessions-20260812 +0 -228
  107. package/src/stations/GenerateStation.tsx.bak-tester-removal +0 -5170
  108. package/src/stations/studio/StudioMovieSessions.tsx.bak-sessions-20260812 +0 -417
@@ -0,0 +1,188 @@
1
+ // ToksPanel — the LIVE "tok/s" overview for the generate workbench, contributed
2
+ // as ONE TAB to the unified drawer (drawerShell.tsx → WorkbenchDrawer.tsx), the
3
+ // same window as Console / ComfyUI / Active and framed with the same
4
+ // `.vi-active-panel-body` chrome. It mirrors ActivePanel: a real component whose
5
+ // MOUNT starts the poll, so `keepMounted: false` IS the pause — the 2s cadence
6
+ // runs exactly when the operator is looking at this tab (see ActivePanel's note).
7
+ //
8
+ // Three compact sections, all fed by `useToks` (two same-origin `/api` endpoints
9
+ // polled together every 2s, error-tolerant, mount-gated):
10
+ // 1. Live tail — the last ~25 recent samples, newest on top.
11
+ // 2. Per-worker strip — recent grouped by worker → last tok/s + window avg.
12
+ // 3. Best-config board — /report's top ~10, best mean first.
13
+ // Every field is guarded: the server side is being built in parallel, so a
14
+ // missing number renders as "—", never a crash.
15
+ import { useEffect, useState } from "react";
16
+ import type { DrawerTab } from "./drawerShell";
17
+ import {
18
+ useToks,
19
+ rollupByWorker,
20
+ tokAgo,
21
+ TOKS_POLL_MS,
22
+ type TokEntry,
23
+ type TokGroup,
24
+ } from "../video/useToks";
25
+
26
+ const TAIL_LIMIT = 25;
27
+ const BOARD_LIMIT = 10;
28
+
29
+ /** A number to N decimals, or "—" when the server sent nothing honest. */
30
+ function fmt(v: number | null | undefined, digits = 1): string {
31
+ return typeof v === "number" && isFinite(v) ? v.toFixed(digits) : "—";
32
+ }
33
+
34
+ /** The ok/err badge for one sample (null ok → no badge, we just don't know). */
35
+ function OkBadge({ ok }: { ok: boolean | null }) {
36
+ if (ok == null) return null;
37
+ return (
38
+ <span
39
+ className={`vi-assistlog-badge ${ok ? "vi-assistlog-badge--ok" : "vi-assistlog-badge--err"}`}
40
+ title={ok ? "completed ok" : "errored"}
41
+ >
42
+ {ok ? "ok" : "err"}
43
+ </span>
44
+ );
45
+ }
46
+
47
+ function LiveTail({ entries, nowMs }: { entries: TokEntry[]; nowMs: number }) {
48
+ const tail = entries.slice(0, TAIL_LIMIT);
49
+ return (
50
+ <div>
51
+ <h3 className="vi-toks-heading">Live tail</h3>
52
+ {tail.length === 0 ? (
53
+ <p className="vi-knob-hint">No samples yet — run an inference and it lands here.</p>
54
+ ) : (
55
+ <ul className="vi-assistlog-list vi-toks-list">
56
+ {tail.map((e, i) => (
57
+ <li
58
+ key={`${e.workerId ?? "?"}-${e.ts ?? i}-${i}`}
59
+ className="vi-assistlog-row vi-toks-row"
60
+ >
61
+ <span className="vi-toks-tok">{fmt(e.tokS)}<span className="vi-knob-hint"> tok/s</span></span>
62
+ <span className="vi-toks-model" title={e.modelKey ?? undefined}>
63
+ {e.modelKey ?? "—"}
64
+ </span>
65
+ <span className="vi-knob-hint vi-toks-worker">{e.workerName ?? e.workerId ?? "—"}</span>
66
+ <span className="vi-knob-hint">ttft {fmt(e.ttftS, 2)}s</span>
67
+ <span className="vi-knob-hint">{e.completionTokens ?? "—"} tok</span>
68
+ {e.configKey ? <span className="vi-assistlog-chip">{e.configKey}</span> : null}
69
+ <OkBadge ok={e.ok} />
70
+ <span className="vi-knob-hint vi-toks-ago">{tokAgo(e.ts, nowMs)}</span>
71
+ </li>
72
+ ))}
73
+ </ul>
74
+ )}
75
+ </div>
76
+ );
77
+ }
78
+
79
+ function WorkerStrip({ entries }: { entries: TokEntry[] }) {
80
+ const rows = rollupByWorker(entries);
81
+ return (
82
+ <div>
83
+ <h3 className="vi-toks-heading">Per-worker</h3>
84
+ {rows.length === 0 ? (
85
+ <p className="vi-knob-hint">No workers reporting.</p>
86
+ ) : (
87
+ <div className="vi-toks-strip">
88
+ {rows.map((w) => (
89
+ <div key={w.workerId} className="vi-toks-worker-card">
90
+ <div className="vi-toks-worker-name" title={w.modelKey ?? undefined}>
91
+ {w.workerName}
92
+ </div>
93
+ <div className="vi-toks-worker-nums">
94
+ <span className="vi-toks-tok">{fmt(w.lastTokS)}</span>
95
+ <span className="vi-knob-hint"> now · avg {fmt(w.avgTokS)} · n{w.n}</span>
96
+ </div>
97
+ {w.modelKey ? <div className="vi-knob-hint vi-toks-worker-model">{w.modelKey}</div> : null}
98
+ </div>
99
+ ))}
100
+ </div>
101
+ )}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ function Leaderboard({ groups }: { groups: TokGroup[] }) {
107
+ const top = groups.slice(0, BOARD_LIMIT);
108
+ return (
109
+ <div>
110
+ <h3 className="vi-toks-heading">Best config</h3>
111
+ {top.length === 0 ? (
112
+ <p className="vi-knob-hint">No rollup yet — the report fills in as samples accrue.</p>
113
+ ) : (
114
+ <ul className="vi-assistlog-list vi-toks-list">
115
+ {top.map((g, i) => (
116
+ <li
117
+ key={`${g.workerName ?? "?"}-${g.modelKey ?? "?"}-${g.configKey ?? i}`}
118
+ className="vi-assistlog-row vi-toks-row"
119
+ >
120
+ <span className="vi-knob-hint vi-toks-rank">#{i + 1}</span>
121
+ <span className="vi-toks-tok">{fmt(g.meanTokS)}<span className="vi-knob-hint"> mean</span></span>
122
+ <span className="vi-knob-hint">p50 {fmt(g.p50TokS)}</span>
123
+ <span className="vi-knob-hint">p95 {fmt(g.p95TokS)}</span>
124
+ <span className="vi-toks-model">{g.modelKey ?? "—"}</span>
125
+ <span className="vi-knob-hint vi-toks-worker">{g.workerName ?? "—"}</span>
126
+ {g.configKey ? <span className="vi-assistlog-chip">{g.configKey}</span> : null}
127
+ <span className="vi-knob-hint">n{g.n ?? "—"}</span>
128
+ </li>
129
+ ))}
130
+ </ul>
131
+ )}
132
+ </div>
133
+ );
134
+ }
135
+
136
+ /** The panel body — a station card with the three sections + a live header. */
137
+ export function ToksStation() {
138
+ const { entries, groups, live } = useToks();
139
+ // A 1s clock so the "Ns ago" column ages between the 2s polls, exactly as the
140
+ // Active panel drives its ProcessTimeline.
141
+ const [nowMs, setNowMs] = useState(() => Date.now());
142
+ useEffect(() => {
143
+ const id = window.setInterval(() => setNowMs(Date.now()), 1000);
144
+ return () => window.clearInterval(id);
145
+ }, []);
146
+
147
+ return (
148
+ <section className="station-card station-wide vi-toks-station">
149
+ <div className="vi-toks-header">
150
+ <h2>tok/s</h2>
151
+ <span className="vi-assistlog-conn vi-toks-cadence">
152
+ <span
153
+ className={`vi-assistlog-dot ${live ? "vi-assistlog-dot--live" : "vi-assistlog-dot--disconnected"}`}
154
+ aria-hidden
155
+ />
156
+ {live ? "live" : "paused"} · every {Math.round(TOKS_POLL_MS / 1000)}s
157
+ </span>
158
+ </div>
159
+ <p className="vi-knob-hint">
160
+ Live inference throughput across the LLM workers — the newest samples, a
161
+ per-worker last/avg strip, and the best-performing configs by mean tok/s.
162
+ </p>
163
+ <LiveTail entries={entries} nowMs={nowMs} />
164
+ <WorkerStrip entries={entries} />
165
+ <Leaderboard groups={groups} />
166
+ </section>
167
+ );
168
+ }
169
+
170
+ /** The tok/s tab in the unified drawer — mount starts the 2s poll, so
171
+ * `keepMounted: false` is the pause (see ActivePanel's polling note). */
172
+ export function toksDrawerTab(): DrawerTab<"toks"> {
173
+ return {
174
+ id: "toks",
175
+ label: "tok/s",
176
+ hint: "Open the live tok/s overview alongside this station — no tab switch",
177
+ toggleClassName: "vi-toks-toggle",
178
+ toggleLabel: "⚡ tok/s",
179
+ ariaLabel: "Live tokens-per-second overview",
180
+ panelClassName: "vi-toks-panel",
181
+ keepMounted: false, // mount IS the pause — same policy as the Active tab
182
+ body: (
183
+ <div className="vi-active-panel-body">
184
+ <ToksStation />
185
+ </div>
186
+ ),
187
+ };
188
+ }
@@ -31,14 +31,15 @@
31
31
  import { useComfyDrawerTab } from "./ComfyPanel";
32
32
  import { consoleDrawerTab } from "./ConsolePanel";
33
33
  import { activeDrawerTab } from "./ActivePanel";
34
+ import { toksDrawerTab } from "./ToksPanel";
34
35
  import { DrawerShell, useDrawerState, type LegacyDrawerKey } from "./drawerShell";
35
36
 
36
- type DrawerTabId = "console" | "comfy" | "active";
37
+ type DrawerTabId = "console" | "comfy" | "active" | "toks";
37
38
 
38
39
  // Strip order = the order the panels arrived in (Console t52, ComfyUI 2026-08-04,
39
40
  // Active 2026-08-06), which is also the toolbar's existing left-to-right order —
40
41
  // so the tab strip and the deep-link row read the same way round.
41
- const TAB_IDS = ["console", "comfy", "active"] as const;
42
+ const TAB_IDS = ["console", "comfy", "active", "toks"] as const;
42
43
 
43
44
  // ONE key now holds {open, size, tab}, replacing the three per-panel records.
44
45
  const STORAGE_KEY = "vi.workbench.drawer.v1";
@@ -58,7 +59,7 @@ export function WorkbenchDrawer() {
58
59
  // descriptors. All three are rebuilt every render — a descriptor is just JSX,
59
60
  // and an unselected tab's body element is only MOUNTED if the shell renders it.
60
61
  const comfy = useComfyDrawerTab();
61
- const tabs = [consoleDrawerTab(), comfy, activeDrawerTab()];
62
+ const tabs = [consoleDrawerTab(), comfy, activeDrawerTab(), toksDrawerTab()];
62
63
 
63
64
  return (
64
65
  <DrawerShell state={drawer} tabs={tabs} tablistLabel="Overlay panels" />
@@ -1029,7 +1029,30 @@ function LibraryPanel({
1029
1029
  items: LibraryItem[];
1030
1030
  onGenerate: boolean;
1031
1031
  }) {
1032
- const works = useMemo(() => groupLibrary(items), [items]);
1032
+ // TYPE SECTIONS (operator 2026-08-13): the library splits into Images and
1033
+ // Videos (plus a rare Other for audio/unknown), each holding its work groups
1034
+ // newest-first — groupLibrary already sorts both levels freshest-first, so
1035
+ // the per-type ordering is "most recent on top" by construction.
1036
+ const sections = useMemo(() => {
1037
+ const imgs = items.filter((it) => it.ref.kind === "image");
1038
+ const vids = items.filter((it) => it.ref.kind === "video");
1039
+ const rest = items.filter((it) => it.ref.kind !== "image" && it.ref.kind !== "video");
1040
+ return [
1041
+ { id: "images", label: "🖼 Images", count: imgs.length, works: groupLibrary(imgs) },
1042
+ { id: "videos", label: "🎞 Videos", count: vids.length, works: groupLibrary(vids) },
1043
+ { id: "other", label: "📎 Other", count: rest.length, works: groupLibrary(rest) },
1044
+ ].filter((sec) => sec.count > 0);
1045
+ }, [items]);
1046
+ // Both type sections start OPEN (hiding freshly-landed work behind a closed
1047
+ // header is how "it never populates" reports happen); the caret remembers.
1048
+ const [closedTypes, setClosedTypes] = useState<Set<string>>(() => new Set());
1049
+ const toggleType = (id: string) =>
1050
+ setClosedTypes((prev) => {
1051
+ const next = new Set(prev);
1052
+ if (next.has(id)) next.delete(id);
1053
+ else next.add(id);
1054
+ return next;
1055
+ });
1033
1056
  // Expand state keyed by work key — the established discipline (a Set that
1034
1057
  // survives the library's refresh cadence: the works array is rebuilt on every
1035
1058
  // change, but the keys are stable, so an expanded work stays open across a
@@ -1052,17 +1075,42 @@ function LibraryPanel({
1052
1075
  }
1053
1076
 
1054
1077
  return (
1055
- <ul className="vi-lib-list">
1056
- {works.map((w) => (
1057
- <WorkSection
1058
- key={w.key}
1059
- group={w}
1060
- open={openKeys.has(w.key)}
1061
- onToggle={() => toggle(w.key)}
1062
- onGenerate={onGenerate}
1063
- />
1064
- ))}
1065
- </ul>
1078
+ <>
1079
+ {sections.map((sec) => {
1080
+ const isOpen = !closedTypes.has(sec.id);
1081
+ return (
1082
+ <div key={sec.id} className="vi-lib-typegroup">
1083
+ <button
1084
+ type="button"
1085
+ className="vi-lib-group-toggle vi-lib-typegroup-head"
1086
+ aria-expanded={isOpen}
1087
+ onClick={() => toggleType(sec.id)}
1088
+ >
1089
+ <span className="vi-lib-group-caret" aria-hidden>
1090
+ {isOpen ? "▾" : "▸"}
1091
+ </span>{" "}
1092
+ {sec.label} <span className="vi-proc-count">{sec.count}</span>
1093
+ </button>
1094
+ {isOpen && (
1095
+ <ul className="vi-lib-list">
1096
+ {sec.works.map((w) => (
1097
+ <WorkSection
1098
+ key={`${sec.id}:${w.key}`}
1099
+ group={w}
1100
+ // Expand state is NAMESPACED by type section: origin-keyed works
1101
+ // (uploads/frames/legacy history) can carry the SAME work key in
1102
+ // both Images and Videos, and a shared key would open both.
1103
+ open={openKeys.has(`${sec.id}:${w.key}`)}
1104
+ onToggle={() => toggle(`${sec.id}:${w.key}`)}
1105
+ onGenerate={onGenerate}
1106
+ />
1107
+ ))}
1108
+ </ul>
1109
+ )}
1110
+ </div>
1111
+ );
1112
+ })}
1113
+ </>
1066
1114
  );
1067
1115
  }
1068
1116