@hugpy/video-intelligence-ui 0.1.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.
- package/LICENSE +41 -0
- package/assets/sampels/images/bycycle/bd8603bad35d4f179f4d1134d8faf2ee.mp4 +0 -0
- package/assets/sampels/images/bycycle/frame_00000.png +0 -0
- package/assets/sampels/images/bycycle/frame_00001.png +0 -0
- package/assets/sampels/images/bycycle/frame_00002.png +0 -0
- package/assets/sampels/images/bycycle/frame_00003.png +0 -0
- package/assets/sampels/images/bycycle/frame_00004.png +0 -0
- package/assets/sampels/images/bycycle/frame_00005.png +0 -0
- package/assets/sampels/images/forrest/ff23090611b844a6b85c224f388bd542.mp4 +0 -0
- package/assets/sampels/images/forrest/frame_00000.png +0 -0
- package/assets/sampels/images/forrest/frame_00001.png +0 -0
- package/assets/sampels/images/forrest/frame_00002.png +0 -0
- package/assets/sampels/images/forrest/frame_00003.png +0 -0
- package/assets/sampels/images/forrest/frame_00004.png +0 -0
- package/assets/sampels/images/forrest/frame_00005.png +0 -0
- package/assets/sampels/images/shenzo/e1a4fcd623084ab4a986d11c8cd00db8.mp4 +0 -0
- package/assets/sampels/images/shenzo/frame_00000.png +0 -0
- package/assets/sampels/images/shenzo/frame_00001.png +0 -0
- package/assets/sampels/images/shenzo/frame_00002.png +0 -0
- package/assets/sampels/images/shenzo/frame_00003.png +0 -0
- package/assets/sampels/images/shenzo/frame_00004.png +0 -0
- package/assets/sampels/images/shenzo/frame_00005.png +0 -0
- package/assets/sampels/scenes/man.mp4 +0 -0
- package/assets/sampels/scenes/woman.mp4 +0 -0
- package/assets/sampels/studio_movies/caverns/_assembly/contrib_00.mp4 +0 -0
- package/assets/sampels/studio_movies/caverns/movie.json +67 -0
- package/assets/sampels/studio_movies/caverns/movie.mp4 +0 -0
- package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/clip.mp4 +0 -0
- package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/manifest.json +87 -0
- package/assets/sampels/studio_movies/caverns/segment_00/5eea7770a554fef0c3bdc38f8f38b925dcf75f13c015d423e8eadec47d41bf6b/provenance.json +7 -0
- package/assets/sampels/studio_movies/caverns/segment_01/branch.png +0 -0
- package/assets/sampels/studio_movies/keeper-matrix/_assembly/concat_list.txt +2 -0
- package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_00.mp4 +0 -0
- package/assets/sampels/studio_movies/keeper-matrix/_assembly/contrib_01.mp4 +0 -0
- package/assets/sampels/studio_movies/keeper-matrix/movie.json +81 -0
- package/assets/sampels/studio_movies/keeper-matrix/movie.mp4 +0 -0
- package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/clip.mp4 +0 -0
- package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/manifest.json +87 -0
- package/assets/sampels/studio_movies/keeper-matrix/segment_00/64854c6a21d47ce10b23e884e12de448b25bfb0a375d33238dd0bec8056c076d/provenance.json +7 -0
- package/assets/sampels/studio_movies/keeper-matrix/segment_01/branch.png +0 -0
- package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/clip.mp4 +0 -0
- package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/manifest.json +87 -0
- package/assets/sampels/studio_movies/keeper-matrix/segment_01/cc926b44a42b90d7d291a4e50e3859458306c207a17c718086a3466e4049d6c6/provenance.json +7 -0
- package/assets/sampels/videos/a_red_paper_boat_drifting_on_a_calm_pond__gentle_ripples__mo_a62d6c28.mp4 +0 -0
- package/assets/sampels/videos/aurora.mp4 +0 -0
- package/assets/sampels/videos/dark_night.mp4 +0 -0
- package/assets/sampels/videos/fire_and_soul.mp4 +0 -0
- package/assets/sampels/videos/harsh_seas.mp4 +0 -0
- package/entry.tsx +74 -0
- package/index.html +53 -0
- package/package.json +42 -0
- package/src/assets/hugpy-mark.png +0 -0
- package/src/config.ts +676 -0
- package/src/demo/DemoBanner.tsx +54 -0
- package/src/demo/assets/demo-audio.mp3 +0 -0
- package/src/demo/assets/demo-clip.mp4 +0 -0
- package/src/demo/assets/demo-crop.mp4 +0 -0
- package/src/demo/assets/demo-poster.jpg +0 -0
- package/src/demo/assets/frame-1.jpg +0 -0
- package/src/demo/assets/frame-2.jpg +0 -0
- package/src/demo/assets/frame-3.jpg +0 -0
- package/src/demo/assets/frame-4.jpg +0 -0
- package/src/demo/assets/frame-5.jpg +0 -0
- package/src/demo/assets/frame-6.jpg +0 -0
- package/src/demo/assets/gen-1.jpg +0 -0
- package/src/demo/assets/gen-2.jpg +0 -0
- package/src/demo/assets.d.ts +14 -0
- package/src/demo/demo.css +64 -0
- package/src/demo/demoFetch.ts +305 -0
- package/src/demo/fixtures.ts +814 -0
- package/src/demo/index.ts +43 -0
- package/src/demo/mode.ts +81 -0
- package/src/demo/promptAssist.ts +181 -0
- package/src/demo/seed.ts +97 -0
- package/src/nav/BrandMark.css +66 -0
- package/src/nav/BrandMark.tsx +40 -0
- package/src/nav/Navbar.css +13 -0
- package/src/nav/Navbar.tsx +68 -0
- package/src/regions/SpatialRegionEditor.tsx +375 -0
- package/src/regions/TemporalRegionEditor.tsx +485 -0
- package/src/regions/types.ts +85 -0
- package/src/session.ts +85 -0
- package/src/share.ts +77 -0
- package/src/stations/ActivePanel.tsx +55 -0
- package/src/stations/ActiveProcessesStation.tsx +272 -0
- package/src/stations/ActiveProcessesStation.tsx.bak-sessions-20260812 +228 -0
- package/src/stations/AddFileBar.tsx +59 -0
- package/src/stations/AudioCropStation.tsx +157 -0
- package/src/stations/ComfyPanel.tsx +210 -0
- package/src/stations/ComfyStation.tsx +196 -0
- package/src/stations/CondensedKnobStrip.tsx +245 -0
- package/src/stations/ConsolePanel.tsx +82 -0
- package/src/stations/FrameExtractStation.tsx +148 -0
- package/src/stations/GenIdentityBar.tsx +412 -0
- package/src/stations/GenerateStation.tsx +5603 -0
- package/src/stations/GenerateStation.tsx.bak-tester-removal +5170 -0
- package/src/stations/GoalComposer.tsx +139 -0
- package/src/stations/ImageCropStation.tsx +153 -0
- package/src/stations/KnobInfo.tsx +64 -0
- package/src/stations/PlaceholderStation.tsx +26 -0
- package/src/stations/SectionTabs.tsx +276 -0
- package/src/stations/SharePanel.tsx +298 -0
- package/src/stations/SidebarRegistry.tsx +105 -0
- package/src/stations/StationShell.tsx +96 -0
- package/src/stations/StudioClipsStation.tsx +28 -0
- package/src/stations/WorkbenchDrawer.tsx +66 -0
- package/src/stations/WorkbenchSidebar.tsx +1224 -0
- package/src/stations/WorkbenchStation.tsx +91 -0
- package/src/stations/comfyEmbed.ts +79 -0
- package/src/stations/drawerShell.tsx +419 -0
- package/src/stations/ops/AudioCropCore.tsx +218 -0
- package/src/stations/ops/CharacterGroupsPanel.tsx +716 -0
- package/src/stations/ops/FrameExtractCore.tsx +695 -0
- package/src/stations/ops/ImageCropCore.tsx +164 -0
- package/src/stations/ops/VideoCropCore.tsx +193 -0
- package/src/stations/ops/useVideoCropJobs.ts +224 -0
- package/src/stations/pollCaps.ts +87 -0
- package/src/stations/registry.ts +191 -0
- package/src/stations/studio/AngleRingEditor.tsx +159 -0
- package/src/stations/studio/IdentitiesStation.tsx +2455 -0
- package/src/stations/studio/IdentityProfileControls.tsx +262 -0
- package/src/stations/studio/StudioActiveProcesses.tsx +224 -0
- package/src/stations/studio/StudioAssistLogPanel.tsx +220 -0
- package/src/stations/studio/StudioGenerateMode.tsx +29 -0
- package/src/stations/studio/StudioGenerateTab.tsx +2688 -0
- package/src/stations/studio/StudioItemCard.tsx +334 -0
- package/src/stations/studio/StudioLibraryTab.tsx +506 -0
- package/src/stations/studio/StudioMovieComposer.tsx +2875 -0
- package/src/stations/studio/StudioMovieComposer.tsx.bak-sessions-20260812 +2729 -0
- package/src/stations/studio/StudioMovieSessions.tsx +553 -0
- package/src/stations/studio/StudioMovieSessions.tsx.bak-sessions-20260812 +417 -0
- package/src/stations/studio/StudioPlane.tsx +302 -0
- package/src/stations/studio/StudioSplicedRow.tsx +476 -0
- package/src/stations/studio/StudioViewer.tsx +339 -0
- package/src/stations/studio/TurntableViewer.tsx +220 -0
- package/src/stations/studio/movieTimeline.ts +198 -0
- package/src/stations/studio/promptCard.tsx +498 -0
- package/src/stations/studio/studioItem.ts +87 -0
- package/src/stations/studio/studioShared.tsx +821 -0
- package/src/stations/studio/useIdentityProfiles.ts +575 -0
- package/src/stations/studio/useReconstruction.ts +283 -0
- package/src/stations/types.ts +43 -0
- package/src/stations/useAudioCropJobs.ts +202 -0
- package/src/stations/useAudioExtractJob.ts +144 -0
- package/src/stations/useCropJobs.ts +206 -0
- package/src/stations/useFrameJobs.ts +140 -0
- package/src/stations/useGenerateJob.ts +151 -0
- package/src/stations/useGenerateMovieJob.ts +177 -0
- package/src/stations/useGenerateRowJobs.ts +184 -0
- package/src/stations/useGenerateSceneJob.ts +156 -0
- package/src/stations/useGenerateSceneRowJobs.ts +282 -0
- package/src/style/app.css +4974 -0
- package/src/transport/client.ts +262 -0
- package/src/transport/telemetry.ts +97 -0
- package/src/video/DropReceptacle.tsx +122 -0
- package/src/video/PromptAssistButtons.tsx +118 -0
- package/src/video/composerBridge.ts +66 -0
- package/src/video/contract.ts +599 -0
- package/src/video/jobTracker.ts +702 -0
- package/src/video/mediaLibrary.ts +328 -0
- package/src/video/renderCompat.ts +512 -0
- package/src/video/sessionForm.ts +189 -0
- package/src/video/spreadAssist.ts +392 -0
- package/src/video/studioBridge.ts +82 -0
- package/src/video/useMediaJobs.ts +279 -0
- package/src/video/useModels.ts +413 -0
- package/src/video/useMoviePresets.ts +97 -0
- package/src/video/useMovieSessions.ts +292 -0
- package/src/video/usePresets.ts +167 -0
- package/src/video/useProjects.ts +87 -0
- package/src/video/usePromptAssist.ts +394 -0
- package/src/video/useRenderPresets.ts +221 -0
- package/src/video/useStudioAssistLog.ts +266 -0
- package/src/video/useStudioPresets.ts +128 -0
- package/src/video/useWorkerCalls.ts +165 -0
- package/tsconfig.json +17 -0
- package/vite.config.ts +33 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
// Single source of truth for hugpy service URLs (video-intelligence arm).
|
|
2
|
+
//
|
|
3
|
+
// Cloned from media_intelligence_ui/src/config.ts. Demo layer: CANNED only
|
|
4
|
+
// (?demo=1 — see demo/mode.ts; deliberately no live flavor, since video jobs
|
|
5
|
+
// drive real worker GPUs). Same conventions: one VITE_* base var defaulting to
|
|
6
|
+
// the SAME-ORIGIN current-hugpy API (/api), derived URLs with per-URL
|
|
7
|
+
// overrides, and this being the ONE module permitted to hold URL literals.
|
|
8
|
+
|
|
9
|
+
import { isCanned } from "./demo/mode";
|
|
10
|
+
import { withShareParam } from "./share";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_API_BASE = "/api";
|
|
13
|
+
|
|
14
|
+
// Read import.meta.env without depending on vite/client ambient types (the scoped
|
|
15
|
+
// strict tsconfig doesn't include them).
|
|
16
|
+
const env: Record<string, string | undefined> =
|
|
17
|
+
(import.meta as unknown as { env?: Record<string, string | undefined> }).env ??
|
|
18
|
+
{};
|
|
19
|
+
|
|
20
|
+
function read(key: string): string | undefined {
|
|
21
|
+
const v = env[key];
|
|
22
|
+
return v && v.trim() ? v.trim() : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function stripTrailingSlash(url: string): string {
|
|
26
|
+
return url.replace(/\/+$/, "");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readBool(key: string, dflt: boolean): boolean {
|
|
30
|
+
const v = read(key);
|
|
31
|
+
if (v == null) return dflt;
|
|
32
|
+
return !/^(0|false|no|off)$/i.test(v);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const apiBase = stripTrailingSlash(read("VITE_HUGPY_API_BASE") ?? DEFAULT_API_BASE);
|
|
36
|
+
|
|
37
|
+
export const hugpyConfig = {
|
|
38
|
+
/** Base host for all hugpy API calls. */
|
|
39
|
+
apiBase,
|
|
40
|
+
/** Public hugpy site — CTA target. Override with VITE_HUGPY_SITE_URL. */
|
|
41
|
+
siteUrl: stripTrailingSlash(read("VITE_HUGPY_SITE_URL") ?? "https://hugpy.ai"),
|
|
42
|
+
/** Multipart file upload endpoint. */
|
|
43
|
+
uploadUrl: read("VITE_HUGPY_UPLOAD_URL") ?? `${apiBase}/uploads`,
|
|
44
|
+
/**
|
|
45
|
+
* Resolve an uploaded server path into a typed MediaRef (native dims, mime,
|
|
46
|
+
* kind). Phase 3 — the first hop after upload for every media station.
|
|
47
|
+
*/
|
|
48
|
+
videoIngestUrl: read("VITE_HUGPY_VIDEO_INGEST_URL") ?? `${apiBase}/video/ingest`,
|
|
49
|
+
/**
|
|
50
|
+
* char360 S4 — VIDEO → CHARACTER VIEW-SET extraction (Identities station,
|
|
51
|
+
* "Extract from video"): POST { source:<MediaRef, kind:"video">,
|
|
52
|
+
* target:"create"|"<slug>", char360_params?:{stride?,yolo_model?,min_h_frac?,
|
|
53
|
+
* cluster_dist?,min_faces?} } → 200 { job_id, target }. Validation errors are a
|
|
54
|
+
* flat 400 {error}; an unknown add-target slug is 404 {error}. The job itself
|
|
55
|
+
* is polled on the generic `videoJobUrl`/`jobStatusUrl` (GET /video/jobs/<id>),
|
|
56
|
+
* NOT a bespoke status route. Declared here so the extract control is a
|
|
57
|
+
* pure-additive flip that never re-touches this URL-literal module.
|
|
58
|
+
*/
|
|
59
|
+
videoExtractUrl:
|
|
60
|
+
read("VITE_HUGPY_VIDEO_EXTRACT_URL") ?? `${apiBase}/video/identity-profiles/video-extract`,
|
|
61
|
+
/** Enqueue a crop job (spatial and/or temporal axis) on the job bus. */
|
|
62
|
+
cropEnqueueUrl: read("VITE_HUGPY_CROP_ENQUEUE_URL") ?? `${apiBase}/video/jobs/crop`,
|
|
63
|
+
/** Enqueue a frame-extract job (Frames & Models station) — one job → many frames. */
|
|
64
|
+
frameExtractEnqueueUrl:
|
|
65
|
+
read("VITE_HUGPY_FRAME_EXTRACT_URL") ?? `${apiBase}/video/jobs/frame_extract`,
|
|
66
|
+
/**
|
|
67
|
+
* Enqueue an audio-extract job (Audio Crop station, Phase 5) — pull a video's
|
|
68
|
+
* audio track into ONE standalone audio MediaRef the temporal editor edits over.
|
|
69
|
+
* Mirrors frameExtractEnqueueUrl: declared here so the station build is a
|
|
70
|
+
* pure-additive registry flip that never re-touches this URL-literal module.
|
|
71
|
+
*/
|
|
72
|
+
audioExtractEnqueueUrl:
|
|
73
|
+
read("VITE_HUGPY_AUDIO_EXTRACT_URL") ?? `${apiBase}/video/jobs/audio_extract`,
|
|
74
|
+
/**
|
|
75
|
+
* Enqueue a text/image → image generate job (Generate station, later phase).
|
|
76
|
+
* Declared here now so the Generate-station build is a pure-additive registry
|
|
77
|
+
* flip that never re-touches this URL-literal module.
|
|
78
|
+
*/
|
|
79
|
+
generateEnqueueUrl:
|
|
80
|
+
read("VITE_HUGPY_GENERATE_URL") ?? `${apiBase}/video/jobs/generate_image`,
|
|
81
|
+
/**
|
|
82
|
+
* Enqueue a text/image → scene generate job (Generate station, Scene mode) — one
|
|
83
|
+
* ordered multimodal prompt → N consecutive image frames + (optionally) one
|
|
84
|
+
* assembled mp4 clip. Same job-bus + status seam as generateEnqueueUrl; declared
|
|
85
|
+
* here so Scene mode is a pure-additive flip that never re-touches this
|
|
86
|
+
* URL-literal module.
|
|
87
|
+
*/
|
|
88
|
+
generateSceneEnqueueUrl:
|
|
89
|
+
read("VITE_HUGPY_GENERATE_SCENE_URL") ?? `${apiBase}/video/jobs/generate_scene`,
|
|
90
|
+
/**
|
|
91
|
+
* Enqueue a GOAL-TIMELINE → movie generate job (Generate station, Movie mode) — an
|
|
92
|
+
* ordered, contiguous list of goals tiling [0,total) → an N-segment movie (segment
|
|
93
|
+
* frames + one assembled mp4 LAST), with an opt-in vision director loop. Same
|
|
94
|
+
* job-bus + poll/cancel seam as generateSceneEnqueueUrl (the poll returns a NESTED
|
|
95
|
+
* movie progress); declared here so Movie mode is a pure-additive flip that never
|
|
96
|
+
* re-touches this URL-literal module.
|
|
97
|
+
*/
|
|
98
|
+
generateMovieEnqueueUrl:
|
|
99
|
+
read("VITE_HUGPY_GENERATE_MOVIE_URL") ?? `${apiBase}/video/jobs/generate_movie`,
|
|
100
|
+
/**
|
|
101
|
+
* Curated "ideal default loads" for the Generate station — GET → { presets: [
|
|
102
|
+
* { id, name, description, mode, model_key, defaults{...}, recommended } ] }. Each
|
|
103
|
+
* preset prefills the generation knobs + model; the per-id apply POST
|
|
104
|
+
* (presetApplyUrl) pre-warms that model on a GPU worker. Declared here so the
|
|
105
|
+
* dropdown is a pure-additive flip that never re-touches this URL-literal module.
|
|
106
|
+
*/
|
|
107
|
+
presetsUrl: read("VITE_HUGPY_PRESETS_URL") ?? `${apiBase}/video/presets`,
|
|
108
|
+
/**
|
|
109
|
+
* Curated MOVIE templates for the Generate-station Movie tab — GET → [{ id, name,
|
|
110
|
+
* description, model_key, width, height, steps, guidance, fps, chain,
|
|
111
|
+
* goals:[{start_frame,end_frame,prompt}], vision_enabled, score_threshold }]. Unlike
|
|
112
|
+
* `presetsUrl` (a {presets:[…]} envelope of knob-only "loads"), a movie preset is a
|
|
113
|
+
* BARE ARRAY and each item carries the WHOLE shot list — picking one drops a ready
|
|
114
|
+
* goal timeline + settings into the Movie editor. The per-id apply POST
|
|
115
|
+
* (moviePresetApplyUrl) echoes the same object. Declared here so the Movie-template
|
|
116
|
+
* dropdown is a pure-additive flip that never re-touches this URL-literal module.
|
|
117
|
+
*/
|
|
118
|
+
moviePresetsUrl: read("VITE_HUGPY_MOVIE_PRESETS_URL") ?? `${apiBase}/movie/presets`,
|
|
119
|
+
/**
|
|
120
|
+
* Enqueue a STUDIO image-to-video clip (Studio Clips viewer, slice #3) — POST a
|
|
121
|
+
* {resolution:{width,height,fps}, seed, ...} body → {job_id}. The job runs through
|
|
122
|
+
* the cinema-studio spine (router → manifest → runner → content-addressed clip)
|
|
123
|
+
* and its mp4 is cataloged in the media store, playable via `studioClipUrl`.
|
|
124
|
+
*/
|
|
125
|
+
studioI2VEnqueueUrl:
|
|
126
|
+
read("VITE_HUGPY_STUDIO_I2V_URL") ?? `${apiBase}/video/studio/i2v`,
|
|
127
|
+
/**
|
|
128
|
+
* Enqueue a STUDIO MOVIE (Studio Movie composer, slice B2/B3) — POST an ordered
|
|
129
|
+
* goal timeline {resolution:{width,height,fps}, seed, vram_budget_gb, negative_prompt,
|
|
130
|
+
* goals:[{prompt, seed?, branch_frame?}]} → {job_id}. Each goal renders one studio
|
|
131
|
+
* clip conjoined at splice points into one NLE row (segment clips + an assembled
|
|
132
|
+
* movie.mp4 LAST); polled on the generic GET /video/jobs/<id> (jobStatusUrl). Same
|
|
133
|
+
* idiom as studioI2VEnqueueUrl — declared here (the one URL-literal module) so the
|
|
134
|
+
* composer imports it instead of deriving the path inline.
|
|
135
|
+
*/
|
|
136
|
+
studioMovieUrl:
|
|
137
|
+
read("VITE_HUGPY_STUDIO_MOVIE_URL") ?? `${apiBase}/video/studio/movie`,
|
|
138
|
+
/**
|
|
139
|
+
* STUDIO TESTER — POST {category, prompt, models?} to sweep one prompt across
|
|
140
|
+
* EVERY servable model of the category's type, recording a model-battery
|
|
141
|
+
* run-dir (one row per model). Operator-gated; runs as a background job and
|
|
142
|
+
* returns {job_id, battery_run_dir}. Per-model results stream to the studio-
|
|
143
|
+
* assist log. Same URL-literal idiom as studioI2VEnqueueUrl.
|
|
144
|
+
*/
|
|
145
|
+
studioTesterUrl:
|
|
146
|
+
read("VITE_HUGPY_STUDIO_TESTER_URL") ?? `${apiBase}/video/studio/tester`,
|
|
147
|
+
/**
|
|
148
|
+
* DURABLE recent studio clips for the Studio Clips list — GET → { clips: [{ job_id,
|
|
149
|
+
* status, playable, created, updated, output:{asset_id,width,height,duration_s} }] }.
|
|
150
|
+
* Sourced from the media CATALOG (not the comms /llm/jobs view, which drops terminal
|
|
151
|
+
* rows after ~600s), so a clip produced an hour ago still lists. Each `playable` row's
|
|
152
|
+
* `job_id` feeds `studioClipUrl(job_id)` to stream the clip. Declared here so the
|
|
153
|
+
* Studio Clips station is a pure-additive registry flip that never re-touches this
|
|
154
|
+
* module.
|
|
155
|
+
*/
|
|
156
|
+
studioClipsUrl:
|
|
157
|
+
read("VITE_HUGPY_STUDIO_CLIPS_URL") ?? `${apiBase}/video/studio/clips`,
|
|
158
|
+
/**
|
|
159
|
+
* CINEMA SESSIONS — every movie the studio-movies root holds, newest first: GET →
|
|
160
|
+
* { movies: [{ movie_id, job_id, title, project, status:"partial"|"paused"|"done"|
|
|
161
|
+
* "failed"|"running", segments_completed, segments_total, resumable, width, height,
|
|
162
|
+
* fps, id_lock, updated, movie, segments:[{ index, segment_id, prompt, status,
|
|
163
|
+
* clip_available, media, duration_s, frames, resumed, error:{code,message}|null }] }] }.
|
|
164
|
+
*
|
|
165
|
+
* A FILESYSTEM listing, not a bus query (k91): a movie is ONE job that can run for
|
|
166
|
+
* hours, and when that job ends — cancelled, reaped, or lost with the tab — its
|
|
167
|
+
* rendered segments stay on disk. This is the handle that outlives the job, which is
|
|
168
|
+
* why the id is the movie DIR leaf (stable across a resume, which mints a new job id)
|
|
169
|
+
* and never the job id. Each done segment carries a ready `/video/media?handle=` url,
|
|
170
|
+
* so a session's clips are watchable AS THEY LAND. Paired with
|
|
171
|
+
* `studioMoviePauseUrl` / `studioMovieResumeUrl` for the two actions on a session.
|
|
172
|
+
*/
|
|
173
|
+
studioMoviesUrl:
|
|
174
|
+
read("VITE_HUGPY_STUDIO_MOVIES_URL") ?? `${apiBase}/video/studio/movies`,
|
|
175
|
+
/**
|
|
176
|
+
* Bus-wide MEDIA JOBS listing for the console-wide "Active Processes" view — GET
|
|
177
|
+
* (?all=1 appends recent terminal rows; ?limit=N, default 50) → { jobs: [{ job_id,
|
|
178
|
+
* name, status, created, updated, principal, progress:{…|phase:"awaiting_capacity",
|
|
179
|
+
* reason, held_since, overtaken}|null, placement?:{source:"reservation"|"template",
|
|
180
|
+
* host, worker_id, gpu, process, reserved_bytes} }] }. Unlike `llmJobsUrl` (the
|
|
181
|
+
* unified cross-transport feed, terminal rows dropped after ~600s) this is the
|
|
182
|
+
* media CATALOG projection and carries PLACEMENT — WHERE each in-flight render
|
|
183
|
+
* physically executes (ae · cuda:0 · P-studio). Declared here so the Active
|
|
184
|
+
* Processes surface never derives the path inline.
|
|
185
|
+
*/
|
|
186
|
+
mediaJobsUrl: read("VITE_HUGPY_MEDIA_JOBS_URL") ?? `${apiBase}/video/jobs`,
|
|
187
|
+
/**
|
|
188
|
+
* Curated STUDIO clip presets for the Studio Clips station — GET → { presets: [{
|
|
189
|
+
* id, name, description, capability:"i2v"|"t2v", width, height, fps, vram_budget_gb,
|
|
190
|
+
* seed, prompt, negative, recommended }] } (a {presets:[…]} envelope, like
|
|
191
|
+
* `presetsUrl`/`moviePresetsUrl`). Unlike a video preset (which pins a model_key),
|
|
192
|
+
* a studio preset pins a CAPABILITY + geometry + a routing `vram_budget_gb` and lets
|
|
193
|
+
* the studio router pick the model — picking one prefills the generate affordance
|
|
194
|
+
* (and drives the enqueue's capability/budget/prompt). The per-id apply POST
|
|
195
|
+
* (studioPresetApplyUrl) echoes the same object as a POSTable /video/studio/i2v body.
|
|
196
|
+
* Declared here so the Studio-preset dropdown is a pure-additive flip that never
|
|
197
|
+
* re-touches this URL-literal module.
|
|
198
|
+
*/
|
|
199
|
+
studioPresetsUrl:
|
|
200
|
+
read("VITE_HUGPY_STUDIO_PRESETS_URL") ?? `${apiBase}/video/studio/presets`,
|
|
201
|
+
/**
|
|
202
|
+
* RENDER PRESETS — what this fleet can ACTUALLY render today. GET → { presets: [{
|
|
203
|
+
* id, title, capability, capabilities[], model, precision, geometry, width, height,
|
|
204
|
+
* fps, default_frames, max_frames, inputs[], proven, evidence, composes[], joints[],
|
|
205
|
+
* vram_envelope_gb, vram_need_gib, fits_render_box }], unavailable: [{ capability,
|
|
206
|
+
* reason, refusal }], menu, frame_cadence, render_box, render_box_vram_gib }.
|
|
207
|
+
*
|
|
208
|
+
* The FOURTH and only MEASURED preset surface: the other three (`presetsUrl`,
|
|
209
|
+
* `moviePresetsUrl`, `studioPresetsUrl`) publish what we CURATED; this one publishes
|
|
210
|
+
* the eight ratified rows measured on the live fleet, plus — the half that makes a
|
|
211
|
+
* compatibility-aware picker possible — every capability NO preset covers, with the
|
|
212
|
+
* measured blocker. Read by useRenderPresets.ts, which every studio surface consults
|
|
213
|
+
* to decide what to OFFER. It is a courtesy layer: the server's own capability gate
|
|
214
|
+
* on POST /video/studio/i2v stays authoritative.
|
|
215
|
+
*/
|
|
216
|
+
renderPresetsUrl:
|
|
217
|
+
read("VITE_HUGPY_RENDER_PRESETS_URL") ?? `${apiBase}/video/render/presets`,
|
|
218
|
+
/**
|
|
219
|
+
* Distinct known PROJECT names — GET → { projects: [name, …] } (read-only; derived
|
|
220
|
+
* from the media-bus job store). Feeds the Settings "Project" combobox (choose an
|
|
221
|
+
* existing name or free-type a new one) that threads `project` into the enqueue.
|
|
222
|
+
* Empty = auto-named (the default): the field is optional scaffolding.
|
|
223
|
+
*/
|
|
224
|
+
projectsUrl: read("VITE_HUGPY_PROJECTS_URL") ?? `${apiBase}/video/projects`,
|
|
225
|
+
/**
|
|
226
|
+
* IDENTITY PROFILES (studio stage (a)) — GET → { profiles: [{ slug, name,
|
|
227
|
+
* reference_images[], created_at, notes }] }; POST { name, reference_images[1..4],
|
|
228
|
+
* notes? } → { profile } (409 on a duplicate name). An identity profile is the
|
|
229
|
+
* DURABLE form of "the reference set IS the identity": a named, curated reference
|
|
230
|
+
* set saved ONCE and associated anywhere (single clips, movies, stills) via the
|
|
231
|
+
* enqueue body's `identity_profile:<slug>`. Declared here so the identity picker /
|
|
232
|
+
* save affordance is a pure-additive flip that never re-touches this URL-literal module.
|
|
233
|
+
*/
|
|
234
|
+
identityProfilesUrl:
|
|
235
|
+
read("VITE_HUGPY_IDENTITY_PROFILES_URL") ?? `${apiBase}/video/identity-profiles`,
|
|
236
|
+
/**
|
|
237
|
+
* CHARACTER-GROUPS COMMIT (dev/CHARACTER-GROUPS-PLAN.md slice S3) — POST the
|
|
238
|
+
* user-CURATED output of the char360 "review" extraction:
|
|
239
|
+
* { groups: [ { name?: string, reference_images: string[] } ] }
|
|
240
|
+
* where each `reference_images` entry is a media-handle (jailed absolute path,
|
|
241
|
+
* a `view.url` from the S1 review manifest) the user KEPT in that group after
|
|
242
|
+
* remove/move/merge edits, and only the groups the user marked "proceed" are
|
|
243
|
+
* sent. Response 200 { results: [ { name, ok, slug?, error? } ] } — one result
|
|
244
|
+
* per submitted group, positionally aligned to the request order, each minting
|
|
245
|
+
* (or failing to mint) one identity profile. Declared here so the
|
|
246
|
+
* character-groups panel is a pure-additive flip that never re-touches this
|
|
247
|
+
* URL-literal module. (S3 builds this route to THIS exact body shape.)
|
|
248
|
+
*/
|
|
249
|
+
identityProfilesFromGroupsUrl:
|
|
250
|
+
read("VITE_HUGPY_IDENTITY_FROM_GROUPS_URL") ??
|
|
251
|
+
`${apiBase}/video/identity-profiles/from-groups`,
|
|
252
|
+
/**
|
|
253
|
+
* LLM PROMPT ASSIST for the Generate-station composer — POST { mode:"detail"|
|
|
254
|
+
* "generate", draft?, model?, context?:{kind?,hint?} } → 200 { prompt, model,
|
|
255
|
+
* kind }. `detail` enriches the caller's `draft` (required, non-empty); `generate`
|
|
256
|
+
* writes a full prompt from scratch (draft optional, used as a loose theme). Send
|
|
257
|
+
* NO `model` (the backend picks the best resolvable chat model) and pass
|
|
258
|
+
* `context.kind` = the active sub-mode (image|scene|movie) so video modes get
|
|
259
|
+
* motion/camera phrasing. Errors: 400 (validation/unknown model) / 502 (no worker /
|
|
260
|
+
* generation failed) carry a flat { error:"<string>" }. Declared here so the
|
|
261
|
+
* composer's assist buttons are a pure-additive flip that never re-touch this
|
|
262
|
+
* URL-literal module.
|
|
263
|
+
*/
|
|
264
|
+
promptAssistUrl:
|
|
265
|
+
read("VITE_HUGPY_PROMPT_ASSIST_URL") ?? `${apiBase}/video/prompt/assist`,
|
|
266
|
+
/**
|
|
267
|
+
* STUDIO-ASSIST LIVE LOG (operator directive, 2026-07-31) — a stored record per
|
|
268
|
+
* prompt-generate attempt: the UNTRUNCATED model reply, what was stripped
|
|
269
|
+
* (text/reasoning), whether the answer came FROM the reasoning, and the outcome
|
|
270
|
+
* (served | empty | parse_error | worker_error | resolve_error). Lets the
|
|
271
|
+
* operator self-diagnose "the assistant returned only reasoning" / "did not
|
|
272
|
+
* return the spread JSON" without asking the keeper.
|
|
273
|
+
* GET {logUrl}?limit&since&after_id → { events:[…], count, cursor } (page-load
|
|
274
|
+
* backfill; `raw` bounded for the load only).
|
|
275
|
+
* GET {logStreamUrl} → SSE, replay-then-live, full `raw`.
|
|
276
|
+
* Same auth as every other /video call (the video gate) — no new credential.
|
|
277
|
+
*/
|
|
278
|
+
promptAssistLogUrl:
|
|
279
|
+
read("VITE_HUGPY_PROMPT_ASSIST_LOG_URL")
|
|
280
|
+
?? `${apiBase}/video/prompt/assist/log`,
|
|
281
|
+
promptAssistLogStreamUrl:
|
|
282
|
+
read("VITE_HUGPY_PROMPT_ASSIST_LOG_STREAM_URL")
|
|
283
|
+
?? `${apiBase}/video/prompt/assist/log/stream`,
|
|
284
|
+
/**
|
|
285
|
+
* Which TEXT GENERATORS the fleet can actually run — GET →
|
|
286
|
+
* { models: [{ model, serving, framework, default }], default, excluded, ... }.
|
|
287
|
+
* Feeds the Enhance/Generate picker. Deliberately NOT the full model registry:
|
|
288
|
+
* the backend offers only rows a live worker holds, and drops catalog entries
|
|
289
|
+
* that are mis-tagged text-generation (a Flux image LoRA is in there), so the
|
|
290
|
+
* picker cannot offer a choice that 404s or renders nothing.
|
|
291
|
+
*/
|
|
292
|
+
promptAssistModelsUrl:
|
|
293
|
+
read("VITE_HUGPY_PROMPT_ASSIST_MODELS_URL")
|
|
294
|
+
?? `${apiBase}/video/prompt/assist/models`,
|
|
295
|
+
/**
|
|
296
|
+
* INTENT ROUTER (STUDIO-SPREAD-SPEC §1d) — POST { text, scope:"segment"|"movie" }
|
|
297
|
+
* → 200 { intent:"empty"|"direction"|"scene_prompt"|"ambiguous", operation,
|
|
298
|
+
* confidence, cached, degraded }. Decides whether what the user typed is a SCENE
|
|
299
|
+
* (→ enhance) or a DIRECTION (→ generate from it), so the composer can pre-arm the
|
|
300
|
+
* right button instead of making that an unassisted guess.
|
|
301
|
+
*
|
|
302
|
+
* ALWAYS 200 — that is the contract, not an accident: blank text short-circuits with
|
|
303
|
+
* no model call, and ANY router failure degrades to `ambiguous` + `degraded:true` so
|
|
304
|
+
* the UI shows BOTH actions. A classifier outage must never make a text box unusable,
|
|
305
|
+
* and a classification must never run a generation on its own. Call it on field BLUR
|
|
306
|
+
* or just before an action, never per keystroke (the backend caches by input hash,
|
|
307
|
+
* which is a latency nicety, not a licence to spam it).
|
|
308
|
+
*/
|
|
309
|
+
promptIntentUrl:
|
|
310
|
+
read("VITE_HUGPY_PROMPT_INTENT_URL") ?? `${apiBase}/video/prompt/intent`,
|
|
311
|
+
/**
|
|
312
|
+
* Model registry — GET → { data: [{ id, task, tasks, hub_id, ... }] }. The Frames
|
|
313
|
+
* and Generate stations filter this to the GENERATION image tasks
|
|
314
|
+
* (text-to-image + image-to-image) off the FULL `tasks` capability list for the
|
|
315
|
+
* model dropdown (see useModels.ts); the registry is revalidated in-session so
|
|
316
|
+
* newly-adopted models appear without a reload.
|
|
317
|
+
*/
|
|
318
|
+
modelsUrl: read("VITE_HUGPY_MODELS_URL") ?? `${apiBase}/v1/models`,
|
|
319
|
+
/** Task defaults — GET → { defaults: { "text-to-image": <id>, ... }, tasks:[...] }. */
|
|
320
|
+
promptTasksUrl: read("VITE_HUGPY_PROMPT_TASKS_URL") ?? `${apiBase}/prompt/tasks`,
|
|
321
|
+
/**
|
|
322
|
+
* The AUTHORITATIVE unified feed of EVERY in-flight worker call across ALL
|
|
323
|
+
* transports (web / v1 / discord / cli / media) — GET → { jobs: [{ id, kind,
|
|
324
|
+
* status, transport, model, model_name, worker, principal, elapsed, tokens,
|
|
325
|
+
* error, ... }] }. Unlike the per-session jobTracker (which only knows jobs THIS
|
|
326
|
+
* browser enqueued) this shows fleet-wide work — identity reconstructions
|
|
327
|
+
* (kind:"identity_reconstruction", transport:"media"), studio renders, and any
|
|
328
|
+
* call from another station/transport. The Active Processes panel polls it,
|
|
329
|
+
* keeps the in-flight rows (pending/processing/streaming, minus `download`
|
|
330
|
+
* provisioning), and merges them with the tracker rows. Declared here so the
|
|
331
|
+
* panel never derives the path inline.
|
|
332
|
+
*/
|
|
333
|
+
llmJobsUrl: read("VITE_HUGPY_LLM_JOBS_URL") ?? `${apiBase}/llm/jobs`,
|
|
334
|
+
/**
|
|
335
|
+
* k9 VIDEO-SHARE links — GET → { keys:[{id,label,prefix,created_at,expires_at,
|
|
336
|
+
* expired,revoked,last_used}] } (OPERATOR-ONLY; a share/anon session gets 401,
|
|
337
|
+
* which the Share button uses as its auth probe). POST { label?, ttl_days? } →
|
|
338
|
+
* { key, url, id, ... } (the full key + link, shown ONCE). DELETE the per-id
|
|
339
|
+
* revoke via `keysVideoShareRevokeUrl`. Operator-gated + off the /video surface
|
|
340
|
+
* server-side, so a share principal can never reach it.
|
|
341
|
+
*/
|
|
342
|
+
keysVideoShareUrl:
|
|
343
|
+
read("VITE_HUGPY_KEYS_VIDEO_SHARE_URL") ?? `${apiBase}/keys/video-share`,
|
|
344
|
+
/**
|
|
345
|
+
* Default endpoint for the ComfyUI viewer station — the URL its <iframe> embeds
|
|
346
|
+
* until the user overrides it (the override is kept per-tab in sessionStorage).
|
|
347
|
+
* MUST be an HTTPS endpoint: the video UI is served over HTTPS, so an http:// or
|
|
348
|
+
* localhost URL is silently blocked as mixed content. Set VITE_HUGPY_COMFY_URL to
|
|
349
|
+
* a documented placeholder (e.g. https://comfy.hugpy.ai); NEVER hardcode a secret.
|
|
350
|
+
* Empty by default — the station then shows its "enter an endpoint" empty state.
|
|
351
|
+
*/
|
|
352
|
+
comfyUrl: read("VITE_HUGPY_COMFY_URL") ?? "",
|
|
353
|
+
/**
|
|
354
|
+
* Dedicated worker pool for this arm's jobs. Same semantics as the media arm:
|
|
355
|
+
* empty string = general resolution (un-pooled); set VITE_HUGPY_POOL to route
|
|
356
|
+
* to reserved workers once a pooled worker exists.
|
|
357
|
+
*/
|
|
358
|
+
pool: env["VITE_HUGPY_POOL"] != null ? env["VITE_HUGPY_POOL"].trim() : "",
|
|
359
|
+
/**
|
|
360
|
+
* Send cookies on hugpy requests for session auth. DEFAULT OFF — see the
|
|
361
|
+
* media arm's config for the CORS rationale. Opt in with
|
|
362
|
+
* VITE_HUGPY_WITH_CREDENTIALS=true once hugpy's CORS is credential-ready.
|
|
363
|
+
*/
|
|
364
|
+
withCredentials: readBool("VITE_HUGPY_WITH_CREDENTIALS", false),
|
|
365
|
+
/**
|
|
366
|
+
* Attach an X-Request-Id header for server-side correlation. DEFAULT OFF —
|
|
367
|
+
* a custom header forces a CORS preflight the server may not answer. Opt in
|
|
368
|
+
* with VITE_HUGPY_SEND_REQUEST_ID=true. The request id is still generated and
|
|
369
|
+
* used for client-side telemetry regardless.
|
|
370
|
+
*/
|
|
371
|
+
sendRequestId: readBool("VITE_HUGPY_SEND_REQUEST_ID", false),
|
|
372
|
+
} as const;
|
|
373
|
+
|
|
374
|
+
// Per-id / per-handle endpoints. Kept as helpers (not static strings) because the
|
|
375
|
+
// job id and media handle are runtime values — but they still derive from apiBase
|
|
376
|
+
// here, in the ONE module permitted to hold URL literals. Both callers must reach
|
|
377
|
+
// these through config, never by concatenating paths themselves.
|
|
378
|
+
|
|
379
|
+
/** Poll a single crop/extract job by id: GET → {job_id, status, result}. */
|
|
380
|
+
export function jobStatusUrl(id: string): string {
|
|
381
|
+
return `${apiBase}/video/jobs/${encodeURIComponent(id)}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Revoke one video-share link by id: DELETE → {ok} (404 unknown id). Per-id like
|
|
385
|
+
* jobStatusUrl — derived from apiBase here, the one module permitted to hold URL
|
|
386
|
+
* literals. (List + create use `keysVideoShareUrl`.) */
|
|
387
|
+
export function keysVideoShareRevokeUrl(id: string): string {
|
|
388
|
+
return `${apiBase}/keys/video-share/${encodeURIComponent(id)}`;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The general VIDEO-JOBS status route, by job id: GET → { job_id, progress,
|
|
393
|
+
* result }. Byte-identical to `jobStatusUrl` (same route) but named generically
|
|
394
|
+
* for callers — like the char360 S4 "Extract from video" poll — that aren't
|
|
395
|
+
* crop-specific. `result` is null/absent while running (`progress` 0..1 with a
|
|
396
|
+
* stage/message); terminal is `result.ok===true` (success — no slug list, the
|
|
397
|
+
* created/updated profile is the durable record) or `result.ok===false` with
|
|
398
|
+
* `result.error.{code,message,retryable}` (honest error-as-data). Per-id like
|
|
399
|
+
* `jobStatusUrl` — derived from apiBase here, the one module permitted to hold
|
|
400
|
+
* URL literals.
|
|
401
|
+
*/
|
|
402
|
+
export function videoJobUrl(jobId: string): string {
|
|
403
|
+
return `${apiBase}/video/jobs/${encodeURIComponent(jobId)}`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Cooperative cancel: POST → {job_id, status, cancelled}. Queued jobs die
|
|
407
|
+
* outright; a running scene stops between frames. */
|
|
408
|
+
export function jobCancelUrl(id: string): string {
|
|
409
|
+
return `${apiBase}/video/jobs/${encodeURIComponent(id)}/cancel`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Cancel a NON-media-transport worker call by id: POST → cooperative stop. The
|
|
414
|
+
* unified `/llm/jobs` feed carries calls from every transport; a `transport:"media"`
|
|
415
|
+
* job cancels through `jobCancelUrl` (the video job bus), but everything else
|
|
416
|
+
* (web / v1 / discord / cli chat + generation) cancels here. Per-id like
|
|
417
|
+
* `jobCancelUrl` — derived from apiBase, the one module permitted to hold URL literals.
|
|
418
|
+
*/
|
|
419
|
+
export function llmChatCancelUrl(id: string): string {
|
|
420
|
+
return `${apiBase}/llm/chat/cancel/${encodeURIComponent(id)}`;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* HARD-CANCEL a job by id on the unified feed: POST → {cancelled, status}. Unlike
|
|
425
|
+
* the transport-specific routes above, this is the ONE cancel the fleet-wide
|
|
426
|
+
* `/llm/jobs` Active-Processes rows hit regardless of which transport launched the
|
|
427
|
+
* job — the backend fans it out to the right bus. Per-id like `jobCancelUrl` —
|
|
428
|
+
* derived from apiBase, the one module permitted to hold URL literals.
|
|
429
|
+
*/
|
|
430
|
+
export function llmJobCancelUrl(id: string): string {
|
|
431
|
+
return `${apiBase}/llm/jobs/${encodeURIComponent(id)}/cancel`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Apply (pre-warm) a curated Generate-station preset by id: POST → on success
|
|
436
|
+
* {ok:true, worker:{name,id}, model_key, mode, defaults{...}, warming}; on failure
|
|
437
|
+
* a {ok:false, error:{code,message}} with 404 (unknown preset/model) or 409 (no GPU
|
|
438
|
+
* worker / won't fit). Per-id like jobStatusUrl — derived from apiBase here.
|
|
439
|
+
*/
|
|
440
|
+
export function presetApplyUrl(id: string): string {
|
|
441
|
+
return `${apiBase}/video/presets/${encodeURIComponent(id)}/apply`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Apply a curated Movie template by id: POST → the same full preset object the list
|
|
446
|
+
* endpoint returns (id/name/model_key/dims/knobs + the whole `goals` shot list +
|
|
447
|
+
* director defaults). Per-id like presetApplyUrl — derived from apiBase here. The
|
|
448
|
+
* Movie-template dropdown populates the editor from the ALREADY-fetched list object,
|
|
449
|
+
* so this seam exists for parity / a future server-side pre-warm.
|
|
450
|
+
*/
|
|
451
|
+
export function moviePresetApplyUrl(id: string): string {
|
|
452
|
+
return `${apiBase}/movie/presets/${encodeURIComponent(id)}/apply`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Raw media bytes for a MediaRef `uri` handle — usable directly as an <img> src.
|
|
457
|
+
* The handle is opaque and may contain reserved characters, so it is always
|
|
458
|
+
* URL-encoded into the query.
|
|
459
|
+
*
|
|
460
|
+
* Canned demo (?demo=1): fixture MediaRefs carry BUNDLED asset URLs as their
|
|
461
|
+
* uri — <img>/<video> element loads bypass window.fetch, so the demo shim can
|
|
462
|
+
* never answer them; the asset URL itself must be the src. Pass those through.
|
|
463
|
+
*/
|
|
464
|
+
export function mediaBytesUrl(handle: string): string {
|
|
465
|
+
if (isCanned() && /^(\/|https?:|data:|blob:)/.test(handle) && !handle.startsWith("/mnt/")) {
|
|
466
|
+
return handle;
|
|
467
|
+
}
|
|
468
|
+
// k9: element-src loads (this is used as <img>/<video> src) can't carry the
|
|
469
|
+
// X-Video-Share header, so a share session rides the credential in the query.
|
|
470
|
+
return withShareParam(`${apiBase}/video/media?handle=${encodeURIComponent(handle)}`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Stream a produced STUDIO clip by media-bus job id — usable directly as an HTML5
|
|
475
|
+
* `<video>` src (Studio Clips viewer, slice #3). The route resolves the job's
|
|
476
|
+
* content-addressed clip.mp4 server-side and streams it Range-aware (seek-able), so
|
|
477
|
+
* the browser never handles a filesystem path. Per-id like `jobStatusUrl` — derived
|
|
478
|
+
* from apiBase here, the one module permitted to hold URL literals.
|
|
479
|
+
*/
|
|
480
|
+
export function studioClipUrl(jobId: string): string {
|
|
481
|
+
// k9: used directly as a <video> src (element load, no header) — a share
|
|
482
|
+
// session rides its credential in the query, exactly like mediaBytesUrl.
|
|
483
|
+
return withShareParam(`${apiBase}/video/studio/clip/${encodeURIComponent(jobId)}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The exact CREATION PARAMETERS of a studio render, for a list-row expander: GET →
|
|
488
|
+
* {job_id, status, spec, manifest|null, error|null}. `manifest` is the content-addressed
|
|
489
|
+
* render manifest for a done clip (the TRUE params); `error` is {code,message,retryable}
|
|
490
|
+
* for a failed/cancelled job. LAZY (fetched on expand, not on the list poll). Per-id like
|
|
491
|
+
* `studioClipUrl` — derived from apiBase here, the one module permitted to hold URL
|
|
492
|
+
* literals.
|
|
493
|
+
*/
|
|
494
|
+
export function studioClipDetailUrl(jobId: string): string {
|
|
495
|
+
return `${apiBase}/video/studio/clip/${encodeURIComponent(jobId)}/detail`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* ARCHIVE a produced studio clip by job id: POST → {ok, job_id, archived:true,
|
|
500
|
+
* already, archived_at}. Fixes "removed clips just reappear" — GET
|
|
501
|
+
* `studioClipsUrl` is DB-driven (not a filesystem walk), so this is the ONE way
|
|
502
|
+
* to make a clip actually stop being listed; the clip's bytes are never touched
|
|
503
|
+
* (never-delete doctrine — see media_bus.archive's docstring). 404 for an unknown
|
|
504
|
+
* id; idempotent (a repeat archive answers `already:true`, never an error) — see
|
|
505
|
+
* `unarchiveUrl` for the reversible half. Per-id like `studioClipUrl` — derived
|
|
506
|
+
* from apiBase here, the one module permitted to hold URL literals.
|
|
507
|
+
*/
|
|
508
|
+
export function studioClipArchiveUrl(jobId: string): string {
|
|
509
|
+
return `${apiBase}/video/studio/clip/${encodeURIComponent(jobId)}/archive`;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* The honest counterpart to `studioClipArchiveUrl`: POST → {ok, job_id,
|
|
514
|
+
* archived:false, already}. Clears the archive mark so the clip rejoins the
|
|
515
|
+
* list; the bytes were never moved, so there is nothing to restore on disk.
|
|
516
|
+
* Per-id like `studioClipUrl` — derived from apiBase here, the one module
|
|
517
|
+
* permitted to hold URL literals.
|
|
518
|
+
*/
|
|
519
|
+
export function studioClipUnarchiveUrl(jobId: string): string {
|
|
520
|
+
return `${apiBase}/video/studio/clip/${encodeURIComponent(jobId)}/unarchive`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* PAUSE a Cinema session by movie id (k91): POST → {ok, movie_id, job_id, status:
|
|
525
|
+
* "paused", cancelled, job_status, segments_completed, segments_total}. Pause is the
|
|
526
|
+
* cooperative cancel PLUS the manifest write that says "parked, not abandoned", so it
|
|
527
|
+
* is IDEMPOTENT: a session whose job already ended still parks (`cancelled:false`
|
|
528
|
+
* says there was nothing live to stop). 404 for an id that is not a session dir.
|
|
529
|
+
* Per-id like `studioClipUrl` — the movie id is a DIR LEAF, so it is URL-encoded here
|
|
530
|
+
* exactly like a job id, in the one module permitted to hold URL literals.
|
|
531
|
+
*/
|
|
532
|
+
export function studioMoviePauseUrl(movieId: string): string {
|
|
533
|
+
return `${apiBase}/video/studio/movie/${encodeURIComponent(movieId)}/pause`;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* RESUME a Cinema session by movie id (k91): POST → {ok, movie_id, job_id, …} once
|
|
538
|
+
* the persisted spec is back on the bus. There is no checkpoint format — the resumed
|
|
539
|
+
* run re-renders the SAME spec and content addressing walks the already-rendered
|
|
540
|
+
* prefix in seconds, so THE CLIPS ARE THE CHECKPOINT.
|
|
541
|
+
*
|
|
542
|
+
* Two honest refusals the caller must SHOW rather than swallow, both 409 {error}:
|
|
543
|
+
* a movie that predates spec.json (nothing to re-enqueue — its segments stay listed
|
|
544
|
+
* and playable), and one whose job is already in flight ("pause it first"). The
|
|
545
|
+
* transport surfaces either body's `error` verbatim through `describeAppError`.
|
|
546
|
+
* Per-id like `studioMoviePauseUrl`.
|
|
547
|
+
*/
|
|
548
|
+
export function studioMovieResumeUrl(movieId: string): string {
|
|
549
|
+
return `${apiBase}/video/studio/movie/${encodeURIComponent(movieId)}/resume`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Studio "Send to Editor" (k12): POST → {ok:true, filename, path, mode}. Hands a
|
|
554
|
+
* produced clip, in a Filmora-native MP4, into a stable inbox the operator's LAN
|
|
555
|
+
* Windows workstation (Filmora desktop) picks up. CONSOLE-OPERATOR ONLY — a
|
|
556
|
+
* video-share guest is refused 403 by the route even though it passes the /video
|
|
557
|
+
* gate (the button is hidden for non-operators; see StudioViewer's probe). No
|
|
558
|
+
* request body (the job id rides the URL, like archive/unarchive). `mode` is
|
|
559
|
+
* "copy" (lossless remux) or "transcode". Per-id like `studioClipUrl` — derived
|
|
560
|
+
* from apiBase here, the one module permitted to hold URL literals.
|
|
561
|
+
*/
|
|
562
|
+
export function studioClipToEditorUrl(jobId: string): string {
|
|
563
|
+
return `${apiBase}/video/studio/clip/${encodeURIComponent(jobId)}/to-editor`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Apply a curated Studio preset by id: POST → the pure-prefill envelope
|
|
568
|
+
* {ok:true, id, name, capability, request:{…a POSTable /video/studio/i2v body}} (a
|
|
569
|
+
* 404 {ok:false,error} for an unknown id). Per-id like `studioClipUrl` — derived
|
|
570
|
+
* from apiBase here, the one module permitted to hold URL literals. The Studio-preset
|
|
571
|
+
* dropdown prefills from the ALREADY-fetched list object, so this seam exists for
|
|
572
|
+
* parity / a future server-side pre-warm (mirrors moviePresetApplyUrl).
|
|
573
|
+
*/
|
|
574
|
+
export function studioPresetApplyUrl(id: string): string {
|
|
575
|
+
return `${apiBase}/video/studio/presets/${encodeURIComponent(id)}/apply`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* One identity profile by slug — GET → { profile } (404 unknown); DELETE → { ok,
|
|
580
|
+
* archived, slug } (the store ARCHIVES under `_deleted`, never erases). Per-slug like
|
|
581
|
+
* `studioClipUrl` — derived from apiBase here, the one module permitted to hold URL
|
|
582
|
+
* literals. (List + create use `identityProfilesUrl`.)
|
|
583
|
+
*/
|
|
584
|
+
export function identityProfileUrl(slug: string): string {
|
|
585
|
+
return `${apiBase}/video/identity-profiles/${encodeURIComponent(slug)}`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Enqueue a TURNAROUND RECONSTRUCTION ("character sheet") for one identity profile
|
|
590
|
+
* — POST { prompt?, views?:string[] (default ["front","three_quarter","profile",
|
|
591
|
+
* "back"]), seed? } → { job_id | job_ids, recon_id } (the enqueue may fan out to ONE
|
|
592
|
+
* job or several — one per requested view; the caller flattens whichever it gets).
|
|
593
|
+
* Each returned job is polled on the existing GET /video/jobs/<id> (`jobStatusUrl`);
|
|
594
|
+
* on completion the profile's `reconstructions[]` carries the generated view paths
|
|
595
|
+
* (served via `mediaBytesUrl`). Per-slug like `identityProfileUrl` — derived from
|
|
596
|
+
* apiBase here, the one module permitted to hold URL literals.
|
|
597
|
+
*/
|
|
598
|
+
export function identityReconstructionUrl(slug: string): string {
|
|
599
|
+
return `${apiBase}/video/identity-profiles/${encodeURIComponent(slug)}/reconstruction`;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* PROMOTE reconstruction views to a profile's CANONICAL set — POST { recon_id,
|
|
604
|
+
* views:number[] (indices into that reconstruction's `views`) } → { profile } (the
|
|
605
|
+
* updated profile, now carrying the enlarged `canonical[]`). The durable "these are
|
|
606
|
+
* the approved reference angles" step after a turnaround is generated. Per-slug like
|
|
607
|
+
* `identityReconstructionUrl` — derived from apiBase here, the one module permitted
|
|
608
|
+
* to hold URL literals.
|
|
609
|
+
*/
|
|
610
|
+
export function identityCanonicalUrl(slug: string): string {
|
|
611
|
+
return `${apiBase}/video/identity-profiles/${encodeURIComponent(slug)}/canonical`;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* ONE-CLICK FULL IDENTITY GENERATION — POST { views?, texture?, turntable?,
|
|
616
|
+
* auto_promote? } (a bare {} is the intended happy-path call) → { job_id, recon_id }.
|
|
617
|
+
* Turns a saved identity PROFILE into a complete 3D identity in a single action: a
|
|
618
|
+
* Hunyuan3D mesh, a 360° turntable, and (by default) auto-promoted canonical angles —
|
|
619
|
+
* no prior reconstruction needed. Poll the minted recon on `identityMeshStatusUrl`.
|
|
620
|
+
* Per-slug like `identityReconstructionUrl` — derived from apiBase here, the one module
|
|
621
|
+
* permitted to hold URL literals.
|
|
622
|
+
*/
|
|
623
|
+
export function identityGenerateUrl(slug: string): string {
|
|
624
|
+
return `${apiBase}/video/identity-profiles/${encodeURIComponent(slug)}/generate`;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Mesh-build STATUS for one reconstruction — GET → the mesh state block
|
|
629
|
+
* ({ status: "none"|"queued"|"running"|"done"|"error"|"cancelled", error?, glb_path?,
|
|
630
|
+
* video_path?, auto_promoted? }). Polled after `identityGenerateUrl` (or the 5g mesh
|
|
631
|
+
* build) until terminal. Per-id like `jobStatusUrl` — derived from apiBase here, the one
|
|
632
|
+
* module permitted to hold URL literals.
|
|
633
|
+
*/
|
|
634
|
+
export function identityMeshStatusUrl(slug: string, reconId: string): string {
|
|
635
|
+
return `${apiBase}/video/identity-profiles/${encodeURIComponent(slug)}/reconstruction/${encodeURIComponent(reconId)}/mesh`;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* IDENTITY VERSIONS slice (design: dev/IDENTITY-VERSIONS-SLICE.md) — per-identity
|
|
640
|
+
* generation settings: PATCH → a PARTIAL `gen_settings` body ({texture?, pose?,
|
|
641
|
+
* frame_count?, fps?, width?, height?, auto_promote?, front_ref?, remove_background?,
|
|
642
|
+
* vision_model?, cleanup_prompt?, negative_prompt?}) → { profile } (an omitted key is
|
|
643
|
+
* left untouched server-side, same partial-PATCH idiom as `identityProfileUrl`).
|
|
644
|
+
* Persists what the Settings panel edits and what a bare `/generate` click honors.
|
|
645
|
+
* cleanup_prompt/negative_prompt are the CLEANUP-PROMPT slice's Advanced-panel "Avoid /
|
|
646
|
+
* cleanup" + "Negative prompt" fields (C4). Per-slug like `identityProfileUrl` —
|
|
647
|
+
* derived from apiBase here, the one module permitted to hold URL literals.
|
|
648
|
+
*/
|
|
649
|
+
export function identityProfileSettingsUrl(slug: string): string {
|
|
650
|
+
return `${apiBase}/video/identity-profiles/${encodeURIComponent(slug)}/settings`;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* One VERSION of an identity (the clay base + N named render-sets it accrues) by
|
|
655
|
+
* id — PATCH { name?, notes? } (rename/annotate) and DELETE (archive; the base
|
|
656
|
+
* version and the currently-ACTIVE version are refused with a 400 — never-delete
|
|
657
|
+
* applies at the version layer too) both hang off this path. The response
|
|
658
|
+
* envelope for these two isn't fixed by the wire contract (unlike the settings
|
|
659
|
+
* PATCH above), so callers should treat it tolerantly. Per-slug+id like
|
|
660
|
+
* `identityMeshStatusUrl` — derived from apiBase here, the one module permitted
|
|
661
|
+
* to hold URL literals.
|
|
662
|
+
*/
|
|
663
|
+
export function identityVersionUrl(slug: string, versionId: string): string {
|
|
664
|
+
return `${apiBase}/video/identity-profiles/${encodeURIComponent(slug)}/versions/${encodeURIComponent(versionId)}`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Make one version the identity's ACTIVE version: POST (no body) → the resolver
|
|
669
|
+
* (`_reference_images_from_body`) then takes THIS version's canonical as the
|
|
670
|
+
* id_lock DNA for future generations that don't name a specific
|
|
671
|
+
* `identity_version`. Per-slug+id like `identityVersionUrl` — derived from
|
|
672
|
+
* apiBase here, the one module permitted to hold URL literals.
|
|
673
|
+
*/
|
|
674
|
+
export function identityVersionActivateUrl(slug: string, versionId: string): string {
|
|
675
|
+
return `${identityVersionUrl(slug, versionId)}/activate`;
|
|
676
|
+
}
|