diff --git a/studio/frontend/src/features/recipe-studio/blocks/definitions.ts b/studio/frontend/src/features/recipe-studio/blocks/definitions.ts
index e4dbd38e2a..889061c931 100644
--- a/studio/frontend/src/features/recipe-studio/blocks/definitions.ts
+++ b/studio/frontend/src/features/recipe-studio/blocks/definitions.ts
@@ -72,7 +72,7 @@ export type BlockDefinition = {
export const BLOCK_GROUPS: BlockGroup[] = [
{
kind: "sampler",
- title: "Sampler",
+ title: "Samplers",
description: "Numeric + categorical blocks.",
icon: DiceFaces03Icon,
},
@@ -84,8 +84,8 @@ export const BLOCK_GROUPS: BlockGroup[] = [
},
{
kind: "llm",
- title: "LLM",
- description: "Text + structured blocks.",
+ title: "LLM + Models",
+ description: "Generation, providers, and model aliases.",
icon: PencilEdit02Icon,
},
{
@@ -182,7 +182,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "sampler",
type: "person",
title: "Person",
- description: "Synthetic person sampler.",
+ description: "Faker person sampler.",
icon: UserAccountIcon,
dialogKey: "person",
createConfig: (id, existing) => makeSamplerConfig(id, "person", existing),
@@ -227,7 +227,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "model_provider",
title: "Model Provider",
- description: "Configure API endpoint + key.",
+ description: "Endpoint, auth, and provider settings.",
icon: Shield02Icon,
dialogKey: "model_provider",
createConfig: (id, existing) => makeModelProviderConfig(id, existing),
@@ -236,7 +236,7 @@ const BLOCK_DEFINITIONS: BlockDefinition[] = [
kind: "llm",
type: "model_config",
title: "Model Config",
- description: "Alias + model + inference params.",
+ description: "Alias, model, provider, and inference params.",
icon: Plant01Icon,
dialogKey: "model_config",
createConfig: (id, existing) => makeModelConfig(id, existing),
@@ -291,4 +291,3 @@ export function getBlockDefinitionForConfig(
}
return getBlockDefinition("expression", "expression");
}
-
diff --git a/studio/frontend/src/features/recipe-studio/blocks/render-dialog.tsx b/studio/frontend/src/features/recipe-studio/blocks/render-dialog.tsx
index bcfdd3b0a8..9192bb1cac 100644
--- a/studio/frontend/src/features/recipe-studio/blocks/render-dialog.tsx
+++ b/studio/frontend/src/features/recipe-studio/blocks/render-dialog.tsx
@@ -87,6 +87,7 @@ export function renderBlockDialog(
) : null;
diff --git a/studio/frontend/src/features/recipe-studio/components/block-sheet.tsx b/studio/frontend/src/features/recipe-studio/components/block-sheet.tsx
index d5c6d95e7a..abb4112444 100644
--- a/studio/frontend/src/features/recipe-studio/components/block-sheet.tsx
+++ b/studio/frontend/src/features/recipe-studio/components/block-sheet.tsx
@@ -36,6 +36,8 @@ type BlockSheetProps = {
container: HTMLDivElement | null;
sheetView: SheetView;
onViewChange: (sheetView: SheetView) => void;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
onAddSampler: (type: SamplerType) => void;
onAddSeed: () => void;
onAddLlm: (type: LlmType) => void;
@@ -128,6 +130,8 @@ export function BlockSheet({
container,
sheetView,
onViewChange,
+ open,
+ onOpenChange,
onAddSampler,
onAddSeed,
onAddLlm,
@@ -140,16 +144,26 @@ export function BlockSheet({
onImport,
}: BlockSheetProps): ReactElement {
const sheetTitle = getSheetTitle(sheetView);
- const [open, setOpen] = useState(false);
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
const expressionBlocks = useMemo(() => getBlocksForKind("expression"), []);
const seedBlocks = useMemo(() => getBlocksForKind("seed"), []);
+ const isControlled = typeof open === "boolean";
+ const sheetOpen = isControlled ? (open as boolean) : uncontrolledOpen;
+
+ const setSheetOpen = (nextOpen: boolean) => {
+ if (!isControlled) {
+ setUncontrolledOpen(nextOpen);
+ }
+ onOpenChange?.(nextOpen);
+ };
+
return (
{
- setOpen(open);
- if (open) {
+ open={sheetOpen}
+ onOpenChange={(nextOpen) => {
+ setSheetOpen(nextOpen);
+ if (nextOpen) {
onViewChange("root");
}
}}
@@ -201,17 +215,17 @@ export function BlockSheet({
isActive={index === 0}
onClick={() => {
if (item.kind === "processor") {
- setOpen(false);
+ setSheetOpen(false);
onOpenProcessors();
return;
}
if (item.kind === "seed" && seedBlocks.length === 1) {
- setOpen(false);
+ setSheetOpen(false);
onAddSeed();
return;
}
if (item.kind === "expression" && expressionBlocks.length === 1) {
- setOpen(false);
+ setSheetOpen(false);
onAddExpression();
return;
}
diff --git a/studio/frontend/src/features/recipe-studio/components/recipe-graph-node.tsx b/studio/frontend/src/features/recipe-studio/components/recipe-graph-node.tsx
index 1afd16c22c..41324d9631 100644
--- a/studio/frontend/src/features/recipe-studio/components/recipe-graph-node.tsx
+++ b/studio/frontend/src/features/recipe-studio/components/recipe-graph-node.tsx
@@ -253,13 +253,13 @@ function LlmInputHandles({ items, isTopBottom }: LlmInputHandlesProps): ReactEle
key={item.id}
className="pointer-events-none relative flex min-w-[80px] flex-1 justify-center pt-2"
>
-
+
{item.label}
))}
@@ -275,7 +275,7 @@ function LlmInputHandles({ items, isTopBottom }: LlmInputHandlesProps): ReactEle
id={item.id}
type="target"
position={Position.Left}
- className="pointer-events-auto !size-2 !border-border !bg-background"
+ className="pointer-events-auto !size-2.5 !border-border/80 !bg-muted shadow-sm hover:!border-primary/70 hover:!bg-primary/20"
style={{ left: -3, top: "50%", transform: "translate(-50%, -50%)" }}
/>
@@ -383,7 +383,7 @@ function RecipeGraphNodeBase({
position={dataInPosition}
className="absolute inset-0 pointer-events-none"
labelClassName="sr-only"
- handleClassName="pointer-events-auto !size-2 !border-border !bg-background"
+ handleClassName="pointer-events-auto !size-2.5 !border-border/80 !bg-muted shadow-sm hover:!border-primary/70 hover:!bg-primary/20"
/>
>
)}
@@ -405,7 +405,7 @@ function RecipeGraphNodeBase({
position={semanticInPosition}
className="absolute inset-0 pointer-events-none"
labelClassName="sr-only"
- handleClassName="pointer-events-auto !size-2 !border-border !bg-background"
+ handleClassName="pointer-events-auto !size-2.5 !border-border/80 !bg-muted shadow-sm hover:!border-primary/70 hover:!bg-primary/20"
/>
)}
@@ -417,7 +417,7 @@ function RecipeGraphNodeBase({
position={semanticOutPosition}
className="absolute inset-0 pointer-events-none"
labelClassName="sr-only"
- handleClassName="pointer-events-auto !size-2 !border-border !bg-background"
+ handleClassName="pointer-events-auto !size-2.5 !border-border/80 !bg-muted shadow-sm hover:!border-primary/70 hover:!bg-primary/20"
/>
)}
diff --git a/studio/frontend/src/features/recipe-studio/components/recipe-graph-semantic-edge.tsx b/studio/frontend/src/features/recipe-studio/components/recipe-graph-semantic-edge.tsx
index 14a1f98dea..16ecc33553 100644
--- a/studio/frontend/src/features/recipe-studio/components/recipe-graph-semantic-edge.tsx
+++ b/studio/frontend/src/features/recipe-studio/components/recipe-graph-semantic-edge.tsx
@@ -11,6 +11,7 @@ export const RecipeGraphSemanticEdge = memo(function RecipeGraphSemanticEdge({
targetPosition,
style,
markerEnd,
+ selected,
}: EdgeProps): ReactElement {
const [path] = getSmoothStepPath({
sourceX,
@@ -29,9 +30,12 @@ export const RecipeGraphSemanticEdge = memo(function RecipeGraphSemanticEdge({
path={path}
markerEnd={markerEnd}
style={{
- strokeDasharray: "4 4",
- strokeWidth: 1.5,
- stroke: "var(--muted-foreground)",
+ strokeDasharray: selected ? "7 5" : "6 5",
+ strokeWidth: selected ? 2.3 : 1.8,
+ stroke: selected
+ ? "hsl(var(--primary) / 0.9)"
+ : "hsl(var(--foreground) / 0.38)",
+ opacity: selected ? 1 : 0.92,
...style,
}}
/>
diff --git a/studio/frontend/src/features/recipe-studio/components/rf-ui/base-handle.tsx b/studio/frontend/src/features/recipe-studio/components/rf-ui/base-handle.tsx
index 4ab56ed844..1901b141f1 100644
--- a/studio/frontend/src/features/recipe-studio/components/rf-ui/base-handle.tsx
+++ b/studio/frontend/src/features/recipe-studio/components/rf-ui/base-handle.tsx
@@ -14,7 +14,7 @@ export function BaseHandle({
diff --git a/studio/frontend/src/features/recipe-studio/components/rf-ui/data-edge.tsx b/studio/frontend/src/features/recipe-studio/components/rf-ui/data-edge.tsx
index 3364179288..e4c9bc2deb 100644
--- a/studio/frontend/src/features/recipe-studio/components/rf-ui/data-edge.tsx
+++ b/studio/frontend/src/features/recipe-studio/components/rf-ui/data-edge.tsx
@@ -17,6 +17,7 @@ export function DataEdge({
data = { path: "auto" },
id,
markerEnd,
+ selected,
sourcePosition,
sourceX,
sourceY,
@@ -44,8 +45,17 @@ export function DataEdge({
targetPosition,
});
+ const edgeStyle = {
+ stroke: selected
+ ? "hsl(var(--primary) / 0.92)"
+ : "hsl(var(--foreground) / 0.42)",
+ strokeWidth: selected ? 2.6 : 2.1,
+ opacity: selected ? 1 : 0.92,
+ ...style,
+ };
+
return (
-
+
);
}
diff --git a/studio/frontend/src/features/recipe-studio/dialogs/config-dialog.tsx b/studio/frontend/src/features/recipe-studio/dialogs/config-dialog.tsx
index fe50734eb6..d0680641fa 100644
--- a/studio/frontend/src/features/recipe-studio/dialogs/config-dialog.tsx
+++ b/studio/frontend/src/features/recipe-studio/dialogs/config-dialog.tsx
@@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter } from "@/components/ui/dialog";
import { Switch } from "@/components/ui/switch";
import type { ReactElement } from "react";
+import { getBlockDefinitionForConfig } from "../blocks/definitions";
import { renderBlockDialog } from "../blocks/registry";
import type { NodeConfig, SamplerConfig } from "../types";
import { DialogShell } from "./shared/dialog-shell";
@@ -30,6 +31,8 @@ export function ConfigDialog({
onUpdate,
container,
}: ConfigDialogProps): ReactElement {
+ const blockDefinition = getBlockDefinitionForConfig(config);
+
return (