From e0404966d7b45397b2d5b6b2ef9ca4694366c576 Mon Sep 17 00:00:00 2001 From: shine1i Date: Fri, 6 Feb 2026 11:28:32 +0100 Subject: [PATCH] ui buttons move, squircle boxes, and resize --- .../features/canvas-lab/canvas-lab-page.tsx | 27 +-- .../canvas-lab/components/block-sheet.tsx | 213 +++++++++++------- .../canvas-lab/components/canvas-node.tsx | 27 ++- .../canvas-lab/components/rf-ui/data-edge.tsx | 59 ++++- 4 files changed, 215 insertions(+), 111 deletions(-) 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 5a5722bdca..9dc81a9849 100644 --- a/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx +++ b/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx @@ -126,6 +126,7 @@ export function CanvasLabPage(): ReactElement { null, ); const [previewLoading, setPreviewLoading] = useState(false); + const [copied, setCopied] = useState(false); const [importOpen, setImportOpen] = useState(false); const [processorsOpen, setProcessorsOpen] = useState(false); const [statusMessage, setStatusMessage] = useState<{ @@ -184,6 +185,7 @@ export function CanvasLabPage(): ReactElement { }; const handleCopyRecipe = async (): Promise => { + setCopied(false); setStatusMessage(null); const { payload, errors } = buildCanvasPayload( configs, @@ -207,6 +209,8 @@ export function CanvasLabPage(): ReactElement { } try { await navigator.clipboard.writeText(JSON.stringify(payload, null, 2)); + setCopied(true); + window.setTimeout(() => setCopied(false), 1500); setStatusMessage({ tone: "success", text: "Recipe copied to clipboard.", @@ -265,24 +269,6 @@ export function CanvasLabPage(): ReactElement { )}
- - - - + { + if (open) { + onViewChange("root"); + } + }} > - -
- {view !== "root" && ( - - )} - {title} -
-
-
-
- {view === "root" && - ROOT_GROUPS.map((item, index) => ( - onViewChange(item.kind)} - /> - ))} - {view === "processor" && ( - - )} - {view !== "root" && - view !== "processor" && - getBlocksForKind(VIEW_KIND[view] ?? "sampler").map( - (item, index) => ( - { - if (item.kind === "sampler") { - onAddSampler(item.type as SamplerType); - } else if (item.kind === "llm") { - if (item.type === "model_provider") { - onAddModelProvider(); - } else if (item.type === "model_config") { - onAddModelConfig(); - } else { - onAddLlm(item.type as LlmType); - } - } else { - onAddExpression(); - } - }} - /> - ), + + + + + +
+ {view !== "root" && ( + )} + {title} +
+
+
+
+ {view === "root" && + ROOT_GROUPS.map((item, index) => ( + onViewChange(item.kind)} + /> + ))} + {view === "processor" && ( + + )} + {view !== "root" && + view !== "processor" && + getBlocksForKind(VIEW_KIND[view] ?? "sampler").map( + (item, index) => ( + { + if (item.kind === "sampler") { + onAddSampler(item.type as SamplerType); + } else if (item.kind === "llm") { + if (item.type === "model_provider") { + onAddModelProvider(); + } else if (item.type === "model_config") { + onAddModelConfig(); + } else { + onAddLlm(item.type as LlmType); + } + } else { + onAddExpression(); + } + }} + /> + ), + )} +
-
- - + + + + +
); } diff --git a/studio/frontend/src/features/canvas-lab/components/canvas-node.tsx b/studio/frontend/src/features/canvas-lab/components/canvas-node.tsx index d5540da30a..cd1dcc5e1d 100644 --- a/studio/frontend/src/features/canvas-lab/components/canvas-node.tsx +++ b/studio/frontend/src/features/canvas-lab/components/canvas-node.tsx @@ -18,7 +18,12 @@ import { UserAccountIcon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; -import { Position, useUpdateNodeInternals, type NodeProps } from "@xyflow/react"; +import { + NodeResizer, + Position, + useUpdateNodeInternals, + type NodeProps, +} from "@xyflow/react"; import { memo, type ReactElement, useEffect } from "react"; import { useCanvasLabStore } from "../stores/canvas-lab"; import type { @@ -223,7 +228,11 @@ function renderInlineEditor( return null; } -function CanvasNodeBase({ id, data }: NodeProps): ReactElement { +function CanvasNodeBase({ + id, + data, + selected, +}: NodeProps): ReactElement { const meta = NODE_META[data.kind]; const icon = resolveNodeIcon(data.kind, data.blockType); const layoutDirection = data.layoutDirection ?? "LR"; @@ -251,7 +260,19 @@ function CanvasNodeBase({ id, data }: NodeProps): ReactElement { const summary = getConfigSummary(config); return ( - + +
= Edge<{ key?: keyof T["data"]; - path?: "bezier" | "smoothstep" | "step" | "straight"; + path?: "auto" | "bezier" | "smoothstep" | "step" | "straight"; }>; export function DataEdge({ - data = { path: "bezier" }, + data = { path: "auto" }, id, markerEnd, source, @@ -31,8 +31,17 @@ export function DataEdge({ targetY, }: EdgeProps): ReactElement { const nodeData = useStore((state) => state.nodeLookup.get(source)?.data); + const resolvedPathType = resolvePathType({ + type: data.path ?? "auto", + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + }); const [edgePath, labelX, labelY] = getPath({ - type: data.path ?? "bezier", + type: resolvedPathType, sourceX, sourceY, sourcePosition, @@ -128,3 +137,47 @@ function getPath({ targetY, }); } + +function resolvePathType({ + type, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, +}: { + type: "auto" | "bezier" | "smoothstep" | "step" | "straight"; + sourceX: number; + sourceY: number; + targetX: number; + targetY: number; + sourcePosition: Position; + targetPosition: Position; +}): "bezier" | "smoothstep" | "step" | "straight" { + if (type !== "auto") { + return type; + } + + const isVerticalFlow = + (sourcePosition === Position.Bottom && targetPosition === Position.Top) || + (sourcePosition === Position.Top && targetPosition === Position.Bottom); + if (isVerticalFlow && Math.abs(sourceX - targetX) <= 18) { + return "straight"; + } + + const isHorizontalFlow = + (sourcePosition === Position.Right && targetPosition === Position.Left) || + (sourcePosition === Position.Left && targetPosition === Position.Right); + if (isHorizontalFlow && Math.abs(sourceY - targetY) <= 18) { + return "straight"; + } + + const deltaX = Math.abs(sourceX - targetX); + const deltaY = Math.abs(sourceY - targetY); + if (deltaX < 40 || deltaY < 40) { + return "smoothstep"; + } + + return "bezier"; +}