@hugpy/video-intelligence-ui 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +9 -0
- package/dist/assets/hugpy-mark-BXktFcOQ.png +0 -0
- package/dist/assets/index-DMoT8G1q.css +1 -0
- package/dist/assets/index-GeD1mo16.js +4190 -0
- package/dist/index.html +71 -0
- package/entry.tsx +6 -27
- package/index.html +4 -4
- package/package.json +44 -5
- package/src/assets/hugpy-mark.png +0 -0
- package/src/config.ts +11 -27
- package/src/demo/DemoBanner.tsx +0 -0
- package/src/demo/demo.css +0 -0
- package/src/demo/demoFetch.ts +0 -9
- package/src/demo/fixtures.ts +0 -0
- package/src/demo/identityFixture.ts +0 -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 +8 -3
- 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 +8 -43
- 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 +10 -6
- 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 +4 -4
- 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 +1 -2
- package/src/stations/WorkbenchDrawer.tsx +0 -0
- package/src/stations/WorkbenchSidebar.tsx +8 -261
- 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 +75 -83
- 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 +86 -76
- 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 +3 -2
- package/src/stations/studio/StudioGenerateTab.tsx +278 -190
- package/src/stations/studio/StudioItemCard.tsx +0 -0
- package/src/stations/studio/StudioLibraryTab.tsx +0 -0
- package/src/stations/studio/StudioMovieComposer.tsx +321 -46
- package/src/stations/studio/StudioMovieSessions.tsx +12 -19
- package/src/stations/studio/StudioPlane.tsx +28 -5
- package/src/stations/studio/StudioSplicedRow.tsx +0 -0
- package/src/stations/studio/StudioViewer.tsx +6 -11
- 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 +14 -2
- package/src/stations/studio/useIdentityCreateJob.ts +26 -21
- package/src/stations/studio/useIdentityProfiles.ts +0 -0
- package/src/stations/studio/useReconstruction.ts +11 -5
- 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 +5 -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/jobStream.ts +119 -0
- package/src/video/jobTracker.ts +67 -76
- package/src/video/mediaLibrary.ts +0 -0
- package/src/video/modelsFeed.ts +105 -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/studioOracle.ts +48 -0
- package/src/video/useMediaJobs.ts +17 -3
- package/src/video/useModels.ts +12 -53
- package/src/video/useMoviePresets.ts +0 -0
- package/src/video/useMovieSessions.ts +15 -6
- package/src/video/useOperatorHistory.ts +11 -101
- package/src/video/usePresets.ts +0 -0
- package/src/video/useProjects.ts +0 -0
- package/src/video/usePromptAssist.ts +70 -25
- 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 +14 -6
- package/src/video/watchVideoJob.ts +43 -0
- package/tsconfig.json +0 -0
- package/vite.config.ts +0 -0
- package/src/stations/studio/StudioMovieComposer.tsx.bak-declutter-20260813 +0 -3031
- package/src/video/useWorkerCalls.ts +0 -165
|
@@ -1,105 +1,15 @@
|
|
|
1
|
-
// OPERATOR HISTORY HYDRATION
|
|
2
|
-
// COMPLETE visibility of previous renders in the Session Library — not just
|
|
3
|
-
// what this browser tab produced. The library itself stays a per-session
|
|
4
|
-
// sessionStorage working set (by design); this hook back-fills it from the
|
|
5
|
-
// SERVER record once per session for an operator principal:
|
|
1
|
+
// OPERATOR HISTORY HYDRATION — RETIRED (2026-10-05).
|
|
6
2
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// the operator removed stays removed), genKind-tagged so the Generate
|
|
16
|
-
// station can filter the strips by its CURRENT SECTION.
|
|
17
|
-
//
|
|
18
|
-
// Once-per-session guard in sessionStorage; canned demo never hydrates.
|
|
19
|
-
import { useEffect } from "react";
|
|
20
|
-
import { request, okValue } from "../transport/client";
|
|
21
|
-
import { hugpyConfig, jobStatusUrl } from "../config";
|
|
22
|
-
import { addToLibrary, type LibraryGenKind } from "./mediaLibrary";
|
|
23
|
-
import { getSessionId } from "../session";
|
|
24
|
-
import { isCanned } from "../demo/mode";
|
|
25
|
-
import type { MediaRef } from "./contract";
|
|
26
|
-
|
|
27
|
-
// generate_studio_movie = CINEMA renders — hydrated too (mapped onto the
|
|
28
|
-
// generate_movie library kind: same Videos shelf, no schema change).
|
|
29
|
-
const GEN_KINDS: readonly string[] = [
|
|
30
|
-
"generate_image", "generate_scene", "generate_movie", "generate_studio_movie",
|
|
31
|
-
];
|
|
32
|
-
// v2 (2026-08-13): the once-per-session flag must change whenever the
|
|
33
|
-
// HYDRATED KINDS change — a tab that hydrated v1 (no cinema jobs) kept its
|
|
34
|
-
// flag through every reload and never saw generate_studio_movie history.
|
|
35
|
-
const FLAG_PREFIX = "vi.operatorHistory.v2";
|
|
36
|
-
const DETAIL_CAP = 150; // newest-first; enough history without hammering the API
|
|
37
|
-
|
|
38
|
-
function flagKey(): string {
|
|
39
|
-
try { return `${FLAG_PREFIX}:${getSessionId()}`; } catch { return FLAG_PREFIX; }
|
|
40
|
-
}
|
|
3
|
+
// This hook back-filled the Session Library from the server record once per
|
|
4
|
+
// session, but ONLY for a console operator, and it decided operator-ness by
|
|
5
|
+
// probing GET /keys/video-share — the same 200⇒operator / 401⇒guest probe
|
|
6
|
+
// SharePanel used. That route has no backend in this build: the probe only ever
|
|
7
|
+
// 404'd, so the hydration never ran. With no operator signal to replace it (and
|
|
8
|
+
// per the directive to drop the dead /keys/video-share calls), this is now a
|
|
9
|
+
// no-op rather than a dead request fired on every page load. The exported
|
|
10
|
+
// signature is kept so its one caller stays valid.
|
|
41
11
|
|
|
12
|
+
/** No-op: operator history hydration is retired (see the file header). */
|
|
42
13
|
export function useOperatorHistory(): void {
|
|
43
|
-
|
|
44
|
-
if (isCanned()) return;
|
|
45
|
-
try { if (sessionStorage.getItem(flagKey())) return; } catch { /* no-op */ }
|
|
46
|
-
let cancelled = false;
|
|
47
|
-
void (async () => {
|
|
48
|
-
// Operator probe — non-operators (401/403) never hydrate.
|
|
49
|
-
const probe = await request<unknown>(hugpyConfig.keysVideoShareUrl, {
|
|
50
|
-
meta: { specKey: "media", operation: "operator.probe" },
|
|
51
|
-
});
|
|
52
|
-
if (cancelled || !probe.ok) return;
|
|
53
|
-
// jobStatusUrl("") = `${apiBase}/video/jobs/` — trim the slash and query
|
|
54
|
-
// the list route with terminal rows included.
|
|
55
|
-
const list = await request<unknown>(
|
|
56
|
-
`${jobStatusUrl("").replace(/\/$/, "")}?all=1&limit=300`,
|
|
57
|
-
{ meta: { specKey: "media", operation: "history.jobs.list" } },
|
|
58
|
-
);
|
|
59
|
-
if (cancelled || !list.ok) return;
|
|
60
|
-
const jobs = ((okValue(list) as { jobs?: unknown[] })?.jobs ?? []) as Array<
|
|
61
|
-
Record<string, unknown>
|
|
62
|
-
>;
|
|
63
|
-
const wanted = jobs
|
|
64
|
-
.filter((j) => j?.status === "done" && GEN_KINDS.includes(String(j?.name)))
|
|
65
|
-
.slice(0, DETAIL_CAP);
|
|
66
|
-
for (const j of wanted) {
|
|
67
|
-
if (cancelled) return;
|
|
68
|
-
const id = String(j.job_id ?? j.id ?? "");
|
|
69
|
-
if (!id) continue;
|
|
70
|
-
const det = await request<unknown>(jobStatusUrl(id), {
|
|
71
|
-
meta: { specKey: "media", operation: "history.job.detail" },
|
|
72
|
-
});
|
|
73
|
-
if (!det.ok) continue;
|
|
74
|
-
const result = (okValue(det) as { result?: { outputs?: unknown[] } })?.result;
|
|
75
|
-
for (const o of (result?.outputs ?? []) as Array<Record<string, unknown>>) {
|
|
76
|
-
if (!o?.uri || !o?.asset_id) continue;
|
|
77
|
-
const ref = {
|
|
78
|
-
asset_id: String(o.asset_id),
|
|
79
|
-
kind: (o.kind === "video" ? "video" : "image"),
|
|
80
|
-
uri: String(o.uri),
|
|
81
|
-
mime: (o.mime as string) ?? (o.kind === "video" ? "video/mp4" : "image/png"),
|
|
82
|
-
width: (o.width as number) ?? null,
|
|
83
|
-
height: (o.height as number) ?? null,
|
|
84
|
-
duration_s: (o.duration_s as number) ?? null,
|
|
85
|
-
fps_native: (o.fps_native as number) ?? null,
|
|
86
|
-
channels: (o.channels as number) ?? null,
|
|
87
|
-
sample_rate: (o.sample_rate as number) ?? null,
|
|
88
|
-
} as MediaRef;
|
|
89
|
-
addToLibrary(ref, "history", `${String(j.name)} · ${id.slice(0, 8)}`, {
|
|
90
|
-
genKind: (j.name === "generate_studio_movie"
|
|
91
|
-
? "generate_movie"
|
|
92
|
-
: j.name) as LibraryGenKind,
|
|
93
|
-
// groupId = the producing job: each historical run groups as its own
|
|
94
|
-
// work (like live pushes) instead of one giant shared "history" pile
|
|
95
|
-
// whose identical key collided ACROSS the type sections (expanding
|
|
96
|
-
// Videos/history also expanded Images/history).
|
|
97
|
-
groupId: id,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
try { sessionStorage.setItem(flagKey(), String(Date.now())); } catch { /* no-op */ }
|
|
102
|
-
})();
|
|
103
|
-
return () => { cancelled = true; };
|
|
104
|
-
}, []);
|
|
14
|
+
/* intentionally empty — no /keys/video-share probe, no history back-fill */
|
|
105
15
|
}
|
package/src/video/usePresets.ts
CHANGED
|
File without changes
|
package/src/video/useProjects.ts
CHANGED
|
File without changes
|
|
@@ -37,7 +37,7 @@ export type AssistMode = "detail" | "generate";
|
|
|
37
37
|
export type AssistBusyMode = AssistMode | "spread" | "negative";
|
|
38
38
|
|
|
39
39
|
/** The active sub-mode, sent as `context.kind` so video modes get motion/camera phrasing. */
|
|
40
|
-
export type AssistKind = "image" | "scene" | "movie";
|
|
40
|
+
export type AssistKind = "image" | "scene" | "movie" | "clip";
|
|
41
41
|
|
|
42
42
|
/** The `specKey` a host's telemetry rides under (matches the host's other calls). */
|
|
43
43
|
type SpecKey = "generate" | "studio";
|
|
@@ -53,8 +53,13 @@ export interface UsePromptAssistOptions {
|
|
|
53
53
|
export interface AssistModel {
|
|
54
54
|
/** Catalog key sent back as `body.model`. */
|
|
55
55
|
model: string;
|
|
56
|
-
/** Seated on a worker right now
|
|
56
|
+
/** Seated on a worker right now. */
|
|
57
57
|
serving: boolean;
|
|
58
|
+
/** Present on a worker, but not necessarily loaded. */
|
|
59
|
+
held?: boolean;
|
|
60
|
+
/** Canonical residency (STATE-MODEL.md #4): serving|loaded = ready; hot = on a
|
|
61
|
+
* worker drive (pays a t_load); cold = central only (pays a download + load). */
|
|
62
|
+
state?: "serving" | "loaded" | "hot" | "cold" | "unknown";
|
|
58
63
|
framework?: string | null;
|
|
59
64
|
/** True for the fleet default — used when the user has never chosen. */
|
|
60
65
|
default?: boolean;
|
|
@@ -154,6 +159,61 @@ function readAssistPrompt(value: unknown): string | null {
|
|
|
154
159
|
return null;
|
|
155
160
|
}
|
|
156
161
|
|
|
162
|
+
// Ticket flow (2026-08-28): a cold model load measured 6m12s on the live
|
|
163
|
+
// fleet, and a held browser connection dies long before that (ClientGone) —
|
|
164
|
+
// killing the load, which restarts on the next click, forever. POST with
|
|
165
|
+
// {async:true} returns {ticket} immediately and the generation keeps running
|
|
166
|
+
// server-side; we poll the ticket until the prompt lands. No connection has
|
|
167
|
+
// to survive the load. An old server without the ticket route answers the
|
|
168
|
+
// POST with the direct result, which passes through unchanged.
|
|
169
|
+
const TICKET_POLL_MS = 3_000;
|
|
170
|
+
const TICKET_CEILING_MS = 12 * 60_000;
|
|
171
|
+
|
|
172
|
+
async function assistCall(
|
|
173
|
+
body: Record<string, unknown>,
|
|
174
|
+
specKey: string | undefined,
|
|
175
|
+
operation: string,
|
|
176
|
+
): Promise<ReturnType<typeof request<unknown>>> {
|
|
177
|
+
const res = await request<unknown>(hugpyConfig.promptAssistUrl, {
|
|
178
|
+
method: "POST",
|
|
179
|
+
headers: { "Content-Type": "application/json" },
|
|
180
|
+
body: JSON.stringify({ ...body, async: true }),
|
|
181
|
+
timeoutMs: 30_000,
|
|
182
|
+
meta: { specKey, operation },
|
|
183
|
+
});
|
|
184
|
+
if (!res.ok) return res;
|
|
185
|
+
const value = okValue(res) as { ticket?: string; pending?: boolean } | null;
|
|
186
|
+
const ticket = value?.ticket;
|
|
187
|
+
if (!ticket) return res; // direct (non-ticket) result — serve as-is
|
|
188
|
+
if (value?.pending === false) return res; // current orbit returns the completed result with its ticket
|
|
189
|
+
const deadline = Date.now() + TICKET_CEILING_MS;
|
|
190
|
+
for (;;) {
|
|
191
|
+
await new Promise((r) => setTimeout(r, TICKET_POLL_MS));
|
|
192
|
+
const poll = await request<unknown>(
|
|
193
|
+
`${hugpyConfig.promptAssistUrl}/ticket/${encodeURIComponent(ticket)}`,
|
|
194
|
+
{
|
|
195
|
+
method: "GET",
|
|
196
|
+
timeoutMs: 15_000,
|
|
197
|
+
meta: { specKey, operation: `${operation}.poll` },
|
|
198
|
+
},
|
|
199
|
+
);
|
|
200
|
+
if (!poll.ok) return poll;
|
|
201
|
+
const row = okValue(poll) as { pending?: boolean } | null;
|
|
202
|
+
if (!row?.pending) return poll;
|
|
203
|
+
if (Date.now() >= deadline) {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
error: {
|
|
207
|
+
kind: "timeout",
|
|
208
|
+
message:
|
|
209
|
+
"the assistant is still working after 12 minutes — the model may be stuck loading; try again",
|
|
210
|
+
requestId: `assist-ticket-${ticket.slice(0, 8)}`,
|
|
211
|
+
},
|
|
212
|
+
} as Awaited<ReturnType<typeof request<unknown>>>;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
157
217
|
export function usePromptAssist({ kind, specKey }: UsePromptAssistOptions): PromptAssistApi {
|
|
158
218
|
// One in-flight call at a time: `assistBusy` holds the ACTIVE mode or null when idle —
|
|
159
219
|
// it both blocks concurrent calls and drives which button shows its loading label.
|
|
@@ -246,13 +306,10 @@ export function usePromptAssist({ kind, specKey }: UsePromptAssistOptions): Prom
|
|
|
246
306
|
context: { kind, ...(extra?.context ?? {}) },
|
|
247
307
|
};
|
|
248
308
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
timeoutMs: 90_000, // a few seconds typically; generous for an on-demand worker
|
|
254
|
-
meta: { specKey, operation: `prompt.assist.${mode}` },
|
|
255
|
-
});
|
|
309
|
+
// Ticket flow — see assistCall: the POST returns immediately and we poll,
|
|
310
|
+
// so a cold model load (measured 6m12s) can finish without the browser
|
|
311
|
+
// holding a connection through it.
|
|
312
|
+
const res = await assistCall(body, specKey, `prompt.assist.${mode}`);
|
|
256
313
|
if (!res.ok) {
|
|
257
314
|
setAssistError(describeAppError(errorOf(res)));
|
|
258
315
|
setAssistBusy(null);
|
|
@@ -292,13 +349,7 @@ export function usePromptAssist({ kind, specKey }: UsePromptAssistOptions): Prom
|
|
|
292
349
|
const negModel = opts.extra?.model ?? assistModel;
|
|
293
350
|
if (negModel) body.model = negModel;
|
|
294
351
|
|
|
295
|
-
const res = await
|
|
296
|
-
method: "POST",
|
|
297
|
-
headers: { "Content-Type": "application/json" },
|
|
298
|
-
body: JSON.stringify(body),
|
|
299
|
-
timeoutMs: 90_000,
|
|
300
|
-
meta: { specKey, operation: "prompt.assist.negative" },
|
|
301
|
-
});
|
|
352
|
+
const res = await assistCall(body, specKey, "prompt.assist.negative");
|
|
302
353
|
if (!res.ok) {
|
|
303
354
|
setAssistError(describeAppError(errorOf(res)));
|
|
304
355
|
setAssistBusy(null);
|
|
@@ -328,15 +379,9 @@ export function usePromptAssist({ kind, specKey }: UsePromptAssistOptions): Prom
|
|
|
328
379
|
setAssistError(null);
|
|
329
380
|
setAssistBusy("spread");
|
|
330
381
|
const payload = { ...body, ...(assistModel ? { model: assistModel } : {}) };
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
body: JSON.stringify(payload),
|
|
335
|
-
// A spread writes N paragraphs in one generation, so it is the longest assist
|
|
336
|
-
// call there is — and on a cold generator it pays a model load first.
|
|
337
|
-
timeoutMs: 180_000,
|
|
338
|
-
meta: { specKey, operation: "prompt.assist.spread" },
|
|
339
|
-
});
|
|
382
|
+
// A spread writes N paragraphs in one generation, the longest assist call
|
|
383
|
+
// there is — the ticket flow means a cold generator's load can't kill it.
|
|
384
|
+
const res = await assistCall(payload, specKey, "prompt.assist.spread");
|
|
340
385
|
if (!res.ok) {
|
|
341
386
|
setAssistError(describeAppError(errorOf(res)));
|
|
342
387
|
setAssistBusy(null);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/video/useToks.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
16
16
|
import { hugpyConfig } from "../config";
|
|
17
17
|
import { request, okValue } from "../transport/client";
|
|
18
|
+
import { subscribeJobStream } from "./jobStream";
|
|
18
19
|
|
|
19
|
-
const POLL_MS = 2000;
|
|
20
20
|
const RECENT_LIMIT = 100;
|
|
21
21
|
|
|
22
22
|
/** One recent inference sample (omit-when-unset tolerated on the wire). */
|
|
@@ -127,22 +127,30 @@ export function useToks(): {
|
|
|
127
127
|
setLive(recent.ok || report.ok);
|
|
128
128
|
}, []);
|
|
129
129
|
|
|
130
|
+
// Seed once, then refresh on PUSH: the shared job stream fires on every
|
|
131
|
+
// worker-reported transition (coalesced), so the tok/s tail tracks live work
|
|
132
|
+
// without a 2s timer. No job activity → the last-good snapshot simply stands.
|
|
130
133
|
useEffect(() => {
|
|
131
134
|
mounted.current = true;
|
|
132
135
|
void load();
|
|
133
|
-
|
|
136
|
+
let coalesce: number | null = null;
|
|
137
|
+
const unsub = subscribeJobStream(() => {
|
|
138
|
+
if (coalesce != null) return;
|
|
139
|
+
coalesce = window.setTimeout(() => {
|
|
140
|
+
coalesce = null;
|
|
141
|
+
void load();
|
|
142
|
+
}, 400);
|
|
143
|
+
});
|
|
134
144
|
return () => {
|
|
135
145
|
mounted.current = false;
|
|
136
|
-
window.
|
|
146
|
+
if (coalesce != null) window.clearTimeout(coalesce);
|
|
147
|
+
unsub();
|
|
137
148
|
};
|
|
138
149
|
}, [load]);
|
|
139
150
|
|
|
140
151
|
return { entries, groups, live };
|
|
141
152
|
}
|
|
142
153
|
|
|
143
|
-
/** The poll cadence, exported so the panel header can label it ("every 2s"). */
|
|
144
|
-
export const TOKS_POLL_MS = POLL_MS;
|
|
145
|
-
|
|
146
154
|
/** "12s ago" / "3m ago" from an epoch-seconds ts, "" when unknown. */
|
|
147
155
|
export function tokAgo(ts: number | null, nowMs: number): string {
|
|
148
156
|
if (ts == null) return "";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** One status seed followed by pushed job transitions. No periodic GET loop. */
|
|
2
|
+
import { videoJobUrl } from "../config";
|
|
3
|
+
import { request, okValue } from "../transport/client";
|
|
4
|
+
import { subscribeJobStream } from "./jobStream";
|
|
5
|
+
|
|
6
|
+
export function watchVideoJob(
|
|
7
|
+
jobId: string,
|
|
8
|
+
onJob: (job: Record<string, unknown>) => boolean | void,
|
|
9
|
+
onTimeout: () => void,
|
|
10
|
+
timeoutMs: number,
|
|
11
|
+
): () => void {
|
|
12
|
+
let active = true;
|
|
13
|
+
let pushed = false;
|
|
14
|
+
let unsubscribe: (() => void) | null = null;
|
|
15
|
+
const deadline = Date.now() + timeoutMs;
|
|
16
|
+
const timer = window.setTimeout(() => {
|
|
17
|
+
if (!active || Date.now() < deadline) return;
|
|
18
|
+
stop();
|
|
19
|
+
onTimeout();
|
|
20
|
+
}, timeoutMs);
|
|
21
|
+
const stop = () => {
|
|
22
|
+
if (!active) return;
|
|
23
|
+
active = false;
|
|
24
|
+
window.clearTimeout(timer);
|
|
25
|
+
unsubscribe?.();
|
|
26
|
+
unsubscribe = null;
|
|
27
|
+
};
|
|
28
|
+
const deliver = (row: unknown) => {
|
|
29
|
+
if (!active || !row || typeof row !== "object") return;
|
|
30
|
+
if (onJob(row as Record<string, unknown>) === true) stop();
|
|
31
|
+
};
|
|
32
|
+
unsubscribe = subscribeJobStream((ev) => {
|
|
33
|
+
if (ev.job_id !== jobId || !ev.job) return;
|
|
34
|
+
pushed = true;
|
|
35
|
+
deliver(ev.job);
|
|
36
|
+
});
|
|
37
|
+
void request<unknown>(videoJobUrl(jobId), {
|
|
38
|
+
meta: { specKey: "studio", operation: "video.job.seed" },
|
|
39
|
+
}).then((res) => {
|
|
40
|
+
if (res.ok && !pushed) deliver(okValue(res));
|
|
41
|
+
});
|
|
42
|
+
return stop;
|
|
43
|
+
}
|
package/tsconfig.json
CHANGED
|
File without changes
|
package/vite.config.ts
CHANGED
|
File without changes
|