mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-09 03:16:38 +00:00
fix: add progress logging during silent render pipeline stages (#1220)
* fix: add progress logging during silent render pipeline stages The render pipeline only updates progress at stage boundaries (5%, 10%, 25%), leaving multi-minute gaps with zero log output on low-memory hardware. This adds log.info calls at key sub-steps within the three silent stages: - Probe stage (5%): browser launch, session initialization, duration discovery, media asset discovery, audio volume automation, video visibility window detection - Video extraction (10%): per-video extraction progress - Calibration (25%): browser launch, session initialization, per-frame calibration progress, final cost estimate Also adds 30-second heartbeat timers for the two initializeSession calls (probe and calibration) that can individually take minutes on constrained hardware. Closes #1218 * fix: resolve CI failures in typecheck, runtime seek test, and timeline test - Make handleGsapMaterializeKeyframes optional in DomEditSessionSlice and use optional chaining at the call site (not yet wired) - Update GSAP adapter seek test to expect nudge+seek pattern (totalTime with suppressEvents:true followed by actual seek) - Fix Timeline canvas height test to use TRACK_H constant (48) instead of stale hardcoded value (72) * refactor: extract helpers to meet 600-line file size limit - App.tsx (603→594): extract StudioToast component - useDomEditSession.ts (688→600): extract useGsapSelectionHandlers hook - Timeline.tsx (614→557): extract useTimelineAssetDrop hook - PropertyPanel.tsx (647→584): extract TimingSection to propertyPanelTimingSection * style: fix formatting in TimelineToolbar
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
interface StudioToastProps {
|
||||
message: string;
|
||||
tone?: "error" | "info";
|
||||
}
|
||||
|
||||
export function StudioToast({ message, tone }: StudioToastProps) {
|
||||
return (
|
||||
<div
|
||||
className={`absolute bottom-6 left-1/2 -translate-x-1/2 z-[91] px-4 py-2 rounded-lg border text-sm shadow-lg animate-in fade-in slide-in-from-bottom-2 ${
|
||||
tone === "error"
|
||||
? "bg-red-900/90 border-red-700/50 text-red-200"
|
||||
: "bg-neutral-900/95 border-neutral-700/60 text-neutral-100"
|
||||
}`}
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -125,8 +125,8 @@ function useKeyframeToggle(session?: DomEditSessionSlice) {
|
||||
? async () => {
|
||||
const t = usePlayerStore.getState().currentTime;
|
||||
if (kfAnim?.keyframes) {
|
||||
if (kfAnim.hasUnresolvedKeyframes && session.handleGsapMaterializeKeyframes) {
|
||||
await session.handleGsapMaterializeKeyframes(kfAnim.id);
|
||||
if (kfAnim.hasUnresolvedKeyframes) {
|
||||
await session.handleGsapMaterializeKeyframes?.(kfAnim.id);
|
||||
}
|
||||
const elStart = Number.parseFloat(sel.dataAttributes?.start ?? "0") || 0;
|
||||
const elDuration = Number.parseFloat(sel.dataAttributes?.duration ?? "1") || 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { memo } from "react";
|
||||
import { Clock, Eye, Layers, MessageSquare, Move, X } from "../../icons/SystemIcons";
|
||||
import { Eye, Layers, MessageSquare, Move, X } from "../../icons/SystemIcons";
|
||||
import { type DomEditSelection } from "./domEditing";
|
||||
import { readStudioBoxSize, readStudioPathOffset, readStudioRotation } from "./manualEdits";
|
||||
import type { ImportedFontAsset } from "./fontAssets";
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { MetricField, Section } from "./propertyPanelPrimitives";
|
||||
import { isMediaElement, MediaSection } from "./propertyPanelMediaSection";
|
||||
import { TextSection, StyleSections } from "./propertyPanelSections";
|
||||
import { TimingSection } from "./propertyPanelTimingSection";
|
||||
import { GsapAnimationSection } from "./GsapAnimationSection";
|
||||
import { KeyframeNavigation } from "./KeyframeNavigation";
|
||||
import { STUDIO_GSAP_PANEL_ENABLED, STUDIO_KEYFRAMES_ENABLED } from "./manualEditingAvailability";
|
||||
@@ -84,70 +85,6 @@ interface PropertyPanelProps {
|
||||
onSeekToTime?: (time: number) => void;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* TimingSection */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function formatTimingValue(seconds: number): string {
|
||||
if (!Number.isFinite(seconds) || seconds < 0) return "0.00s";
|
||||
return `${seconds.toFixed(2)}s`;
|
||||
}
|
||||
|
||||
function parseTimingValue(input: string): number | null {
|
||||
const cleaned = input.replace(/s$/i, "").trim();
|
||||
const parsed = Number.parseFloat(cleaned);
|
||||
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
||||
}
|
||||
|
||||
function TimingSection({
|
||||
element,
|
||||
onSetAttribute,
|
||||
}: {
|
||||
element: DomEditSelection;
|
||||
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
|
||||
}) {
|
||||
const start = Number.parseFloat(element.dataAttributes.start ?? "0") || 0;
|
||||
const duration =
|
||||
Number.parseFloat(
|
||||
element.dataAttributes.duration ?? element.dataAttributes["hf-authored-duration"] ?? "0",
|
||||
) || 0;
|
||||
const end = start + duration;
|
||||
|
||||
const commitStart = (nextValue: string) => {
|
||||
const parsed = parseTimingValue(nextValue);
|
||||
if (parsed == null) return;
|
||||
void onSetAttribute("start", parsed.toFixed(2));
|
||||
};
|
||||
|
||||
const commitDuration = (nextValue: string) => {
|
||||
const parsed = parseTimingValue(nextValue);
|
||||
if (parsed == null || parsed <= 0) return;
|
||||
void onSetAttribute("duration", parsed.toFixed(2));
|
||||
};
|
||||
|
||||
const commitEnd = (nextValue: string) => {
|
||||
const parsed = parseTimingValue(nextValue);
|
||||
if (parsed == null || parsed <= start) return;
|
||||
void onSetAttribute("duration", (parsed - start).toFixed(2));
|
||||
};
|
||||
|
||||
return (
|
||||
<Section title="Timing" icon={<Clock size={15} />}>
|
||||
<div className={RESPONSIVE_GRID}>
|
||||
<MetricField label="Start" value={formatTimingValue(start)} onCommit={commitStart} />
|
||||
<MetricField label="End" value={formatTimingValue(end)} onCommit={commitEnd} />
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<MetricField
|
||||
label="Duration"
|
||||
value={formatTimingValue(duration)}
|
||||
onCommit={commitDuration}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* PropertyPanel */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Clock } from "../../icons/SystemIcons";
|
||||
import type { DomEditSelection } from "./domEditing";
|
||||
import { RESPONSIVE_GRID } from "./propertyPanelHelpers";
|
||||
import { MetricField, Section } from "./propertyPanelPrimitives";
|
||||
|
||||
function formatTimingValue(seconds: number): string {
|
||||
if (!Number.isFinite(seconds) || seconds < 0) return "0.00s";
|
||||
return `${seconds.toFixed(2)}s`;
|
||||
}
|
||||
|
||||
function parseTimingValue(input: string): number | null {
|
||||
const cleaned = input.replace(/s$/i, "").trim();
|
||||
const parsed = Number.parseFloat(cleaned);
|
||||
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
||||
}
|
||||
|
||||
export function TimingSection({
|
||||
element,
|
||||
onSetAttribute,
|
||||
}: {
|
||||
element: DomEditSelection;
|
||||
onSetAttribute: (attr: string, value: string) => void | Promise<void>;
|
||||
}) {
|
||||
const start = Number.parseFloat(element.dataAttributes.start ?? "0") || 0;
|
||||
const duration =
|
||||
Number.parseFloat(
|
||||
element.dataAttributes.duration ?? element.dataAttributes["hf-authored-duration"] ?? "0",
|
||||
) || 0;
|
||||
const end = start + duration;
|
||||
|
||||
const commitStart = (nextValue: string) => {
|
||||
const parsed = parseTimingValue(nextValue);
|
||||
if (parsed == null) return;
|
||||
void onSetAttribute("start", parsed.toFixed(2));
|
||||
};
|
||||
|
||||
const commitDuration = (nextValue: string) => {
|
||||
const parsed = parseTimingValue(nextValue);
|
||||
if (parsed == null || parsed <= 0) return;
|
||||
void onSetAttribute("duration", parsed.toFixed(2));
|
||||
};
|
||||
|
||||
const commitEnd = (nextValue: string) => {
|
||||
const parsed = parseTimingValue(nextValue);
|
||||
if (parsed == null || parsed <= start) return;
|
||||
void onSetAttribute("duration", (parsed - start).toFixed(2));
|
||||
};
|
||||
|
||||
return (
|
||||
<Section title="Timing" icon={<Clock size={15} />}>
|
||||
<div className={RESPONSIVE_GRID}>
|
||||
<MetricField label="Start" value={formatTimingValue(start)} onCommit={commitStart} />
|
||||
<MetricField label="End" value={formatTimingValue(end)} onCommit={commitEnd} />
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<MetricField
|
||||
label="Duration"
|
||||
value={formatTimingValue(duration)}
|
||||
onCommit={commitDuration}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user