@hugpy/video-intelligence-ui 0.3.6 → 0.3.7

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.
package/dist/index.html CHANGED
@@ -49,7 +49,7 @@
49
49
  overflow-x: hidden;
50
50
  }
51
51
  </style>
52
- <script type="module" crossorigin src="/video/assets/index--is_kWXv.js"></script>
52
+ <script type="module" crossorigin src="/video/assets/index-xEPYADt9.js"></script>
53
53
  <link rel="stylesheet" crossorigin href="/video/assets/index-DMoT8G1q.css">
54
54
  </head>
55
55
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hugpy/video-intelligence-ui",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "description": "Hugpy video-intelligence console: image crop, audio crop, frames/models and generate stations over the hugpy-video job bus; a standalone Vite + Tailwind v4 arm served at /video",
@@ -42,7 +42,7 @@ function RunPicker({ runs }: { runs: RunManifestApi }) {
42
42
  <section className="vi-oracle-panel" aria-label="Runs">
43
43
  <h3>
44
44
  Runs
45
- <span className="vi-oracle-sub">performance jobs on the media bus (GET /video/jobs?all=1) — pick one to read its manifest</span>
45
+ <span className="vi-oracle-sub">Video production jobs — select a run to follow its progress</span>
46
46
  </h3>
47
47
  <div className="vi-oracle-row">
48
48
  <button type="button" className="vi-btn vi-btn-sm" onClick={jobs.refresh}>
@@ -82,7 +82,7 @@ function RunPicker({ runs }: { runs: RunManifestApi }) {
82
82
  ))}
83
83
  {!jobs.jobs.length && !jobs.gap ? (
84
84
  <span className="vi-knob-hint">
85
- No performance jobs in the bus feed (live or recent terminal). Enqueue one via the oracle runner, or paste an
85
+ No video production jobs in the feed. Submit a request above, or paste an
86
86
  older job id.
87
87
  </span>
88
88
  ) : null}
@@ -119,10 +119,10 @@ function RunHeader({ runs }: { runs: RunManifestApi }) {
119
119
  {m ? (
120
120
  <>
121
121
  <div className="vi-oracle-row">
122
- <Tag kind={m.ok ? "ok" : "alarm"}>{m.ok ? "ok" : "not ok"}</Tag>
122
+ <Tag kind={(m.ok ?? job?.result?.ok) ? "ok" : "alarm"}>{(m.ok ?? job?.result?.ok) ? "ok" : "not ok"}</Tag>
123
123
  {m.stopped_after ? <Tag kind="warn">stopped after {m.stopped_after}</Tag> : null}
124
124
  {job?.status ? <StateChip state={job.status} /> : null}
125
- {m.dag ? <Tag kind="accent">ran on the DAG</Tag> : <Tag>linear recipe</Tag>}
125
+ {m.dag ? <Tag kind="accent">ran on the DAG</Tag> : <Tag>{m.kind === "studio_movie" ? "sequential production" : "linear recipe"}</Tag>}
126
126
  {job?.result?.error ? (
127
127
  <Tag kind="alarm">
128
128
  {job.result.error.code}: {job.result.error.message}
@@ -132,7 +132,7 @@ function RunHeader({ runs }: { runs: RunManifestApi }) {
132
132
  <dl className="vi-oracle-kv" style={{ marginTop: "0.5rem" }}>
133
133
  <dt>run id</dt>
134
134
  <dd>
135
- <code>{m.run_id}</code>
135
+ <code>{m.run_id ?? m.job_id}</code>
136
136
  </dd>
137
137
  <dt>registry version</dt>
138
138
  <dd>
@@ -143,9 +143,9 @@ function RunHeader({ runs }: { runs: RunManifestApi }) {
143
143
  <code>{short(m.goal_digest)}</code> · <code>{short(m.snapshot_digest)}</code> · <code>{short(m.lock_digest)}</code>
144
144
  </dd>
145
145
  <dt>segments</dt>
146
- <dd>{m.segment_digests?.length ?? 0}</dd>
146
+ <dd>{m.segments_completed !== undefined ? `${m.segments_completed} / ${m.segments_total ?? "?"}` : m.segment_digests?.length ?? 0}</dd>
147
147
  <dt>video</dt>
148
- <dd>{m.video_ref ? <code title={m.video_ref}>{short(m.video_ref, 56)}</code> : "none accepted"}</dd>
148
+ <dd>{(m.video_ref ?? m.assembly?.movie) ? <code>{short(m.video_ref ?? m.assembly?.movie, 56)}</code> : "none accepted"}</dd>
149
149
  <dt>receipts</dt>
150
150
  <dd>
151
151
  {(m.receipts ?? []).length ? (
@@ -7,15 +7,16 @@ type EnqueueResult = { job_id?: string; error?: string };
7
7
  type PerformanceProbe = { ready: boolean; bound?: string[]; unbound?: Array<{ seam: string; requirement: string }> };
8
8
  type Stage = "authority" | "snapshot" | "audio" | "lock" | "segments" | "keyframes" | "clips" | "assembly";
9
9
 
10
- /** The small, explicit entry point for the Oracle's durable performance job. */
10
+ /** Submit a requested output; retain the explicit performance workflow when needed. */
11
11
  export function PerformanceComposer({ onEnqueued }: { onEnqueued: (jobId: string) => void }) {
12
+ const [performance, setPerformance] = useState(false);
12
13
  const [prompt, setPrompt] = useState("");
13
14
  const [dialogue, setDialogue] = useState("");
14
15
  const [speaker, setSpeaker] = useState("narrator");
15
16
  const [voiceId, setVoiceId] = useState("narrator");
16
17
  const [rawRef, setRawRef] = useState("");
17
18
  const [quality, setQuality] = useState("balanced");
18
- const [stopAfter, setStopAfter] = useState<Stage | "">("segments");
19
+ const [stopAfter, setStopAfter] = useState<Stage | "">("");
19
20
  const [tts, setTts] = useState(3);
20
21
  const [keyframes, setKeyframes] = useState(3);
21
22
  const [clips, setClips] = useState(3);
@@ -28,6 +29,7 @@ export function PerformanceComposer({ onEnqueued }: { onEnqueued: (jobId: string
28
29
  const [probeGap, setProbeGap] = useState("");
29
30
 
30
31
  useEffect(() => {
32
+ if (!performance) return;
31
33
  let active = true;
32
34
  void (async () => {
33
35
  const result = await request<PerformanceProbe>(`${hugpyConfig.apiBase}/video/performance/probe`, {
@@ -38,15 +40,15 @@ export function PerformanceComposer({ onEnqueued }: { onEnqueued: (jobId: string
38
40
  else setProbeGap(describeAppError(errorOf(result)));
39
41
  })();
40
42
  return () => { active = false; };
41
- }, []);
43
+ }, [performance]);
42
44
 
43
45
  async function enqueue() {
44
46
  const lines = dialogue.split("\n").map((text) => text.trim()).filter(Boolean);
45
- if (!prompt.trim() || !lines.length || !speaker.trim() || !voiceId.trim() || !rawRef.trim()) {
46
- setMessage("Add the goal, at least one dialogue line, speaker, voice ID, and raw request reference.");
47
+ if (!prompt.trim() || (performance && (!lines.length || !speaker.trim() || !voiceId.trim() || !rawRef.trim()))) {
48
+ setMessage(performance ? "Add the goal, at least one dialogue line, speaker, voice ID, and raw request reference." : "Describe the video you want.");
47
49
  return;
48
50
  }
49
- const body = {
51
+ const body = performance ? {
50
52
  goal: { objective: prompt.trim(), raw_prompt: prompt, quality, planner_mode: "local_only" },
51
53
  dialogue: { locked: true, lines: lines.map((text, i) => ({ line_id: `line-${i + 1}`, speaker: speaker.trim(), text })) },
52
54
  casting: [[speaker.trim(), { voice_id: voiceId.trim(), kind: "synthetic" }]],
@@ -58,12 +60,12 @@ export function PerformanceComposer({ onEnqueued }: { onEnqueued: (jobId: string
58
60
  negative_prompt: negative.trim() || null,
59
61
  max_seconds: maxSeconds ? Number(maxSeconds) : null,
60
62
  stop_after: stopAfter || null,
61
- };
63
+ } : { prompt: prompt.trim(), output: "video" };
62
64
  setBusy(true);
63
65
  setMessage("");
64
- const result = await request<EnqueueResult>(`${hugpyConfig.apiBase}/video/jobs/performance`, {
66
+ const result = await request<EnqueueResult>(`${hugpyConfig.apiBase}${performance ? "/video/jobs/performance" : "/oracle/route"}`, {
65
67
  method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
66
- meta: { specKey: "oracle", operation: "oracle.performance.enqueue" },
68
+ meta: { specKey: "oracle", operation: "oracle.production.enqueue" },
67
69
  });
68
70
  setBusy(false);
69
71
  if (!result.ok) {
@@ -79,12 +81,14 @@ export function PerformanceComposer({ onEnqueued }: { onEnqueued: (jobId: string
79
81
  onEnqueued(jobId);
80
82
  }
81
83
 
82
- return <section className="vi-oracle-panel vi-oracle-composer" aria-label="New performance">
83
- <h3>New performance <span className="vi-oracle-sub">audio first · durable media job · inspect each stage below</span></h3>
84
+ return <section className="vi-oracle-panel vi-oracle-composer" aria-label="New video request">
85
+ <h3>New video request <span className="vi-oracle-sub">Oracle plans the shots and settings using available models</span></h3>
84
86
  <div className="vi-oracle-fields">
85
87
  <label className="vi-oracle-field vi-oracle-wide">Goal / visual direction
86
88
  <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} rows={3} placeholder="Describe the film, characters, setting and action" />
87
89
  </label>
90
+ <label className="vi-oracle-field vi-oracle-wide"><span><input type="checkbox" checked={performance} onChange={(e) => setPerformance(e.target.checked)} /> Use the explicit dialogue performance workflow</span></label>
91
+ {performance && <>
88
92
  <label className="vi-oracle-field vi-oracle-wide">Locked dialogue · one line per utterance
89
93
  <textarea value={dialogue} onChange={(e) => setDialogue(e.target.value)} rows={3} placeholder="The words to speak" />
90
94
  </label>
@@ -107,13 +111,14 @@ export function PerformanceComposer({ onEnqueued }: { onEnqueued: (jobId: string
107
111
  <label className="vi-oracle-field">Max seconds <input type="number" min={1} value={maxSeconds} onChange={(e) => setMaxSeconds(e.target.value)} placeholder="No limit" /></label>
108
112
  <label className="vi-oracle-field">Seed salt <input type="number" min={0} value={seed} onChange={(e) => setSeed(Number(e.target.value))} /></label>
109
113
  <label className="vi-oracle-field vi-oracle-wide">Negative prompt <input value={negative} onChange={(e) => setNegative(e.target.value)} placeholder="Optional exclusions" /></label>
114
+ </>}
110
115
  </div>
111
- <div className="vi-oracle-note" role="status">
116
+ {performance && <div className="vi-oracle-note" role="status">
112
117
  {probe ? <>
113
118
  <strong>{probe.ready ? "Full recipe wired" : "Staged runs available; full recipe has gaps"}</strong>
114
119
  {probe.unbound?.length ? <ul className="vi-oracle-limits">{probe.unbound.map((gap) => <li key={gap.seam}><code>{gap.seam}</code>: {gap.requirement}</li>)}</ul> : null}
115
120
  </> : probeGap ? <>Could not read performance capability probe: {probeGap}</> : "Checking performance capabilities…"}
116
- </div>
117
- <div className="vi-oracle-row"><button type="button" className="vi-btn vi-btn-accent" disabled={busy} onClick={() => void enqueue()}>{busy ? "Queuing…" : "Queue performance"}</button>{message && <span role="status">{message}</span>}</div>
121
+ </div>}
122
+ <div className="vi-oracle-row"><button type="button" className="vi-btn vi-btn-accent" disabled={busy} onClick={() => void enqueue()}>{busy ? "Planning and queuing…" : performance ? "Queue performance" : "Produce video"}</button>{message && <span role="status">{message}</span>}</div>
118
123
  </section>;
119
124
  }
@@ -244,6 +244,12 @@ export interface ManifestReceipt {
244
244
  }
245
245
 
246
246
  export interface RunManifest {
247
+ kind?: string;
248
+ title?: string;
249
+ job_id?: string;
250
+ segments_completed?: number;
251
+ segments_total?: number;
252
+ assembly?: { movie?: string | null; total_frames?: number };
247
253
  run_id: string;
248
254
  ok: boolean;
249
255
  goal_digest?: string;
@@ -27,6 +27,7 @@
27
27
  */
28
28
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
29
29
 
30
+ import { subscribeJobStream } from "../../video/jobStream";
30
31
  import { hugpyConfig, videoJobUrl } from "../../config";
31
32
  import {
32
33
  describeAppError,
@@ -329,7 +330,7 @@ function parseJobRows(raw: unknown): PerformanceJobRow[] {
329
330
  // The bus kind for the oracle's audio-first runner is ("oracle","performance")
330
331
  // → "performance" / "oracle.performance" / "video_performance" depending on
331
332
  // the bridge; match loosely on the word rather than on one spelling.
332
- if (!/performance/i.test(name)) continue;
333
+ if (!/performance|studio_movie/i.test(name)) continue;
333
334
  const id = String(row.job_id ?? row.id ?? "");
334
335
  if (!id) continue;
335
336
  const prog = isRecord(row.progress) ? row.progress : null;
@@ -352,7 +353,7 @@ export function usePerformanceJobs(): { jobs: PerformanceJobRow[]; gap: Gap | nu
352
353
  meta: { specKey: STATION, operation: "oracle.runs.list" },
353
354
  });
354
355
  if (!r.ok) {
355
- setGap(gapOf(errorOf(r), "performance jobs"));
356
+ setGap(gapOf(errorOf(r), "video production jobs"));
356
357
  return;
357
358
  }
358
359
  setGap(null);
@@ -405,7 +406,17 @@ export function useRunManifest(): RunManifestApi {
405
406
  }, []);
406
407
 
407
408
  useEffect(() => {
409
+ setJob(null);
408
410
  void load(jobId);
411
+ if (!jobId) return;
412
+ const unsubscribe = subscribeJobStream((event) => {
413
+ if (event.job_id !== jobId || !event.job) return;
414
+ ++seq.current;
415
+ setJob(event.job as unknown as JobView);
416
+ setLoading(false);
417
+ setGap(null);
418
+ });
419
+ return () => { ++seq.current; unsubscribe(); };
409
420
  }, [jobId, load]);
410
421
 
411
422
  const manifest = useMemo<RunManifest | null>(() => {