@hugpy/video-intelligence-ui 0.3.3 → 0.3.4

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 (38) hide show
  1. package/dist/assets/index-DMoT8G1q.css +1 -0
  2. package/dist/assets/index-GeD1mo16.js +4190 -0
  3. package/dist/index.html +2 -2
  4. package/entry.tsx +6 -27
  5. package/package.json +3 -2
  6. package/src/config.ts +11 -27
  7. package/src/demo/demoFetch.ts +0 -9
  8. package/src/nav/Navbar.tsx +3 -3
  9. package/src/session.ts +8 -43
  10. package/src/stations/GenerateStation.tsx +8 -5
  11. package/src/stations/SharePanel.tsx +4 -4
  12. package/src/stations/ToksPanel.tsx +1 -2
  13. package/src/stations/WorkbenchSidebar.tsx +8 -261
  14. package/src/stations/ops/CharacterGroupsPanel.tsx +75 -83
  15. package/src/stations/studio/IdentitiesStation.tsx +66 -75
  16. package/src/stations/studio/StudioGenerateMode.tsx +3 -2
  17. package/src/stations/studio/StudioGenerateTab.tsx +258 -188
  18. package/src/stations/studio/StudioMovieComposer.tsx +321 -46
  19. package/src/stations/studio/StudioPlane.tsx +3 -1
  20. package/src/stations/studio/StudioViewer.tsx +6 -11
  21. package/src/stations/studio/studioShared.tsx +14 -2
  22. package/src/stations/studio/useIdentityCreateJob.ts +26 -21
  23. package/src/stations/studio/useReconstruction.ts +11 -5
  24. package/src/video/PromptAssistButtons.tsx +5 -6
  25. package/src/video/jobStream.ts +119 -0
  26. package/src/video/jobTracker.ts +67 -76
  27. package/src/video/modelsFeed.ts +105 -0
  28. package/src/video/studioOracle.ts +48 -0
  29. package/src/video/useMediaJobs.ts +17 -3
  30. package/src/video/useModels.ts +12 -53
  31. package/src/video/useMovieSessions.ts +15 -6
  32. package/src/video/useOperatorHistory.ts +11 -101
  33. package/src/video/usePromptAssist.ts +6 -3
  34. package/src/video/useToks.ts +14 -6
  35. package/src/video/watchVideoJob.ts +43 -0
  36. package/dist/assets/index-DuT1AMLn.css +0 -1
  37. package/dist/assets/index-_13ZiBDP.js +0 -4191
  38. package/src/video/useWorkerCalls.ts +0 -165
@@ -34,7 +34,8 @@ import * as THREE from "three";
34
34
  import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
35
35
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
36
36
  import { request, okValue, errorOf, describeAppError } from "../../transport/client";
37
- import { hugpyConfig, mediaBytesUrl, identityGenerateUrl, identityMeshStatusUrl, videoJobUrl } from "../../config";
37
+ import { hugpyConfig, mediaBytesUrl, identityGenerateUrl, identityMeshStatusUrl } from "../../config";
38
+ import { watchVideoJob } from "../../video/watchVideoJob";
38
39
  import { useMediaLibrary } from "../../video/mediaLibrary";
39
40
  import type { LibraryItem } from "../../video/mediaLibrary";
40
41
  import type { MediaRef, IdentityReconstruction, IdentityAngleView } from "../../video/contract";
@@ -529,12 +530,12 @@ export function ExtractFromVideoPanel({
529
530
  const [pickError, setPickError] = useState<string | null>(null);
530
531
 
531
532
  const active = useRef(false);
532
- const timer = useRef<number | null>(null);
533
+ const jobStop = useRef<(() => void) | null>(null);
533
534
 
534
535
  useEffect(() => {
535
536
  return () => {
536
537
  active.current = false;
537
- if (timer.current != null) window.clearTimeout(timer.current);
538
+ jobStop.current?.();
538
539
  };
539
540
  }, []);
540
541
 
@@ -575,47 +576,34 @@ export function ExtractFromVideoPanel({
575
576
  }
576
577
 
577
578
  const jobId = enq.jobId;
578
- const deadline = Date.now() + 20 * 60 * 1000; // ~20 min bound, mirrors the /generate poll
579
- const pollOnce = async () => {
580
- if (!active.current) return;
581
- if (Date.now() > deadline) {
579
+ jobStop.current?.();
580
+ jobStop.current = watchVideoJob(jobId, (row) => {
581
+ if (!active.current) return true;
582
+ const job = row as {
583
+ progress?: number | null;
584
+ result?: { ok?: boolean; error?: { code?: string; message?: string; retryable?: boolean } } | null;
585
+ stage?: string | null;
586
+ message?: string | null;
587
+ };
588
+ const result = job.result;
589
+ if (result && typeof result.ok === "boolean") {
582
590
  active.current = false;
583
591
  setBusy(false);
584
- setStatus("Timed out waiting for the video extraction.");
585
- return;
586
- }
587
- const res = await request<unknown>(videoJobUrl(jobId), {
588
- meta: { specKey: "studio", operation: "identity.video_extract.status" },
589
- });
590
- if (!active.current) return;
591
- if (res.ok) {
592
- const job = okValue(res) as {
593
- progress?: number | null;
594
- result?: { ok?: boolean; error?: { code?: string; message?: string; retryable?: boolean } } | null;
595
- stage?: string | null;
596
- message?: string | null;
597
- };
598
- const result = job.result;
599
- if (result && typeof result.ok === "boolean") {
600
- active.current = false;
601
- setBusy(false);
602
- if (result.ok) {
603
- setStatus("done");
604
- setVideo(null);
605
- reload();
606
- } else {
607
- setStatus((result.error && result.error.message) || "Extraction failed.");
608
- }
609
- return;
610
- }
611
- const pct = typeof job.progress === "number" ? ` (${Math.round(job.progress * 100)}%)` : "";
612
- const stage = job.stage || job.message;
613
- setStatus(`Extracting…${pct}${stage ? ` — ${stage}` : ""}`);
592
+ if (result.ok) {
593
+ setStatus("done");
594
+ setVideo(null);
595
+ reload();
596
+ } else setStatus((result.error && result.error.message) || "Extraction failed.");
597
+ return true;
614
598
  }
615
- // queued/running/transient-poll-failure → keep polling.
616
- timer.current = window.setTimeout(() => void pollOnce(), 5000);
617
- };
618
- timer.current = window.setTimeout(() => void pollOnce(), 5000);
599
+ const pct = typeof job.progress === "number" ? ` (${Math.round(job.progress * 100)}%)` : "";
600
+ const stage = job.stage || job.message;
601
+ setStatus(`Extracting…${pct}${stage ? ` — ${stage}` : ""}`);
602
+ }, () => {
603
+ active.current = false;
604
+ setBusy(false);
605
+ setStatus("Timed out waiting for the video extraction.");
606
+ }, 20 * 60 * 1000);
619
607
  }
620
608
 
621
609
  const canSubmit = video != null && !busy && uploadPhase === "idle";
@@ -1523,13 +1511,13 @@ function IdentityDetail({
1523
1511
  // SUPERSEDED by the one-click mesh path for the identity gen flow — kept behind
1524
1512
  // the "⚙ Settings & advanced" disclosure (see the render) so the tab stays to
1525
1513
  // the point.
1526
- const genTimer = useRef<number | null>(null);
1514
+ const genStop = useRef<(() => void) | null>(null);
1527
1515
  const genActive = useRef(false);
1528
1516
  // Stop the poll loop + free the timer when this card unmounts mid-build.
1529
1517
  useEffect(() => {
1530
1518
  return () => {
1531
1519
  genActive.current = false;
1532
- if (genTimer.current != null) window.clearTimeout(genTimer.current);
1520
+ genStop.current?.();
1533
1521
  };
1534
1522
  }, []);
1535
1523
 
@@ -1770,43 +1758,46 @@ function IdentityDetail({
1770
1758
  return;
1771
1759
  }
1772
1760
 
1773
- const deadline = Date.now() + 20 * 60 * 1000; // ~20 min bound
1774
- const pollOnce = async () => {
1775
- if (!genActive.current) return;
1776
- if (Date.now() > deadline) {
1777
- genActive.current = false;
1778
- setGenBusy(false);
1779
- setGenStatus("Timed out waiting for the 3D identity build.");
1780
- reload();
1781
- return;
1782
- }
1783
- const sres = await request<unknown>(identityMeshStatusUrl(profile.slug, reconId), {
1784
- meta: { specKey: "studio", operation: "identity.mesh.status" },
1785
- });
1786
- if (!genActive.current) return;
1787
- if (sres.ok) {
1788
- const ms = okValue(sres) as { status?: string; error?: string | null };
1789
- const st = ms.status ?? "queued";
1790
- if (st === "done") {
1791
- genActive.current = false;
1792
- setGenBusy(false);
1793
- setGenStatus("done");
1794
- reload();
1795
- return;
1796
- }
1797
- if (st === "error" || st === "cancelled") {
1761
+ if (!enq.job_id) {
1762
+ genActive.current = false;
1763
+ setGenBusy(false);
1764
+ setGenStatus("Generate response had no job_id.");
1765
+ return;
1766
+ }
1767
+ genStop.current?.();
1768
+ genStop.current = watchVideoJob(enq.job_id, (row) => {
1769
+ if (!genActive.current) return true;
1770
+ const result = row.result as { ok?: boolean; error?: { message?: string } } | null;
1771
+ if (result && typeof result.ok === "boolean") {
1772
+ if (!result.ok) {
1798
1773
  genActive.current = false;
1799
1774
  setGenBusy(false);
1800
- setGenStatus(ms.error || `Build ${st}.`);
1775
+ setGenStatus(result.error?.message || "3D identity build failed.");
1801
1776
  reload();
1802
- return;
1777
+ } else {
1778
+ void request<unknown>(identityMeshStatusUrl(profile.slug, reconId), {
1779
+ meta: { specKey: "studio", operation: "identity.mesh.final" },
1780
+ }).then((sres) => {
1781
+ if (!genActive.current) return;
1782
+ genActive.current = false;
1783
+ setGenBusy(false);
1784
+ if (!sres.ok) setGenStatus(describeAppError(errorOf(sres)));
1785
+ else {
1786
+ const ms = okValue(sres) as { status?: string; error?: string | null };
1787
+ setGenStatus(ms.status === "done" ? "done" : ms.error || `Build ${ms.status || "unknown"}.`);
1788
+ }
1789
+ reload();
1790
+ });
1803
1791
  }
1804
- setGenStatus(`Generating… (${st})`);
1792
+ return true;
1805
1793
  }
1806
- // queued/running/transient-poll-failure → keep polling.
1807
- genTimer.current = window.setTimeout(() => void pollOnce(), 5000);
1808
- };
1809
- genTimer.current = window.setTimeout(() => void pollOnce(), 5000);
1794
+ setGenStatus(`Generating… (${String(row.current_stage || row.status || "running")})`);
1795
+ }, () => {
1796
+ genActive.current = false;
1797
+ setGenBusy(false);
1798
+ setGenStatus("Timed out waiting for the 3D identity build.");
1799
+ reload();
1800
+ }, 20 * 60 * 1000);
1810
1801
  }
1811
1802
 
1812
1803
  const { short: createdShort, full: createdFull } = fmtCreated(profile.created_at);
@@ -16,13 +16,14 @@ export interface StudioGenerateModeProps {
16
16
  /** Set by the top-level Generate tab (Clip → "clip", Cinema → "movie") to lock the
17
17
  * studio surface to one sub-mode and hide its internal Clip|Cinema switcher. */
18
18
  lockedSurfaceMode?: "clip" | "movie";
19
+ onSelectSurfaceMode?: (mode: "clip" | "movie") => void;
19
20
  }
20
21
 
21
- export function StudioGenerateMode({ lockedSurfaceMode }: StudioGenerateModeProps = {}) {
22
+ export function StudioGenerateMode({ lockedSurfaceMode, onSelectSurfaceMode }: StudioGenerateModeProps = {}) {
22
23
  return (
23
24
  <div className="vi-section-tabs vi-section-tabs--stacked">
24
25
  <section className="vi-section-region vi-section-region--work" aria-label="Studio">
25
- <StudioPlane lockedSurfaceMode={lockedSurfaceMode} />
26
+ <StudioPlane lockedSurfaceMode={lockedSurfaceMode} onSelectSurfaceMode={onSelectSurfaceMode} />
26
27
  </section>
27
28
  </div>
28
29
  );