Skip to content

Commit 772d6c7

Browse files
Merge pull request #1025 from heygen-com/feat/studio-catalog-panel
feat(studio): catalog panel with Ask agent, block preview, and composition context
2 parents 13be10a + 0cc012c commit 772d6c7

13 files changed

Lines changed: 834 additions & 399 deletions

File tree

packages/cli/src/server/studioServer.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,38 @@ export function createStudioServer(options: StudioServerOptions): StudioServer {
398398
async installRegistryBlock(opts) {
399399
const { resolveItem } = await import("../registry/resolver.js");
400400
const { installItem } = await import("../registry/installer.js");
401+
const { readFileSync, writeFileSync, existsSync } = await import("node:fs");
402+
const { join } = await import("node:path");
401403
const item = await resolveItem(opts.blockName);
402404
const { written } = await installItem(item, { destDir: opts.project.dir });
405+
406+
const indexPath = join(opts.project.dir, "index.html");
407+
if (existsSync(indexPath)) {
408+
const indexHtml = readFileSync(indexPath, "utf-8");
409+
const hostW = indexHtml.match(/data-width="(\d+)"/)?.[1];
410+
const hostH = indexHtml.match(/data-height="(\d+)"/)?.[1];
411+
if (hostW && hostH) {
412+
for (const absPath of written) {
413+
if (!absPath.endsWith(".html")) continue;
414+
let content = readFileSync(absPath, "utf-8");
415+
content = content.replace(
416+
/(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+/i,
417+
`$1${hostW}$2${hostH}`,
418+
);
419+
content = content.replace(
420+
/(\bwidth:\s*)\d+(px;\s*\n?\s*height:\s*)\d+(px;)/g,
421+
(match, pre, mid, post) => {
422+
if (match.includes("1920") || match.includes("1080")) {
423+
return `${pre}${hostW}${mid}${hostH}${post}`;
424+
}
425+
return match;
426+
},
427+
);
428+
writeFileSync(absPath, content, "utf-8");
429+
}
430+
}
431+
}
432+
403433
const relativePaths = written.map((abs) => {
404434
const rel = abs.startsWith(opts.project.dir) ? abs.slice(opts.project.dir.length + 1) : abs;
405435
return rel;

packages/studio/src/components/StudioRightPanel.tsx

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Tooltip } from "./ui";
12
import { PropertyPanel } from "./editor/PropertyPanel";
23
import { MotionPanel } from "./editor/MotionPanel";
34
import { LayersPanel } from "./editor/LayersPanel";
@@ -106,54 +107,62 @@ export function StudioRightPanel({
106107
<div className="flex items-center gap-1 border-b border-neutral-800 px-3 py-2">
107108
{STUDIO_INSPECTOR_PANELS_ENABLED && (
108109
<>
109-
<button
110-
type="button"
111-
onClick={() => setRightPanelTab("design")}
112-
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
113-
rightPanelTab === "design"
114-
? "bg-neutral-800 text-white"
115-
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
116-
}`}
117-
>
118-
Design
119-
</button>
120-
<button
121-
type="button"
122-
onClick={() => setRightPanelTab("layers")}
123-
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
124-
rightPanelTab === "layers"
125-
? "bg-neutral-800 text-white"
126-
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
127-
}`}
128-
>
129-
Layers
130-
</button>
131-
{STUDIO_MOTION_PANEL_ENABLED && (
110+
<Tooltip label="Element styles and properties" side="bottom">
111+
<button
112+
type="button"
113+
onClick={() => setRightPanelTab("design")}
114+
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
115+
rightPanelTab === "design"
116+
? "bg-neutral-800 text-white"
117+
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
118+
}`}
119+
>
120+
Design
121+
</button>
122+
</Tooltip>
123+
<Tooltip label="Composition layer stack" side="bottom">
132124
<button
133125
type="button"
134-
onClick={() => setRightPanelTab("motion")}
126+
onClick={() => setRightPanelTab("layers")}
135127
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
136-
rightPanelTab === "motion"
128+
rightPanelTab === "layers"
137129
? "bg-neutral-800 text-white"
138130
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
139131
}`}
140132
>
141-
Motion
133+
Layers
142134
</button>
135+
</Tooltip>
136+
{STUDIO_MOTION_PANEL_ENABLED && (
137+
<Tooltip label="Animation and motion" side="bottom">
138+
<button
139+
type="button"
140+
onClick={() => setRightPanelTab("motion")}
141+
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
142+
rightPanelTab === "motion"
143+
? "bg-neutral-800 text-white"
144+
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
145+
}`}
146+
>
147+
Motion
148+
</button>
149+
</Tooltip>
143150
)}
144151
</>
145152
)}
146-
<button
147-
type="button"
148-
onClick={() => setRightPanelTab("renders")}
149-
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
150-
rightPanelTab === "renders"
151-
? "bg-neutral-800 text-white"
152-
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
153-
}`}
154-
>
155-
{renderJobs.length > 0 ? `Renders (${renderJobs.length})` : "Renders"}
156-
</button>
153+
<Tooltip label="Render queue and exports" side="bottom">
154+
<button
155+
type="button"
156+
onClick={() => setRightPanelTab("renders")}
157+
className={`h-8 rounded-xl px-3 text-[11px] font-medium transition-colors ${
158+
rightPanelTab === "renders"
159+
? "bg-neutral-800 text-white"
160+
: "text-neutral-500 hover:bg-neutral-800/70 hover:text-neutral-200"
161+
}`}
162+
>
163+
{renderJobs.length > 0 ? `Renders (${renderJobs.length})` : "Renders"}
164+
</button>
165+
</Tooltip>
157166
</div>
158167
<div className="min-h-0 flex-1">
159168
{rightPanelTab === "block-params" && activeBlockParams ? (

packages/studio/src/components/TimelineToolbar.tsx

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from "../player/components/timelineZoom";
55
import { getTimelineToggleTitle } from "../utils/timelineDiscovery";
66
import { usePlayerStore } from "../player";
7+
import { Tooltip } from "./ui";
78

89
interface TimelineToolbarProps {
910
toggleTimelineVisibility: () => void;
@@ -23,65 +24,71 @@ export function TimelineToolbar({ toggleTimelineVisibility }: TimelineToolbarPro
2324
Timeline
2425
</div>
2526
<div className="flex items-center gap-1">
26-
<button
27-
type="button"
28-
onClick={() => setZoomMode("fit")}
29-
className={`h-7 px-2.5 rounded-md border text-[11px] font-medium transition-colors ${
30-
zoomMode === "fit"
31-
? "border-studio-accent/30 bg-studio-accent/10 text-studio-accent"
32-
: "border-neutral-800 text-neutral-400 hover:border-neutral-700 hover:text-neutral-200"
33-
}`}
34-
title="Fit timeline to width"
35-
>
36-
Fit
37-
</button>
38-
<button
39-
type="button"
40-
onClick={() => {
41-
setZoomMode("manual");
42-
setManualZoomPercent(getNextTimelineZoomPercent("out", zoomMode, manualZoomPercent));
43-
}}
44-
className="h-7 w-7 rounded-md border border-neutral-800 text-neutral-400 transition-colors hover:border-neutral-700 hover:text-neutral-200"
45-
title="Zoom out"
46-
>
47-
-
48-
</button>
27+
<Tooltip label="Fit timeline to width">
28+
<button
29+
type="button"
30+
onClick={() => setZoomMode("fit")}
31+
className={`h-7 px-2.5 rounded-md border text-[11px] font-medium transition-colors ${
32+
zoomMode === "fit"
33+
? "border-studio-accent/30 bg-studio-accent/10 text-studio-accent"
34+
: "border-neutral-800 text-neutral-400 hover:border-neutral-700 hover:text-neutral-200"
35+
}`}
36+
>
37+
Fit
38+
</button>
39+
</Tooltip>
40+
<Tooltip label="Zoom out">
41+
<button
42+
type="button"
43+
onClick={() => {
44+
setZoomMode("manual");
45+
setManualZoomPercent(
46+
getNextTimelineZoomPercent("out", zoomMode, manualZoomPercent),
47+
);
48+
}}
49+
className="h-7 w-7 rounded-md border border-neutral-800 text-neutral-400 transition-colors hover:border-neutral-700 hover:text-neutral-200"
50+
>
51+
-
52+
</button>
53+
</Tooltip>
4954
<div className="min-w-[58px] text-center text-[10px] font-medium tabular-nums text-neutral-500">
5055
{`${displayedTimelineZoomPercent}%`}
5156
</div>
52-
<button
53-
type="button"
54-
onClick={() => {
55-
setZoomMode("manual");
56-
setManualZoomPercent(getNextTimelineZoomPercent("in", zoomMode, manualZoomPercent));
57-
}}
58-
className="h-7 w-7 rounded-md border border-neutral-800 text-neutral-400 transition-colors hover:border-neutral-700 hover:text-neutral-200"
59-
title="Zoom in"
60-
>
61-
+
62-
</button>
63-
<button
64-
type="button"
65-
onClick={toggleTimelineVisibility}
66-
className="ml-1 flex h-7 w-7 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-neutral-900 hover:text-neutral-200"
67-
title={getTimelineToggleTitle(true)}
68-
aria-label="Hide timeline editor"
69-
>
70-
<svg
71-
width="14"
72-
height="14"
73-
viewBox="0 0 24 24"
74-
fill="none"
75-
stroke="currentColor"
76-
strokeWidth="1.8"
77-
strokeLinecap="round"
78-
strokeLinejoin="round"
79-
aria-hidden="true"
57+
<Tooltip label="Zoom in">
58+
<button
59+
type="button"
60+
onClick={() => {
61+
setZoomMode("manual");
62+
setManualZoomPercent(getNextTimelineZoomPercent("in", zoomMode, manualZoomPercent));
63+
}}
64+
className="h-7 w-7 rounded-md border border-neutral-800 text-neutral-400 transition-colors hover:border-neutral-700 hover:text-neutral-200"
65+
>
66+
+
67+
</button>
68+
</Tooltip>
69+
<Tooltip label={getTimelineToggleTitle(true)}>
70+
<button
71+
type="button"
72+
onClick={toggleTimelineVisibility}
73+
className="ml-1 flex h-7 w-7 items-center justify-center rounded-md text-neutral-500 transition-colors hover:bg-neutral-900 hover:text-neutral-200"
74+
aria-label="Hide timeline editor"
8075
>
81-
<path d="M5 7h14" />
82-
<path d="m8 11 4 4 4-4" />
83-
</svg>
84-
</button>
76+
<svg
77+
width="14"
78+
height="14"
79+
viewBox="0 0 24 24"
80+
fill="none"
81+
stroke="currentColor"
82+
strokeWidth="1.8"
83+
strokeLinecap="round"
84+
strokeLinejoin="round"
85+
aria-hidden="true"
86+
>
87+
<path d="M5 7h14" />
88+
<path d="m8 11 4 4 4-4" />
89+
</svg>
90+
</button>
91+
</Tooltip>
8592
</div>
8693
</div>
8794
</div>

packages/studio/src/components/nle/NLEPreview.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,16 @@ export const NLEPreview = memo(function NLEPreview({
391391
}}
392392
data-testid="preview-zoom-stage"
393393
>
394+
{directUrl?.includes("/components/") && (
395+
<Player
396+
key={`backdrop-${projectId}`}
397+
projectId={projectId}
398+
onLoad={() => {}}
399+
portrait={portrait}
400+
suppressLoadingOverlay
401+
style={{ position: "absolute", inset: 0, zIndex: 0 }}
402+
/>
403+
)}
394404
<Player
395405
key={activeKey}
396406
ref={iframeRef}
@@ -403,6 +413,11 @@ export const NLEPreview = memo(function NLEPreview({
403413
onCompositionLoadingChange={onCompositionLoadingChange}
404414
portrait={portrait}
405415
suppressLoadingOverlay={suppressLoadingOverlay}
416+
style={
417+
directUrl?.includes("/components/")
418+
? { position: "absolute", inset: 0, zIndex: 1 }
419+
: undefined
420+
}
406421
/>
407422
</div>
408423
</div>

0 commit comments

Comments
 (0)