diff --git a/studio/frontend/.gitignore b/studio/frontend/.gitignore
index ec9ec8d674..7358cc82ad 100644
--- a/studio/frontend/.gitignore
+++ b/studio/frontend/.gitignore
@@ -26,3 +26,4 @@ test/
*.njsproj
*.sln
*.sw?
+/src/features/canvas-lab/AGENTS.md
diff --git a/studio/frontend/src/features/canvas-lab/api/index.ts b/studio/frontend/src/features/canvas-lab/api/index.ts
index c04efb0be6..ea4890ba57 100644
--- a/studio/frontend/src/features/canvas-lab/api/index.ts
+++ b/studio/frontend/src/features/canvas-lab/api/index.ts
@@ -1,4 +1,4 @@
-const DEFAULT_BASE = "http://127.0.0.1:8000";
+const DEFAULT_BASE = "/preview-api";
export const CANVAS_LAB_API_BASE =
import.meta.env.VITE_DATA_DESIGNER_API ?? DEFAULT_BASE;
diff --git a/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx b/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx
index 2449f4831a..3a4a74a6ce 100644
--- a/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx
+++ b/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx
@@ -1,7 +1,6 @@
import {
Background,
BackgroundVariant,
- Controls,
type Edge,
type EdgeChange,
type EdgeTypes,
@@ -26,9 +25,11 @@ import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EyeIcon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
+import { Lock, LockOpen, Maximize2, Minus, Plus } from "lucide-react";
import { previewCanvas } from "./api";
import { CanvasAuxNode, type CanvasAuxNodeData } from "./components/canvas-aux-node";
import { BlockSheet } from "./components/block-sheet";
+import { CANVAS_FLOATING_ICON_BUTTON_CLASS } from "./components/floating-icon-button-class";
import { CanvasNode } from "./components/canvas-node";
import { CanvasSemanticEdge } from "./components/canvas-semantic-edge";
import { DataEdge } from "./components/rf-ui/data-edge";
@@ -56,6 +57,11 @@ type LayoutControlsProps = {
onToggleDirection: () => void;
};
+type ViewportControlsProps = {
+ interactive: boolean;
+ onToggleInteractive: () => void;
+};
+
type InternalsSyncProps = {
nodeIds: string[];
};
@@ -125,6 +131,74 @@ function LayoutControls({
);
}
+function ViewportControls({
+ interactive,
+ onToggleInteractive,
+}: ViewportControlsProps): ReactElement {
+ const { zoomIn, zoomOut, fitView } = useReactFlow();
+
+ const handleZoomIn = useCallback(() => {
+ zoomIn({ duration: 150 });
+ }, [zoomIn]);
+
+ const handleZoomOut = useCallback(() => {
+ zoomOut({ duration: 150 });
+ }, [zoomOut]);
+
+ const handleFitView = useCallback(() => {
+ fitView({ duration: 250 });
+ }, [fitView]);
+
+ return (
+
+
+
+
+
+
+ );
+}
+
export function CanvasLabPage(): ReactElement {
const {
nodes,
@@ -200,6 +274,7 @@ export function CanvasLabPage(): ReactElement {
const [copied, setCopied] = useState(false);
const [importOpen, setImportOpen] = useState(false);
const [processorsOpen, setProcessorsOpen] = useState(false);
+ const [interactive, setInteractive] = useState(true);
const [statusMessage, setStatusMessage] = useState<{
tone: "success" | "error";
text: string;
@@ -481,6 +556,9 @@ export function CanvasLabPage(): ReactElement {
onConnect={onConnect}
onNodeClick={handleNodeClick}
isValidConnection={isValidConnection}
+ nodesDraggable={interactive}
+ nodesConnectable={interactive}
+ elementsSelectable={interactive}
fitView={true}
className="h-full w-full"
>
@@ -512,7 +590,10 @@ export function CanvasLabPage(): ReactElement {
onImport={() => setImportOpen(true)}
/>
-
+ setInteractive((value) => !value)}
+ />
diff --git a/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx b/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx
index 65ddb19662..bb27ba4d5d 100644
--- a/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx
+++ b/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx
@@ -18,6 +18,7 @@ import {
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import type { ReactElement } from "react";
+import { CANVAS_FLOATING_ICON_BUTTON_CLASS } from "./floating-icon-button-class";
import type { LlmType, SamplerType } from "../types";
import { BLOCK_GROUPS, getBlocksForKind } from "../blocks/registry";
@@ -118,9 +119,6 @@ function BlockSheetButton({
);
}
-const FLOATING_ICON_BUTTON_CLASS =
- "corner-squircle group h-11 w-11 rounded-xl border border-border/60 bg-transparent p-0 text-muted-foreground hover:bg-transparent hover:text-primary hover:border-primary/60";
-
export function BlockSheet({
container,
view,
@@ -146,7 +144,11 @@ export function BlockSheet({
}}
>
-