@hugpy/video-intelligence-ui 0.3.1 → 0.3.3
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/LICENSE +0 -0
- package/README.md +9 -0
- package/dist/assets/hugpy-mark-BXktFcOQ.png +0 -0
- package/dist/assets/index-DuT1AMLn.css +1 -0
- package/dist/assets/index-_13ZiBDP.js +4191 -0
- package/dist/index.html +71 -0
- package/entry.tsx +0 -0
- package/index.html +15 -0
- package/package.json +42 -4
- package/src/assets/hugpy-mark.png +0 -0
- package/src/config.ts +0 -0
- package/src/demo/DemoBanner.tsx +0 -0
- package/src/demo/demo.css +0 -0
- package/src/demo/demoFetch.ts +5 -2
- package/src/demo/fixtures.ts +0 -0
- package/src/demo/identityFixture.ts +96 -0
- package/src/demo/index.ts +0 -0
- package/src/demo/mediaBase.ts +0 -0
- package/src/demo/mode.ts +0 -0
- package/src/demo/promptAssist.ts +0 -0
- package/src/demo/sampleData.ts +0 -0
- package/src/demo/sampleSets.ts +0 -0
- package/src/demo/seed.ts +0 -0
- package/src/nav/BrandMark.css +0 -0
- package/src/nav/BrandMark.tsx +0 -0
- package/src/nav/Navbar.css +0 -0
- package/src/nav/Navbar.tsx +6 -1
- package/src/regions/SpatialRegionEditor.tsx +0 -0
- package/src/regions/TemporalRegionEditor.tsx +0 -0
- package/src/regions/types.ts +0 -0
- package/src/session.ts +0 -0
- package/src/share.ts +0 -0
- package/src/stations/ActivePanel.tsx +0 -0
- package/src/stations/ActiveProcessesStation.tsx +0 -0
- package/src/stations/AddFileBar.tsx +0 -0
- package/src/stations/AudioCropStation.tsx +0 -0
- package/src/stations/ComfyPanel.tsx +0 -0
- package/src/stations/ComfyStation.tsx +0 -0
- package/src/stations/CondensedKnobStrip.tsx +0 -0
- package/src/stations/ConsolePanel.tsx +0 -0
- package/src/stations/FrameExtractStation.tsx +0 -0
- package/src/stations/GenIdentityBar.tsx +0 -0
- package/src/stations/GenerateStation.tsx +2 -1
- package/src/stations/GoalComposer.tsx +0 -0
- package/src/stations/ImageCropStation.tsx +0 -0
- package/src/stations/KnobInfo.tsx +0 -0
- package/src/stations/PlaceholderStation.tsx +0 -0
- package/src/stations/ScriptFirstStation.tsx +4 -0
- package/src/stations/SectionTabs.tsx +0 -0
- package/src/stations/SharePanel.tsx +0 -0
- package/src/stations/SidebarRegistry.tsx +0 -0
- package/src/stations/StationShell.tsx +0 -0
- package/src/stations/StudioClipsStation.tsx +0 -0
- package/src/stations/TemplateStepsPanel.tsx +190 -0
- package/src/stations/ToksPanel.tsx +0 -0
- package/src/stations/WorkbenchDrawer.tsx +0 -0
- package/src/stations/WorkbenchSidebar.tsx +0 -0
- package/src/stations/WorkbenchStation.tsx +0 -0
- package/src/stations/comfyEmbed.ts +0 -0
- package/src/stations/drawerShell.tsx +0 -0
- package/src/stations/ops/AudioCropCore.tsx +0 -0
- package/src/stations/ops/CharacterGroupsPanel.tsx +0 -0
- package/src/stations/ops/FrameExtractCore.tsx +0 -0
- package/src/stations/ops/ImageCropCore.tsx +0 -0
- package/src/stations/ops/VideoCropCore.tsx +0 -0
- package/src/stations/ops/useVideoCropJobs.ts +0 -0
- package/src/stations/oracle/DagPanel.tsx +0 -0
- package/src/stations/oracle/OracleStation.tsx +2 -0
- package/src/stations/oracle/PerformanceComposer.tsx +119 -0
- package/src/stations/oracle/SpatialTab.tsx +0 -0
- package/src/stations/oracle/StewardPanel.tsx +0 -0
- package/src/stations/oracle/oracle.css +33 -0
- package/src/stations/oracle/oracleShared.tsx +0 -0
- package/src/stations/oracle/oracleTypes.ts +0 -0
- package/src/stations/oracle/useOracle.ts +0 -0
- package/src/stations/pollCaps.ts +0 -0
- package/src/stations/registry.ts +0 -0
- package/src/stations/scriptFirst.css +23 -0
- package/src/stations/studio/AngleRingEditor.tsx +0 -0
- package/src/stations/studio/IdentitiesStation.tsx +21 -2
- package/src/stations/studio/IdentityCreatePanel.tsx +0 -0
- package/src/stations/studio/IdentityProfileControls.tsx +0 -0
- package/src/stations/studio/StudioActiveProcesses.tsx +0 -0
- package/src/stations/studio/StudioAssistLogPanel.tsx +0 -0
- package/src/stations/studio/StudioGenerateMode.tsx +0 -0
- package/src/stations/studio/StudioGenerateTab.tsx +20 -2
- package/src/stations/studio/StudioItemCard.tsx +0 -0
- package/src/stations/studio/StudioLibraryTab.tsx +0 -0
- package/src/stations/studio/StudioMovieComposer.tsx +0 -0
- package/src/stations/studio/StudioMovieSessions.tsx +12 -19
- package/src/stations/studio/StudioPlane.tsx +25 -4
- package/src/stations/studio/StudioSplicedRow.tsx +0 -0
- package/src/stations/studio/StudioViewer.tsx +0 -0
- package/src/stations/studio/TurntableViewer.tsx +0 -0
- package/src/stations/studio/coordinationBadge.tsx +0 -0
- package/src/stations/studio/identityUpload.ts +0 -0
- package/src/stations/studio/movieTimeline.ts +0 -0
- package/src/stations/studio/promptCard.tsx +0 -0
- package/src/stations/studio/studioItem.ts +0 -0
- package/src/stations/studio/studioShared.tsx +0 -0
- package/src/stations/studio/useIdentityCreateJob.ts +0 -0
- package/src/stations/studio/useIdentityProfiles.ts +0 -0
- package/src/stations/studio/useReconstruction.ts +0 -0
- package/src/stations/types.ts +0 -0
- package/src/stations/useAudioCropJobs.ts +0 -0
- package/src/stations/useAudioExtractJob.ts +0 -0
- package/src/stations/useCropJobs.ts +0 -0
- package/src/stations/useFrameJobs.ts +0 -0
- package/src/stations/useGenerateJob.ts +0 -0
- package/src/stations/useGenerateMovieJob.ts +0 -0
- package/src/stations/useGenerateRowJobs.ts +0 -0
- package/src/stations/useGenerateSceneJob.ts +0 -0
- package/src/stations/useGenerateSceneRowJobs.ts +0 -0
- package/src/stations/useScriptFirstRun.ts +0 -0
- package/src/style/app.css +49 -0
- package/src/transport/client.ts +0 -0
- package/src/transport/telemetry.ts +0 -0
- package/src/video/DropReceptacle.tsx +0 -0
- package/src/video/LengthRow.tsx +0 -0
- package/src/video/LibraryGroups.tsx +0 -0
- package/src/video/PromptAssistButtons.tsx +6 -4
- package/src/video/UnnamedWarn.tsx +0 -0
- package/src/video/composerBridge.ts +0 -0
- package/src/video/contract.ts +0 -0
- package/src/video/coordinationReport.ts +0 -0
- package/src/video/jobClock.ts +0 -0
- package/src/video/jobTracker.ts +0 -0
- package/src/video/mediaLibrary.ts +0 -0
- package/src/video/renderCompat.ts +0 -0
- package/src/video/sessionForm.ts +0 -0
- package/src/video/spreadAssist.ts +0 -0
- package/src/video/studioBridge.ts +0 -0
- package/src/video/useMediaJobs.ts +0 -0
- package/src/video/useModels.ts +0 -0
- package/src/video/useMoviePresets.ts +0 -0
- package/src/video/useMovieSessions.ts +0 -0
- package/src/video/useOperatorHistory.ts +0 -0
- package/src/video/usePresets.ts +0 -0
- package/src/video/useProjects.ts +0 -0
- package/src/video/usePromptAssist.ts +65 -23
- package/src/video/useRenderPresets.ts +0 -0
- package/src/video/useStudioAssistLog.ts +0 -0
- package/src/video/useStudioPresets.ts +0 -0
- package/src/video/useToks.ts +0 -0
- package/src/video/useWorkerCalls.ts +0 -0
- package/tsconfig.json +0 -0
- package/vite.config.ts +0 -0
- package/src/stations/studio/StudioMovieComposer.tsx.bak-declutter-20260813 +0 -3031
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -19,6 +19,7 @@ import { useState } from "react";
|
|
|
19
19
|
|
|
20
20
|
import type { StationSpec } from "../types";
|
|
21
21
|
import { DagPanel } from "./DagPanel";
|
|
22
|
+
import { PerformanceComposer } from "./PerformanceComposer";
|
|
22
23
|
import { SpatialTab } from "./SpatialTab";
|
|
23
24
|
import { StewardPanel } from "./StewardPanel";
|
|
24
25
|
import { GapNote, RawJson, StateChip, Tag, short, when } from "./oracleShared";
|
|
@@ -273,6 +274,7 @@ export function OracleStation({ spec }: { spec: StationSpec }) {
|
|
|
273
274
|
<p className="station-blurb">{spec.blurb}</p>
|
|
274
275
|
|
|
275
276
|
<StewardPanel api={steward} embedded={runs.manifest?.dag?.steward ?? null} />
|
|
277
|
+
<PerformanceComposer onEnqueued={(jobId) => runs.select(jobId)} />
|
|
276
278
|
<RunPicker runs={runs} />
|
|
277
279
|
<RunHeader runs={runs} />
|
|
278
280
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { hugpyConfig } from "../../config";
|
|
4
|
+
import { describeAppError, errorOf, okValue, request } from "../../transport/client";
|
|
5
|
+
|
|
6
|
+
type EnqueueResult = { job_id?: string; error?: string };
|
|
7
|
+
type PerformanceProbe = { ready: boolean; bound?: string[]; unbound?: Array<{ seam: string; requirement: string }> };
|
|
8
|
+
type Stage = "authority" | "snapshot" | "audio" | "lock" | "segments" | "keyframes" | "clips" | "assembly";
|
|
9
|
+
|
|
10
|
+
/** The small, explicit entry point for the Oracle's durable performance job. */
|
|
11
|
+
export function PerformanceComposer({ onEnqueued }: { onEnqueued: (jobId: string) => void }) {
|
|
12
|
+
const [prompt, setPrompt] = useState("");
|
|
13
|
+
const [dialogue, setDialogue] = useState("");
|
|
14
|
+
const [speaker, setSpeaker] = useState("narrator");
|
|
15
|
+
const [voiceId, setVoiceId] = useState("narrator");
|
|
16
|
+
const [rawRef, setRawRef] = useState("");
|
|
17
|
+
const [quality, setQuality] = useState("balanced");
|
|
18
|
+
const [stopAfter, setStopAfter] = useState<Stage | "">("segments");
|
|
19
|
+
const [tts, setTts] = useState(3);
|
|
20
|
+
const [keyframes, setKeyframes] = useState(3);
|
|
21
|
+
const [clips, setClips] = useState(3);
|
|
22
|
+
const [maxSeconds, setMaxSeconds] = useState("");
|
|
23
|
+
const [seed, setSeed] = useState(0);
|
|
24
|
+
const [negative, setNegative] = useState("");
|
|
25
|
+
const [busy, setBusy] = useState(false);
|
|
26
|
+
const [message, setMessage] = useState("");
|
|
27
|
+
const [probe, setProbe] = useState<PerformanceProbe | null>(null);
|
|
28
|
+
const [probeGap, setProbeGap] = useState("");
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
let active = true;
|
|
32
|
+
void (async () => {
|
|
33
|
+
const result = await request<PerformanceProbe>(`${hugpyConfig.apiBase}/video/performance/probe`, {
|
|
34
|
+
meta: { specKey: "oracle", operation: "oracle.performance.probe" },
|
|
35
|
+
});
|
|
36
|
+
if (!active) return;
|
|
37
|
+
if (result.ok) setProbe(okValue(result));
|
|
38
|
+
else setProbeGap(describeAppError(errorOf(result)));
|
|
39
|
+
})();
|
|
40
|
+
return () => { active = false; };
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
async function enqueue() {
|
|
44
|
+
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
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const body = {
|
|
50
|
+
goal: { objective: prompt.trim(), raw_prompt: prompt, quality, planner_mode: "local_only" },
|
|
51
|
+
dialogue: { locked: true, lines: lines.map((text, i) => ({ line_id: `line-${i + 1}`, speaker: speaker.trim(), text })) },
|
|
52
|
+
casting: [[speaker.trim(), { voice_id: voiceId.trim(), kind: "synthetic" }]],
|
|
53
|
+
raw_request_ref: rawRef.trim(),
|
|
54
|
+
tts_candidates: tts,
|
|
55
|
+
keyframe_candidates: keyframes,
|
|
56
|
+
clip_candidates: clips,
|
|
57
|
+
seed_salt: seed,
|
|
58
|
+
negative_prompt: negative.trim() || null,
|
|
59
|
+
max_seconds: maxSeconds ? Number(maxSeconds) : null,
|
|
60
|
+
stop_after: stopAfter || null,
|
|
61
|
+
};
|
|
62
|
+
setBusy(true);
|
|
63
|
+
setMessage("");
|
|
64
|
+
const result = await request<EnqueueResult>(`${hugpyConfig.apiBase}/video/jobs/performance`, {
|
|
65
|
+
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
|
|
66
|
+
meta: { specKey: "oracle", operation: "oracle.performance.enqueue" },
|
|
67
|
+
});
|
|
68
|
+
setBusy(false);
|
|
69
|
+
if (!result.ok) {
|
|
70
|
+
setMessage(describeAppError(errorOf(result)));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const jobId = okValue(result).job_id;
|
|
74
|
+
if (!jobId) {
|
|
75
|
+
setMessage("The server answered without a job id.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setMessage(`Queued ${jobId}`);
|
|
79
|
+
onEnqueued(jobId);
|
|
80
|
+
}
|
|
81
|
+
|
|
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
|
+
<div className="vi-oracle-fields">
|
|
85
|
+
<label className="vi-oracle-field vi-oracle-wide">Goal / visual direction
|
|
86
|
+
<textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} rows={3} placeholder="Describe the film, characters, setting and action" />
|
|
87
|
+
</label>
|
|
88
|
+
<label className="vi-oracle-field vi-oracle-wide">Locked dialogue · one line per utterance
|
|
89
|
+
<textarea value={dialogue} onChange={(e) => setDialogue(e.target.value)} rows={3} placeholder="The words to speak" />
|
|
90
|
+
</label>
|
|
91
|
+
<label className="vi-oracle-field">Speaker <input value={speaker} onChange={(e) => setSpeaker(e.target.value)} /></label>
|
|
92
|
+
<label className="vi-oracle-field">Synthetic voice ID <input value={voiceId} onChange={(e) => setVoiceId(e.target.value)} /></label>
|
|
93
|
+
<label className="vi-oracle-field vi-oracle-wide">Raw request reference
|
|
94
|
+
<input value={rawRef} onChange={(e) => setRawRef(e.target.value)} placeholder="A durable reference to the original request" />
|
|
95
|
+
</label>
|
|
96
|
+
<label className="vi-oracle-field">Quality
|
|
97
|
+
<select value={quality} onChange={(e) => setQuality(e.target.value)}><option value="preview">Preview</option><option value="balanced">Balanced</option><option value="best">Best</option></select>
|
|
98
|
+
</label>
|
|
99
|
+
<label className="vi-oracle-field">Stop after
|
|
100
|
+
<select value={stopAfter} onChange={(e) => setStopAfter(e.target.value as Stage | "")}>
|
|
101
|
+
<option value="">Full run</option>{(["authority", "snapshot", "audio", "lock", "segments", "keyframes", "clips", "assembly"] as Stage[]).map((s) => <option key={s} value={s}>{s}</option>)}
|
|
102
|
+
</select>
|
|
103
|
+
</label>
|
|
104
|
+
<label className="vi-oracle-field">TTS candidates <input type="number" min={1} max={16} value={tts} onChange={(e) => setTts(Number(e.target.value))} /></label>
|
|
105
|
+
<label className="vi-oracle-field">Keyframe candidates <input type="number" min={1} max={16} value={keyframes} onChange={(e) => setKeyframes(Number(e.target.value))} /></label>
|
|
106
|
+
<label className="vi-oracle-field">Clip candidates <input type="number" min={1} max={16} value={clips} onChange={(e) => setClips(Number(e.target.value))} /></label>
|
|
107
|
+
<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
|
+
<label className="vi-oracle-field">Seed salt <input type="number" min={0} value={seed} onChange={(e) => setSeed(Number(e.target.value))} /></label>
|
|
109
|
+
<label className="vi-oracle-field vi-oracle-wide">Negative prompt <input value={negative} onChange={(e) => setNegative(e.target.value)} placeholder="Optional exclusions" /></label>
|
|
110
|
+
</div>
|
|
111
|
+
<div className="vi-oracle-note" role="status">
|
|
112
|
+
{probe ? <>
|
|
113
|
+
<strong>{probe.ready ? "Full recipe wired" : "Staged runs available; full recipe has gaps"}</strong>
|
|
114
|
+
{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
|
+
</> : 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>
|
|
118
|
+
</section>;
|
|
119
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -22,6 +22,39 @@
|
|
|
22
22
|
min-width: 0;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
.vi-oracle-fields {
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
|
28
|
+
gap: 0.75rem;
|
|
29
|
+
margin-bottom: 0.75rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.vi-oracle-field {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: 0.3rem;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
color: var(--vi-text-dim);
|
|
38
|
+
font-size: 0.76rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.vi-oracle-wide { grid-column: 1 / -1; }
|
|
42
|
+
|
|
43
|
+
.vi-oracle-field input,
|
|
44
|
+
.vi-oracle-field textarea,
|
|
45
|
+
.vi-oracle-field select {
|
|
46
|
+
width: 100%;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
padding: 0.48rem 0.58rem;
|
|
49
|
+
border: 1px solid var(--vi-border);
|
|
50
|
+
border-radius: 6px;
|
|
51
|
+
background: var(--vi-surface);
|
|
52
|
+
color: var(--vi-text);
|
|
53
|
+
font: inherit;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.vi-oracle-field textarea { resize: vertical; }
|
|
57
|
+
|
|
25
58
|
.vi-oracle-panel > h3 {
|
|
26
59
|
margin: 0 0 0.6rem;
|
|
27
60
|
font-family: var(--vi-mono);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/stations/pollCaps.ts
CHANGED
|
File without changes
|
package/src/stations/registry.ts
CHANGED
|
File without changes
|
|
@@ -348,3 +348,26 @@
|
|
|
348
348
|
grid-template-columns: 1fr;
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
|
+
|
|
352
|
+
/* ---- Template steps panel (TemplateStepsPanel.tsx) ---- */
|
|
353
|
+
.vi-template-steps {
|
|
354
|
+
margin: 0.5rem 0 0;
|
|
355
|
+
padding-left: 1.5rem;
|
|
356
|
+
display: flex;
|
|
357
|
+
flex-direction: column;
|
|
358
|
+
gap: 0.6rem;
|
|
359
|
+
}
|
|
360
|
+
.vi-template-step-head {
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: 0.6rem;
|
|
364
|
+
flex-wrap: wrap;
|
|
365
|
+
}
|
|
366
|
+
.vi-template-step-head > select {
|
|
367
|
+
max-width: 24rem;
|
|
368
|
+
}
|
|
369
|
+
.vi-template-step-desc {
|
|
370
|
+
margin: 0.15rem 0 0;
|
|
371
|
+
font-size: 0.85em;
|
|
372
|
+
opacity: 0.75;
|
|
373
|
+
}
|
|
File without changes
|
|
@@ -34,7 +34,7 @@ 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 { mediaBytesUrl, identityGenerateUrl, identityMeshStatusUrl, videoJobUrl } from "../../config";
|
|
37
|
+
import { hugpyConfig, mediaBytesUrl, identityGenerateUrl, identityMeshStatusUrl, videoJobUrl } from "../../config";
|
|
38
38
|
import { useMediaLibrary } from "../../video/mediaLibrary";
|
|
39
39
|
import type { LibraryItem } from "../../video/mediaLibrary";
|
|
40
40
|
import type { MediaRef, IdentityReconstruction, IdentityAngleView } from "../../video/contract";
|
|
@@ -2149,6 +2149,17 @@ type IdTab = "active" | "session" | "settings";
|
|
|
2149
2149
|
|
|
2150
2150
|
export function IdentitiesStation({ spec }: { spec: StationSpec }) {
|
|
2151
2151
|
void spec;
|
|
2152
|
+
const [serviceProbe, setServiceProbe] = useState<{ configured: boolean; reachable: boolean; reason?: string; version?: string; capabilities?: Record<string, unknown> } | null>(null);
|
|
2153
|
+
useEffect(() => {
|
|
2154
|
+
let active = true;
|
|
2155
|
+
void (async () => {
|
|
2156
|
+
const result = await request<typeof serviceProbe>(`${hugpyConfig.apiBase}/video/identity-render/probe`, {
|
|
2157
|
+
meta: { specKey: "identities", operation: "identity.render.probe" },
|
|
2158
|
+
});
|
|
2159
|
+
if (active && result.ok) setServiceProbe(okValue(result));
|
|
2160
|
+
})();
|
|
2161
|
+
return () => { active = false; };
|
|
2162
|
+
}, []);
|
|
2152
2163
|
const {
|
|
2153
2164
|
profiles,
|
|
2154
2165
|
loading,
|
|
@@ -2225,6 +2236,14 @@ export function IdentitiesStation({ spec }: { spec: StationSpec }) {
|
|
|
2225
2236
|
return (
|
|
2226
2237
|
<section className="station-wide vi-identities vi-identities-md" aria-label="Identities workspace"
|
|
2227
2238
|
style={{ display: "grid", gridTemplateColumns: "300px minmax(0, 1fr)", gap: "1rem", alignItems: "start" }}>
|
|
2239
|
+
<div role="status" style={{ gridColumn: "1 / -1", padding: "0.55rem 0.75rem", border: "1px solid var(--vi-border)", borderRadius: "0.5rem", fontSize: "0.8rem" }}>
|
|
2240
|
+
<strong>Identity render service:</strong>{" "}
|
|
2241
|
+
{!serviceProbe ? "checking…" : serviceProbe.reachable
|
|
2242
|
+
? `online${serviceProbe.version ? ` · ${serviceProbe.version}` : ""}`
|
|
2243
|
+
: serviceProbe.reason || "unavailable"}
|
|
2244
|
+
{serviceProbe?.reachable && serviceProbe.capabilities ?
|
|
2245
|
+
<span> · {Object.entries(serviceProbe.capabilities).filter(([, value]) => Boolean(value)).map(([name]) => name).join(", ")}</span> : null}
|
|
2246
|
+
</div>
|
|
2228
2247
|
{/* ── SIDEBAR: tab filter + master list (wireframe b/c/d/f/e) ────────── */}
|
|
2229
2248
|
<aside className="vi-id-sidebar" aria-label="Identity list"
|
|
2230
2249
|
style={{ border: "1px solid var(--vi-border)", borderRadius: "0.5rem", padding: "0.5rem", position: "sticky", top: "0.5rem" }}>
|
|
@@ -2379,4 +2398,4 @@ export function IdentitiesStation({ spec }: { spec: StationSpec }) {
|
|
|
2379
2398
|
</div>
|
|
2380
2399
|
</section>
|
|
2381
2400
|
);
|
|
2382
|
-
}
|
|
2401
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -416,6 +416,11 @@ export function StudioGenerateSurface({
|
|
|
416
416
|
height?: number;
|
|
417
417
|
fps?: number;
|
|
418
418
|
seed?: number;
|
|
419
|
+
startImage?: string;
|
|
420
|
+
steps?: number;
|
|
421
|
+
cfg?: number;
|
|
422
|
+
requestedFrames?: number;
|
|
423
|
+
negative?: string;
|
|
419
424
|
}) => {
|
|
420
425
|
const p = prompt.trim();
|
|
421
426
|
if (!p) {
|
|
@@ -430,6 +435,11 @@ export function StudioGenerateSurface({
|
|
|
430
435
|
if (opts.height != null) body.height = opts.height;
|
|
431
436
|
if (opts.fps != null) body.fps = opts.fps;
|
|
432
437
|
if (opts.seed != null) body.seed = opts.seed;
|
|
438
|
+
if (opts.startImage) body.start_image = opts.startImage;
|
|
439
|
+
if (opts.steps != null) body.steps = opts.steps;
|
|
440
|
+
if (opts.cfg != null) body.cfg = opts.cfg;
|
|
441
|
+
if (opts.requestedFrames != null) body.requested_frames = opts.requestedFrames;
|
|
442
|
+
if (opts.negative) body.negative = opts.negative;
|
|
433
443
|
if (opts.models.length > 0) body.models = opts.models;
|
|
434
444
|
const res = await request<unknown>(hugpyConfig.studioTesterUrl, {
|
|
435
445
|
method: "POST",
|
|
@@ -2813,10 +2823,13 @@ export function StudioGenerateSurface({
|
|
|
2813
2823
|
</div>
|
|
2814
2824
|
{clipTesterOpen && (
|
|
2815
2825
|
<div className="vi-gen-tester-actions">
|
|
2826
|
+
{!isT2V && !(isI2V && hasImage) && (
|
|
2827
|
+
<span className="vi-knob-hint" role="note">The current sweep supports text-to-video and image-to-video with a start image. Choose one of those shapes to run it.</span>
|
|
2828
|
+
)}
|
|
2816
2829
|
<button
|
|
2817
2830
|
type="button"
|
|
2818
2831
|
className="vi-btn vi-btn-sm vi-btn-tester"
|
|
2819
|
-
disabled={testerBusy || !prompt.trim()}
|
|
2832
|
+
disabled={testerBusy || !prompt.trim() || (!isT2V && !(isI2V && hasImage))}
|
|
2820
2833
|
onClick={() =>
|
|
2821
2834
|
void onTestAllModels({
|
|
2822
2835
|
category: surfaceMode,
|
|
@@ -2825,9 +2838,14 @@ export function StudioGenerateSurface({
|
|
|
2825
2838
|
height,
|
|
2826
2839
|
fps,
|
|
2827
2840
|
seed,
|
|
2841
|
+
startImage: isI2V ? startImages[0]?.uri : undefined,
|
|
2842
|
+
steps: steps.trim() ? Number(steps) : undefined,
|
|
2843
|
+
cfg: cfg.trim() ? Number(cfg) : undefined,
|
|
2844
|
+
requestedFrames: requestedFrames.trim() ? Number(requestedFrames) : undefined,
|
|
2845
|
+
negative: negative.trim() || undefined,
|
|
2828
2846
|
})
|
|
2829
2847
|
}
|
|
2830
|
-
title=
|
|
2848
|
+
title={!isT2V && !(isI2V && hasImage) ? "This sweep currently supports text-to-video or image-to-video with a start image." : "Run this prompt across each model that fits the current filter; one battery row per model."}
|
|
2831
2849
|
>
|
|
2832
2850
|
{testerBusy ? "Starting…" : "🧪 Test all models"}
|
|
2833
2851
|
</button>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -326,7 +326,7 @@ function SessionRowBody({
|
|
|
326
326
|
|
|
327
327
|
/** The panel body — split out so the poll it drives exists ONLY while the panel is
|
|
328
328
|
* open (mounting is the visibility signal; see the `active` gate below). */
|
|
329
|
-
function SessionsBody() {
|
|
329
|
+
export function SessionsBody() {
|
|
330
330
|
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
331
331
|
// A hidden browser tab is not a visible panel: pause the walk of the movies root
|
|
332
332
|
// while the console is in the background, and reload the moment it comes back.
|
|
@@ -419,7 +419,7 @@ const SESSION_CATEGORIES: ReadonlyArray<{
|
|
|
419
419
|
id: "cinema",
|
|
420
420
|
label: "Cinema",
|
|
421
421
|
kinds: ["generate_studio_movie"],
|
|
422
|
-
hint: "movies
|
|
422
|
+
hint: "cinema movies",
|
|
423
423
|
},
|
|
424
424
|
];
|
|
425
425
|
|
|
@@ -450,7 +450,6 @@ function CategoryJobsBody({
|
|
|
450
450
|
<p className="vi-knob-hint">
|
|
451
451
|
No {label.toLowerCase()} builds in the feed — live ones appear here with
|
|
452
452
|
progress and Cancel; tick “Show finished” above for recent history.
|
|
453
|
-
(Durable resumable sessions exist for Cinema today.)
|
|
454
453
|
</p>
|
|
455
454
|
);
|
|
456
455
|
}
|
|
@@ -491,7 +490,7 @@ export function SessionsPanel({
|
|
|
491
490
|
cancelBusy: Set<string>;
|
|
492
491
|
onCancel: (id: string) => void;
|
|
493
492
|
}) {
|
|
494
|
-
const [open, setOpen] = useState<Set<string>>(() => new Set(
|
|
493
|
+
const [open, setOpen] = useState<Set<string>>(() => new Set());
|
|
495
494
|
const toggle = (id: string) =>
|
|
496
495
|
setOpen((prev) => {
|
|
497
496
|
const next = new Set(prev);
|
|
@@ -504,7 +503,7 @@ export function SessionsPanel({
|
|
|
504
503
|
<p className="vi-knob-headline" style={{ margin: "0.8rem 0 0.2rem" }}>
|
|
505
504
|
Sessions{" "}
|
|
506
505
|
<span className="vi-knob-headline-sub">
|
|
507
|
-
previous & in-flight work, per surface
|
|
506
|
+
previous & in-flight work, per surface
|
|
508
507
|
</span>
|
|
509
508
|
</p>
|
|
510
509
|
{SESSION_CATEGORIES.map((cat) => {
|
|
@@ -526,24 +525,18 @@ export function SessionsPanel({
|
|
|
526
525
|
{cat.label}{" "}
|
|
527
526
|
<span className="vi-knob-headline-sub">
|
|
528
527
|
{cat.hint}
|
|
529
|
-
{
|
|
530
|
-
? ` · ${catJobs.length} in feed`
|
|
531
|
-
: ""}
|
|
528
|
+
{catJobs.length > 0 ? ` · ${catJobs.length} in feed` : ""}
|
|
532
529
|
</span>
|
|
533
530
|
</span>
|
|
534
531
|
</button>
|
|
535
532
|
{isOpen ? (
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
onCancel={onCancel}
|
|
544
|
-
label={cat.label}
|
|
545
|
-
/>
|
|
546
|
-
)
|
|
533
|
+
<CategoryJobsBody
|
|
534
|
+
jobs={catJobs}
|
|
535
|
+
nowMs={nowMs}
|
|
536
|
+
cancelBusy={cancelBusy}
|
|
537
|
+
onCancel={onCancel}
|
|
538
|
+
label={cat.label}
|
|
539
|
+
/>
|
|
547
540
|
) : null}
|
|
548
541
|
</div>
|
|
549
542
|
);
|
|
@@ -111,6 +111,9 @@ export function StudioPlane({ registerBridge = false, lockedSurfaceMode }: Studi
|
|
|
111
111
|
const [pinned, setPinned] = useState(false);
|
|
112
112
|
|
|
113
113
|
const topRef = useRef<HTMLDivElement | null>(null);
|
|
114
|
+
const monitorRef = useRef<HTMLDivElement | null>(null);
|
|
115
|
+
const binRef = useRef<HTMLDivElement | null>(null);
|
|
116
|
+
const renderRef = useRef<HTMLDivElement | null>(null);
|
|
114
117
|
|
|
115
118
|
// Round 9: fold into the ONE shell sidebar — reveal its guarded Settings tab (knobs +
|
|
116
119
|
// template dropdown portal there). registerSettings is stable, so this runs once per
|
|
@@ -275,10 +278,24 @@ export function StudioPlane({ registerBridge = false, lockedSurfaceMode }: Studi
|
|
|
275
278
|
|
|
276
279
|
return (
|
|
277
280
|
<div className="vi-studio-workbench" ref={topRef}>
|
|
281
|
+
<nav className="vi-studio-workflow" aria-label="Studio workflow">
|
|
282
|
+
<button type="button" onClick={() => monitorRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}>
|
|
283
|
+
<span className="vi-studio-workflow-step">01 · Monitor</span>
|
|
284
|
+
<span>Review the current take</span>
|
|
285
|
+
</button>
|
|
286
|
+
<button type="button" onClick={() => binRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}>
|
|
287
|
+
<span className="vi-studio-workflow-step">02 · Shot bin</span>
|
|
288
|
+
<span>{items.length} staged {items.length === 1 ? "item" : "items"}</span>
|
|
289
|
+
</button>
|
|
290
|
+
<button type="button" onClick={() => renderRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}>
|
|
291
|
+
<span className="vi-studio-workflow-step">03 · Render</span>
|
|
292
|
+
<span>Clip or cinema</span>
|
|
293
|
+
</button>
|
|
294
|
+
</nav>
|
|
278
295
|
{/* CENTER, movie skeleton: viewer on top → generator below. Viewer header controls
|
|
279
296
|
(Play from library / Clear / follow-newest toggle) are wired here so both
|
|
280
297
|
plane mounts get them for free — see the `pinned` note above. */}
|
|
281
|
-
<StudioViewer
|
|
298
|
+
<div ref={monitorRef} className="vi-studio-workflow-target"><StudioViewer
|
|
282
299
|
clips={clipsState.clips}
|
|
283
300
|
library={libraryMedia}
|
|
284
301
|
selected={selected}
|
|
@@ -286,7 +303,7 @@ export function StudioPlane({ registerBridge = false, lockedSurfaceMode }: Studi
|
|
|
286
303
|
onPick={pickViewerClip}
|
|
287
304
|
onClear={clearViewer}
|
|
288
305
|
onToggleFollow={toggleFollowNewest}
|
|
289
|
-
|
|
306
|
+
/></div>
|
|
290
307
|
|
|
291
308
|
{/* Slice 2b: the staged studio items as a strip of first-class cards with inline
|
|
292
309
|
per-item ops. Additive — it sits ABOVE the generate surface and does not touch
|
|
@@ -294,7 +311,8 @@ export function StudioPlane({ registerBridge = false, lockedSurfaceMode }: Studi
|
|
|
294
311
|
mounts identically (this is the one shared StudioPlane). Each card is wrapped in
|
|
295
312
|
a keyed intrinsic <div> so the map key never lands on a component (which the
|
|
296
313
|
bare-tsc env flags as a spurious prop). */}
|
|
297
|
-
{
|
|
314
|
+
<div ref={binRef} className="vi-studio-workflow-target" aria-label="Shot bin">
|
|
315
|
+
{items.length > 0 ? (
|
|
298
316
|
<div className="vi-studio-item-strip" aria-label="Studio items">
|
|
299
317
|
{items.map((it) => (
|
|
300
318
|
<div className="vi-studio-item-cell" key={it.key}>
|
|
@@ -308,8 +326,10 @@ export function StudioPlane({ registerBridge = false, lockedSurfaceMode }: Studi
|
|
|
308
326
|
</div>
|
|
309
327
|
))}
|
|
310
328
|
</div>
|
|
311
|
-
)}
|
|
329
|
+
) : <p className="vi-studio-bin-empty">Staged media will appear here. Send a library item to Studio or add a source in the render desk.</p>}
|
|
330
|
+
</div>
|
|
312
331
|
|
|
332
|
+
<div ref={renderRef} className="vi-studio-workflow-target">
|
|
313
333
|
<StudioGenerateSurface
|
|
314
334
|
presets={presets}
|
|
315
335
|
presetsLoading={presetsLoading}
|
|
@@ -322,6 +342,7 @@ export function StudioPlane({ registerBridge = false, lockedSurfaceMode }: Studi
|
|
|
322
342
|
settingsHost={reg.settingsHost}
|
|
323
343
|
lockedSurfaceMode={lockedSurfaceMode}
|
|
324
344
|
/>
|
|
345
|
+
</div>
|
|
325
346
|
{/* STUDIO-ASSIST LIVE LOG → portaled into the sidebar's ACTIVE (Active
|
|
326
347
|
Processes) tab via the registry's activeExtra host (operator correction
|
|
327
348
|
2026-08-05: "active tab" = Active Processes). Single mount, shared stream. */}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/stations/types.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/style/app.css
CHANGED
|
@@ -3406,6 +3406,55 @@ button.vi-studio-src-thumb {
|
|
|
3406
3406
|
min-width: 0;
|
|
3407
3407
|
}
|
|
3408
3408
|
|
|
3409
|
+
.vi-studio-workflow {
|
|
3410
|
+
display: grid;
|
|
3411
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
3412
|
+
gap: 0.5rem;
|
|
3413
|
+
border-bottom: 1px solid var(--vi-border);
|
|
3414
|
+
padding-bottom: 0.7rem;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
.vi-studio-workflow button {
|
|
3418
|
+
display: flex;
|
|
3419
|
+
flex-direction: column;
|
|
3420
|
+
gap: 0.25rem;
|
|
3421
|
+
text-align: left;
|
|
3422
|
+
min-width: 0;
|
|
3423
|
+
padding: 0.65rem 0.75rem;
|
|
3424
|
+
border: 1px solid var(--vi-border);
|
|
3425
|
+
border-radius: 8px;
|
|
3426
|
+
background: var(--vi-surface-raised);
|
|
3427
|
+
color: var(--vi-text-dim);
|
|
3428
|
+
cursor: pointer;
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
.vi-studio-workflow button:hover,
|
|
3432
|
+
.vi-studio-workflow button:focus-visible {
|
|
3433
|
+
border-color: var(--vi-accent);
|
|
3434
|
+
color: var(--vi-text);
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
.vi-studio-workflow-step {
|
|
3438
|
+
color: var(--vi-text);
|
|
3439
|
+
font-family: var(--vi-mono);
|
|
3440
|
+
font-size: 0.76rem;
|
|
3441
|
+
font-weight: 600;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
.vi-studio-workflow-target { scroll-margin-top: 1rem; min-width: 0; }
|
|
3445
|
+
.vi-studio-bin-empty {
|
|
3446
|
+
margin: 0;
|
|
3447
|
+
padding: 0.7rem 0.85rem;
|
|
3448
|
+
border: 1px dashed var(--vi-border);
|
|
3449
|
+
border-radius: 8px;
|
|
3450
|
+
color: var(--vi-text-dim);
|
|
3451
|
+
font-size: 0.78rem;
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
@media (max-width: 35rem) {
|
|
3455
|
+
.vi-studio-workflow { grid-template-columns: 1fr; }
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3409
3458
|
/* Round 9: the studio no longer carries its OWN sidebar — it folds into the arm shell's
|
|
3410
3459
|
ONE left sidebar (Settings / Active / Library, via the sidebar registry). So the
|
|
3411
3460
|
workbench is a SINGLE centre column (viewer on top → generator below), the Movie
|
package/src/transport/client.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/video/LengthRow.tsx
CHANGED
|
File without changes
|
|
File without changes
|