chore: rename Canvas Lab components and utilities
This commit is contained in:
parent
0ed6c141b0
commit
93f45ffd07
76 changed files with 352 additions and 390 deletions
2
studio/frontend/.gitignore
vendored
2
studio/frontend/.gitignore
vendored
|
|
@ -26,4 +26,4 @@ test/
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
/src/features/canvas-lab/AGENTS.md
|
||||
/src/features/recipe-studio/AGENTS.md
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createRouter } from "@tanstack/react-router";
|
||||
import { Route as rootRoute } from "./routes/__root";
|
||||
import { Route as canvasLabRoute } from "./routes/canvas-lab";
|
||||
import { Route as dataRecipesNewRoute } from "./routes/data-recipes-new";
|
||||
import { Route as chatRoute } from "./routes/chat";
|
||||
import { Route as exportRoute } from "./routes/export";
|
||||
import { Route as gridTestRoute } from "./routes/grid-test";
|
||||
|
|
@ -19,7 +19,7 @@ const routeTree = rootRoute.addChildren([
|
|||
studioRoute,
|
||||
chatRoute,
|
||||
exportRoute,
|
||||
canvasLabRoute,
|
||||
dataRecipesNewRoute,
|
||||
]);
|
||||
|
||||
export const router = createRouter({ routeTree });
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import { createRoute } from "@tanstack/react-router";
|
|||
import { lazy } from "react";
|
||||
import { Route as rootRoute } from "./__root";
|
||||
|
||||
const CanvasLabPage = lazy(() =>
|
||||
import("@/features/canvas-lab").then((m) => ({
|
||||
default: m.CanvasLabPage,
|
||||
const RecipeStudioPage = lazy(() =>
|
||||
import("@/features/recipe-studio").then((m) => ({
|
||||
default: m.RecipeStudioPage,
|
||||
})),
|
||||
);
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/canvas-lab",
|
||||
component: CanvasLabPage,
|
||||
path: "/data-recipes/new",
|
||||
component: RecipeStudioPage,
|
||||
});
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { CanvasLabPage } from "./canvas-lab-page";
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export {
|
||||
applyCanvasConnection,
|
||||
isValidCanvasConnection,
|
||||
} from "./graph/canvas-connection";
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export { importCanvasPayload } from "./importer";
|
||||
export type { CanvasSnapshot, ImportResult } from "./types";
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export { buildCanvasPayload } from "./build-payload";
|
||||
export type { CanvasPayload, CanvasPayloadResult } from "./types";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
const DEFAULT_BASE = "/preview-api";
|
||||
const DEFAULT_BASE = "";
|
||||
|
||||
export const CANVAS_LAB_API_BASE =
|
||||
export const DATA_DESIGNER_API_BASE =
|
||||
import.meta.env.VITE_DATA_DESIGNER_API ?? DEFAULT_BASE;
|
||||
|
||||
type PreviewResponse = {
|
||||
|
|
@ -8,10 +8,10 @@ type PreviewResponse = {
|
|||
processorArtifacts?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export async function previewCanvas(
|
||||
export async function previewRecipe(
|
||||
payload: unknown,
|
||||
): Promise<PreviewResponse> {
|
||||
const response = await fetch(`${CANVAS_LAB_API_BASE}/preview`, {
|
||||
const response = await fetch(`${DATA_DESIGNER_API_BASE}/preview`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -18,7 +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 { RECIPE_FLOATING_ICON_BUTTON_CLASS } from "./recipe-floating-icon-button-class";
|
||||
import type { LlmType, SamplerType } from "../types";
|
||||
import { BLOCK_GROUPS, getBlocksForKind } from "../blocks/registry";
|
||||
|
||||
|
|
@ -34,8 +34,8 @@ type RootGroup = {
|
|||
|
||||
type BlockSheetProps = {
|
||||
container: HTMLDivElement | null;
|
||||
view: SheetView;
|
||||
onViewChange: (view: SheetView) => void;
|
||||
sheetView: SheetView;
|
||||
onViewChange: (sheetView: SheetView) => void;
|
||||
onAddSampler: (type: SamplerType) => void;
|
||||
onAddLlm: (type: LlmType) => void;
|
||||
onAddModelProvider: () => void;
|
||||
|
|
@ -47,17 +47,17 @@ type BlockSheetProps = {
|
|||
onImport: () => void;
|
||||
};
|
||||
|
||||
function getSheetTitle(view: SheetView): string {
|
||||
if (view === "root") {
|
||||
function getSheetTitle(sheetView: SheetView): string {
|
||||
if (sheetView === "root") {
|
||||
return "Add a block";
|
||||
}
|
||||
if (view === "sampler") {
|
||||
if (sheetView === "sampler") {
|
||||
return "Sampler blocks";
|
||||
}
|
||||
if (view === "expression") {
|
||||
if (sheetView === "expression") {
|
||||
return "Expression blocks";
|
||||
}
|
||||
if (view === "processor") {
|
||||
if (sheetView === "processor") {
|
||||
return "Processor blocks";
|
||||
}
|
||||
return "LLM blocks";
|
||||
|
|
@ -98,7 +98,7 @@ function BlockSheetButton({
|
|||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`flex w-full items-center gap-3 bg-white px-3 py-3 text-left transition border-l-2 ${
|
||||
className={`flex w-full items-center gap-3 bg-white px-3 py-3 text-left transition border-l-2 ${
|
||||
isActive
|
||||
? "border-emerald-500"
|
||||
: "border-transparent hover:border-border/60"
|
||||
|
|
@ -121,7 +121,7 @@ function BlockSheetButton({
|
|||
|
||||
export function BlockSheet({
|
||||
container,
|
||||
view,
|
||||
sheetView,
|
||||
onViewChange,
|
||||
onAddSampler,
|
||||
onAddLlm,
|
||||
|
|
@ -133,7 +133,7 @@ export function BlockSheet({
|
|||
onCopy,
|
||||
onImport,
|
||||
}: BlockSheetProps): ReactElement {
|
||||
const title = getSheetTitle(view);
|
||||
const sheetTitle = getSheetTitle(sheetView);
|
||||
return (
|
||||
<div className="flex flex-col items-end gap-2">
|
||||
<Sheet
|
||||
|
|
@ -146,7 +146,7 @@ export function BlockSheet({
|
|||
<SheetTrigger asChild={true}>
|
||||
<Button
|
||||
size="icon"
|
||||
className={CANVAS_FLOATING_ICON_BUTTON_CLASS}
|
||||
className={RECIPE_FLOATING_ICON_BUTTON_CLASS}
|
||||
variant="ghost"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
|
|
@ -165,7 +165,7 @@ export function BlockSheet({
|
|||
>
|
||||
<SheetHeader className="border-b border-border/60 px-6 py-5">
|
||||
<div className="flex items-center gap-2">
|
||||
{view !== "root" && (
|
||||
{sheetView !== "root" && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
|
@ -175,12 +175,12 @@ export function BlockSheet({
|
|||
<HugeiconsIcon icon={ArrowLeft02Icon} className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
<SheetTitle>{title}</SheetTitle>
|
||||
<SheetTitle>{sheetTitle}</SheetTitle>
|
||||
</div>
|
||||
</SheetHeader>
|
||||
<div className=" py-4">
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
{view === "root" &&
|
||||
{sheetView === "root" &&
|
||||
ROOT_GROUPS.map((item, index) => (
|
||||
<BlockSheetButton
|
||||
key={item.kind}
|
||||
|
|
@ -191,7 +191,7 @@ export function BlockSheet({
|
|||
onClick={() => onViewChange(item.kind)}
|
||||
/>
|
||||
))}
|
||||
{view === "processor" && (
|
||||
{sheetView === "processor" && (
|
||||
<BlockSheetButton
|
||||
icon={CodeIcon}
|
||||
title="Schema Transform"
|
||||
|
|
@ -200,9 +200,9 @@ export function BlockSheet({
|
|||
onClick={onOpenProcessors}
|
||||
/>
|
||||
)}
|
||||
{view !== "root" &&
|
||||
view !== "processor" &&
|
||||
getBlocksForKind(VIEW_KIND[view] ?? "sampler").map(
|
||||
{sheetView !== "root" &&
|
||||
sheetView !== "processor" &&
|
||||
getBlocksForKind(VIEW_KIND[sheetView] ?? "sampler").map(
|
||||
(item, index) => (
|
||||
<BlockSheetButton
|
||||
key={item.type}
|
||||
|
|
@ -236,7 +236,7 @@ export function BlockSheet({
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={CANVAS_FLOATING_ICON_BUTTON_CLASS}
|
||||
className={RECIPE_FLOATING_ICON_BUTTON_CLASS}
|
||||
onClick={onImport}
|
||||
>
|
||||
<HugeiconsIcon
|
||||
|
|
@ -248,7 +248,7 @@ export function BlockSheet({
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={CANVAS_FLOATING_ICON_BUTTON_CLASS}
|
||||
className={RECIPE_FLOATING_ICON_BUTTON_CLASS}
|
||||
onClick={onCopy}
|
||||
>
|
||||
<HugeiconsIcon
|
||||
|
|
@ -8,7 +8,7 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import type { ReactElement } from "react";
|
||||
import { useCanvasLabStore } from "../../stores/canvas-lab";
|
||||
import { useRecipeStudioStore } from "../../stores/recipe-studio";
|
||||
import type { ExpressionConfig, ExpressionDtype } from "../../types";
|
||||
import { getAvailableVariables } from "../../utils/variables";
|
||||
import { InlineField } from "./inline-field";
|
||||
|
|
@ -24,7 +24,7 @@ export function InlineExpression({
|
|||
config,
|
||||
onUpdate,
|
||||
}: InlineExpressionProps): ReactElement {
|
||||
const configs = useCanvasLabStore((state) => state.configs);
|
||||
const configs = useRecipeStudioStore((state) => state.configs);
|
||||
const vars = getAvailableVariables(configs, config.id);
|
||||
|
||||
return (
|
||||
|
|
@ -14,7 +14,7 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { type ReactElement, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCanvasLabStore } from "../../stores/canvas-lab";
|
||||
import { useRecipeStudioStore } from "../../stores/recipe-studio";
|
||||
import type { LlmConfig } from "../../types";
|
||||
import { InlineField } from "./inline-field";
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ const CODE_LANG_OPTIONS = [
|
|||
|
||||
export function InlineLlm({ config, onUpdate }: InlineLlmProps): ReactElement {
|
||||
const isCode = config.llm_type === "code";
|
||||
const configs = useCanvasLabStore((state) => state.configs);
|
||||
const configs = useRecipeStudioStore((state) => state.configs);
|
||||
const modelConfigAliases = useMemo(
|
||||
() =>
|
||||
Object.values(configs)
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
export const CANVAS_FLOATING_ICON_BUTTON_CLASS =
|
||||
export const RECIPE_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";
|
||||
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from "@xyflow/react";
|
||||
import { memo, type ReactElement } from "react";
|
||||
import { MAX_NODE_WIDTH, MIN_NODE_WIDTH } from "../constants";
|
||||
import { useCanvasLabStore } from "../stores/canvas-lab";
|
||||
import { useRecipeStudioStore } from "../stores/recipe-studio";
|
||||
import type { LayoutDirection, LlmConfig, Score, ScoreOption } from "../types";
|
||||
import { HANDLE_IDS } from "../utils/handles";
|
||||
import { getAvailableVariables } from "../utils/variables";
|
||||
|
|
@ -34,8 +34,8 @@ type JudgeScoreNodeData = {
|
|||
layoutDirection: LayoutDirection;
|
||||
};
|
||||
|
||||
export type CanvasAuxNodeData = PromptInputNodeData | JudgeScoreNodeData;
|
||||
export type CanvasAuxNodeType = Node<CanvasAuxNodeData, "aux">;
|
||||
export type RecipeGraphAuxNodeData = PromptInputNodeData | JudgeScoreNodeData;
|
||||
export type RecipeGraphAuxNodeType = Node<RecipeGraphAuxNodeData, "aux">;
|
||||
|
||||
function updateScoreAt(
|
||||
config: LlmConfig,
|
||||
|
|
@ -59,7 +59,7 @@ function updateOptionAt(
|
|||
}
|
||||
|
||||
function AuxVariableBadges({ llmId }: { llmId: string }): ReactElement | null {
|
||||
const configs = useCanvasLabStore((state) => state.configs);
|
||||
const configs = useRecipeStudioStore((state) => state.configs);
|
||||
const vars = getAvailableVariables(configs, llmId);
|
||||
if (vars.length === 0) return null;
|
||||
return (
|
||||
|
|
@ -82,9 +82,9 @@ function AuxVariableBadges({ llmId }: { llmId: string }): ReactElement | null {
|
|||
|
||||
function AuxNodeBase({
|
||||
data,
|
||||
}: NodeProps<CanvasAuxNodeType>): ReactElement | null {
|
||||
const config = useCanvasLabStore((state) => state.configs[data.llmId]);
|
||||
const updateConfig = useCanvasLabStore((state) => state.updateConfig);
|
||||
}: NodeProps<RecipeGraphAuxNodeType>): ReactElement | null {
|
||||
const config = useRecipeStudioStore((state) => state.configs[data.llmId]);
|
||||
const updateConfig = useRecipeStudioStore((state) => state.updateConfig);
|
||||
|
||||
if (!(config && config.kind === "llm")) {
|
||||
return null;
|
||||
|
|
@ -255,4 +255,4 @@ function AuxNodeBase({
|
|||
);
|
||||
}
|
||||
|
||||
export const CanvasAuxNode = memo(AuxNodeBase);
|
||||
export const RecipeGraphAuxNode = memo(AuxNodeBase);
|
||||
|
|
@ -27,9 +27,9 @@ import {
|
|||
} from "@xyflow/react";
|
||||
import { memo, type ReactElement, useEffect } from "react";
|
||||
import { MAX_NODE_WIDTH, MIN_NODE_WIDTH } from "../constants";
|
||||
import { useCanvasLabStore } from "../stores/canvas-lab";
|
||||
import { useRecipeStudioStore } from "../stores/recipe-studio";
|
||||
import type {
|
||||
CanvasNode as CanvasNodeType,
|
||||
RecipeNode as RecipeGraphNodeType,
|
||||
LlmType,
|
||||
NodeConfig,
|
||||
SamplerType,
|
||||
|
|
@ -90,8 +90,8 @@ const LLM_ICONS: Record<LlmType, IconType> = {
|
|||
};
|
||||
|
||||
function resolveNodeIcon(
|
||||
kind: CanvasNodeType["data"]["kind"],
|
||||
blockType: CanvasNodeType["data"]["blockType"],
|
||||
kind: RecipeGraphNodeType["data"]["kind"],
|
||||
blockType: RecipeGraphNodeType["data"]["blockType"],
|
||||
): IconType {
|
||||
if (kind === "sampler" && blockType in SAMPLER_ICONS) {
|
||||
return SAMPLER_ICONS[blockType as SamplerType];
|
||||
|
|
@ -274,17 +274,17 @@ function LlmInputHandles({ items, isTopBottom }: LlmInputHandlesProps): ReactEle
|
|||
);
|
||||
}
|
||||
|
||||
function CanvasNodeBase({
|
||||
function RecipeGraphNodeBase({
|
||||
id,
|
||||
data,
|
||||
selected,
|
||||
}: NodeProps<CanvasNodeType>): ReactElement {
|
||||
}: NodeProps<RecipeGraphNodeType>): ReactElement {
|
||||
const meta = NODE_META[data.kind];
|
||||
const icon = resolveNodeIcon(data.kind, data.blockType);
|
||||
const layoutDirection = data.layoutDirection ?? "LR";
|
||||
const config = useCanvasLabStore((state) => state.configs[id]);
|
||||
const openConfig = useCanvasLabStore((state) => state.openConfig);
|
||||
const updateConfig = useCanvasLabStore((state) => state.updateConfig);
|
||||
const config = useRecipeStudioStore((state) => state.configs[id]);
|
||||
const openConfig = useRecipeStudioStore((state) => state.openConfig);
|
||||
const updateConfig = useRecipeStudioStore((state) => state.updateConfig);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -409,4 +409,4 @@ function CanvasNodeBase({
|
|||
);
|
||||
}
|
||||
|
||||
export const CanvasNode = memo(CanvasNodeBase);
|
||||
export const RecipeNode = memo(RecipeGraphNodeBase);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { BaseEdge, type EdgeProps, getSmoothStepPath } from "@xyflow/react";
|
||||
import { memo, type ReactElement } from "react";
|
||||
|
||||
export const CanvasSemanticEdge = memo(function CanvasSemanticEdge({
|
||||
export const RecipeGraphSemanticEdge = memo(function RecipeGraphSemanticEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
|
|
@ -24,7 +24,7 @@ export function ImportDialog({
|
|||
}: ImportDialogProps): ReactElement {
|
||||
const [value, setValue] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const payloadId = "canvas-import-payload";
|
||||
const payloadId = "recipe-import-payload";
|
||||
const handleOpenChange = (nextOpen: boolean) => {
|
||||
if (!nextOpen) {
|
||||
setValue("");
|
||||
|
|
@ -186,7 +186,7 @@ export function LlmDialog({
|
|||
</div>
|
||||
{scores.length === 0 && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Add scorer blocks. Each block spawns on canvas and connects to this judge node.
|
||||
Add scorer blocks. Each block spawns on graph and connects to this judge node.
|
||||
</p>
|
||||
)}
|
||||
{scores.map((score, index) => (
|
||||
|
|
@ -5,14 +5,14 @@ import { Switch } from "@/components/ui/switch";
|
|||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { VisuallyHidden } from "radix-ui";
|
||||
import { type ReactElement, useMemo } from "react";
|
||||
import type { CanvasProcessorConfig } from "../types";
|
||||
import type { RecipeProcessorConfig } from "../types";
|
||||
import { buildDefaultSchemaTransform } from "../utils/processors";
|
||||
import { AvailableVariables } from "./shared/available-variables";
|
||||
type ProcessorsDialogProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
processors: CanvasProcessorConfig[];
|
||||
onProcessorsChange: (processors: CanvasProcessorConfig[]) => void;
|
||||
processors: RecipeProcessorConfig[];
|
||||
onProcessorsChange: (processors: RecipeProcessorConfig[]) => void;
|
||||
container?: HTMLDivElement | null;
|
||||
};
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ export function ProcessorsDialog({
|
|||
);
|
||||
};
|
||||
|
||||
const updateSchema = (patch: Partial<CanvasProcessorConfig>) => {
|
||||
const updateSchema = (patch: Partial<RecipeProcessorConfig>) => {
|
||||
if (!schemaProcessor) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Badge } from "@/components/ui/badge";
|
||||
import type { ReactElement } from "react";
|
||||
import { useCanvasLabStore } from "../../stores/canvas-lab";
|
||||
import { useRecipeStudioStore } from "../../stores/recipe-studio";
|
||||
import { getAvailableVariables } from "../../utils/variables";
|
||||
|
||||
type AvailableVariablesProps = {
|
||||
|
|
@ -10,7 +10,7 @@ type AvailableVariablesProps = {
|
|||
export function AvailableVariables({
|
||||
configId,
|
||||
}: AvailableVariablesProps): ReactElement | null {
|
||||
const configs = useCanvasLabStore((state) => state.configs);
|
||||
const configs = useRecipeStudioStore((state) => state.configs);
|
||||
const vars = getAvailableVariables(configs, configId);
|
||||
|
||||
if (vars.length === 0) return null;
|
||||
1
studio/frontend/src/features/recipe-studio/index.ts
Normal file
1
studio/frontend/src/features/recipe-studio/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { RecipeStudioPage } from "./recipe-studio-page";
|
||||
|
|
@ -26,30 +26,30 @@ 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 { previewRecipe } from "./api";
|
||||
import { RecipeGraphAuxNode, type RecipeGraphAuxNodeData } from "./components/recipe-graph-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 { RECIPE_FLOATING_ICON_BUTTON_CLASS } from "./components/recipe-floating-icon-button-class";
|
||||
import { RecipeNode } from "./components/recipe-graph-node";
|
||||
import { RecipeGraphSemanticEdge } from "./components/recipe-graph-semantic-edge";
|
||||
import { DataEdge } from "./components/rf-ui/data-edge";
|
||||
import { ConfigDialog } from "./dialogs/config-dialog";
|
||||
import { ImportDialog } from "./dialogs/import-dialog";
|
||||
import { ProcessorsDialog } from "./dialogs/processors-dialog";
|
||||
import { useCanvasLabStore } from "./stores/canvas-lab";
|
||||
import { useRecipeStudioStore } from "./stores/recipe-studio";
|
||||
import type {
|
||||
CanvasNode as CanvasBuilderNode,
|
||||
CanvasNodeData,
|
||||
RecipeNode as RecipeBuilderNode,
|
||||
RecipeNodeData,
|
||||
SamplerConfig,
|
||||
} from "./types";
|
||||
import { isCategoryConfig } from "./utils";
|
||||
import { deriveDisplayGraph } from "./utils/graph/derive-display-graph";
|
||||
import { importCanvasPayload } from "./utils/import";
|
||||
import { buildCanvasPayload } from "./utils/payload";
|
||||
import { importRecipePayload } from "./utils/import";
|
||||
import { buildRecipePayload } from "./utils/payload";
|
||||
import { buildDefaultSchemaTransform } from "./utils/processors";
|
||||
|
||||
const NODE_TYPES: NodeTypes = { builder: CanvasNode, aux: CanvasAuxNode };
|
||||
const EDGE_TYPES: EdgeTypes = { canvas: DataEdge, semantic: CanvasSemanticEdge };
|
||||
const NODE_TYPES: NodeTypes = { builder: RecipeNode, aux: RecipeGraphAuxNode };
|
||||
const EDGE_TYPES: EdgeTypes = { canvas: DataEdge, semantic: RecipeGraphSemanticEdge };
|
||||
|
||||
type LayoutControlsProps = {
|
||||
direction: "LR" | "TB";
|
||||
|
|
@ -66,6 +66,17 @@ type InternalsSyncProps = {
|
|||
nodeIds: string[];
|
||||
};
|
||||
|
||||
type StatusTone = "success" | "error";
|
||||
type StatusMessage = {
|
||||
tone: StatusTone;
|
||||
text: string;
|
||||
};
|
||||
|
||||
const STATUS_MESSAGE_CLASS: Record<StatusTone, string> = {
|
||||
success: "mt-2 text-xs text-emerald-600",
|
||||
error: "mt-2 text-xs text-rose-600",
|
||||
};
|
||||
|
||||
function InternalsSync({ nodeIds }: InternalsSyncProps): null {
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
|
|
@ -155,7 +166,7 @@ function ViewportControls({
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={CANVAS_FLOATING_ICON_BUTTON_CLASS}
|
||||
className={RECIPE_FLOATING_ICON_BUTTON_CLASS}
|
||||
onClick={handleZoomIn}
|
||||
aria-label="Zoom in"
|
||||
>
|
||||
|
|
@ -165,7 +176,7 @@ function ViewportControls({
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={CANVAS_FLOATING_ICON_BUTTON_CLASS}
|
||||
className={RECIPE_FLOATING_ICON_BUTTON_CLASS}
|
||||
onClick={handleZoomOut}
|
||||
aria-label="Zoom out"
|
||||
>
|
||||
|
|
@ -175,7 +186,7 @@ function ViewportControls({
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={CANVAS_FLOATING_ICON_BUTTON_CLASS}
|
||||
className={RECIPE_FLOATING_ICON_BUTTON_CLASS}
|
||||
onClick={handleFitView}
|
||||
aria-label="Fit view"
|
||||
>
|
||||
|
|
@ -185,7 +196,7 @@ function ViewportControls({
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={CANVAS_FLOATING_ICON_BUTTON_CLASS}
|
||||
className={RECIPE_FLOATING_ICON_BUTTON_CLASS}
|
||||
onClick={onToggleInteractive}
|
||||
aria-label={interactive ? "Lock interaction" : "Unlock interaction"}
|
||||
>
|
||||
|
|
@ -199,7 +210,7 @@ function ViewportControls({
|
|||
);
|
||||
}
|
||||
|
||||
export function CanvasLabPage(): ReactElement {
|
||||
export function RecipeStudioPage(): ReactElement {
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
|
|
@ -225,14 +236,14 @@ export function CanvasLabPage(): ReactElement {
|
|||
setSheetView,
|
||||
setProcessors,
|
||||
setDialogOpen,
|
||||
loadCanvas,
|
||||
loadRecipe,
|
||||
setLayoutDirection,
|
||||
applyLayout,
|
||||
setAuxNodePosition,
|
||||
setAuxNodeSize,
|
||||
syncAuxNodePositions,
|
||||
syncAuxNodeSizes,
|
||||
} = useCanvasLabStore(
|
||||
} = useRecipeStudioStore(
|
||||
useShallow((state) => ({
|
||||
nodes: state.nodes,
|
||||
edges: state.edges,
|
||||
|
|
@ -258,7 +269,7 @@ export function CanvasLabPage(): ReactElement {
|
|||
setSheetView: state.setSheetView,
|
||||
setProcessors: state.setProcessors,
|
||||
setDialogOpen: state.setDialogOpen,
|
||||
loadCanvas: state.loadCanvas,
|
||||
loadRecipe: state.loadRecipe,
|
||||
setLayoutDirection: state.setLayoutDirection,
|
||||
applyLayout: state.applyLayout,
|
||||
setAuxNodePosition: state.setAuxNodePosition,
|
||||
|
|
@ -275,10 +286,7 @@ export function CanvasLabPage(): ReactElement {
|
|||
const [importOpen, setImportOpen] = useState(false);
|
||||
const [processorsOpen, setProcessorsOpen] = useState(false);
|
||||
const [interactive, setInteractive] = useState(true);
|
||||
const [statusMessage, setStatusMessage] = useState<{
|
||||
tone: "success" | "error";
|
||||
text: string;
|
||||
} | null>(null);
|
||||
const [statusMessage, setStatusMessage] = useState<StatusMessage | null>(null);
|
||||
|
||||
const baseNodeIds = useMemo(
|
||||
() => new Set(nodes.map((node) => node.id)),
|
||||
|
|
@ -311,7 +319,7 @@ export function CanvasLabPage(): ReactElement {
|
|||
}, [displayGraph.auxNodeIds, syncAuxNodeSizes]);
|
||||
|
||||
const handleNodeClick = useCallback(
|
||||
(_: unknown, node: Node<CanvasNodeData | CanvasAuxNodeData>) => {
|
||||
(_: unknown, node: Node<RecipeNodeData | RecipeGraphAuxNodeData>) => {
|
||||
if (node.type !== "builder") {
|
||||
return;
|
||||
}
|
||||
|
|
@ -321,12 +329,9 @@ export function CanvasLabPage(): ReactElement {
|
|||
);
|
||||
|
||||
const handleNodesChange = useCallback(
|
||||
(changes: NodeChange<Node<CanvasNodeData | CanvasAuxNodeData>>[]) => {
|
||||
(changes: NodeChange<Node<RecipeNodeData | RecipeGraphAuxNodeData>>[]) => {
|
||||
for (const change of changes) {
|
||||
if (
|
||||
!("id" in change) ||
|
||||
!change.id.startsWith("aux-")
|
||||
) {
|
||||
if (!("id" in change) || !change.id.startsWith("aux-")) {
|
||||
continue;
|
||||
}
|
||||
if (change.type === "position" && change.position) {
|
||||
|
|
@ -346,7 +351,7 @@ export function CanvasLabPage(): ReactElement {
|
|||
}
|
||||
}
|
||||
const next = changes.filter(
|
||||
(change): change is NodeChange<CanvasBuilderNode> =>
|
||||
(change): change is NodeChange<RecipeBuilderNode> =>
|
||||
"id" in change && baseNodeIds.has(change.id),
|
||||
);
|
||||
if (next.length > 0) {
|
||||
|
|
@ -369,67 +374,77 @@ export function CanvasLabPage(): ReactElement {
|
|||
[baseEdgeIds, onEdgesChange],
|
||||
);
|
||||
|
||||
const configList = useMemo(() => Object.values(configs), [configs]);
|
||||
const config = activeConfigId ? configs[activeConfigId] : null;
|
||||
const categoryOptions = useMemo<SamplerConfig[]>(
|
||||
() => Object.values(configs).filter(isCategoryConfig),
|
||||
[configs],
|
||||
() => configList.filter(isCategoryConfig),
|
||||
[configList],
|
||||
);
|
||||
const modelConfigAliases = useMemo<string[]>(
|
||||
() =>
|
||||
Object.values(configs)
|
||||
.filter((item) => item.kind === "model_config")
|
||||
.map((item) => item.name),
|
||||
[configs],
|
||||
() => configList.filter((item) => item.kind === "model_config").map((item) => item.name),
|
||||
[configList],
|
||||
);
|
||||
const modelProviderOptions = useMemo<string[]>(
|
||||
() =>
|
||||
Object.values(configs)
|
||||
.filter((item) => item.kind === "model_provider")
|
||||
.map((item) => item.name),
|
||||
[configs],
|
||||
() => configList.filter((item) => item.kind === "model_provider").map((item) => item.name),
|
||||
[configList],
|
||||
);
|
||||
const datetimeOptions = useMemo<string[]>(
|
||||
() =>
|
||||
Object.values(configs)
|
||||
configList
|
||||
.filter(
|
||||
(item) => item.kind === "sampler" && item.sampler_type === "datetime",
|
||||
)
|
||||
.map((item) => item.name),
|
||||
[configs],
|
||||
[configList],
|
||||
);
|
||||
|
||||
const handleToggleDirection = useCallback(() => {
|
||||
setLayoutDirection(layoutDirection === "LR" ? "TB" : "LR");
|
||||
}, [layoutDirection, setLayoutDirection]);
|
||||
|
||||
const toggleInteractive = useCallback(() => {
|
||||
setInteractive((value) => !value);
|
||||
}, []);
|
||||
|
||||
const setSuccessStatus = useCallback((text: string) => {
|
||||
setStatusMessage({ tone: "success", text });
|
||||
}, []);
|
||||
|
||||
const setErrorStatus = useCallback((text: string) => {
|
||||
setStatusMessage({ tone: "error", text });
|
||||
}, []);
|
||||
|
||||
const buildPayload = useCallback(
|
||||
() => buildRecipePayload(configs, nodes, edges, processors),
|
||||
[configs, nodes, edges, processors],
|
||||
);
|
||||
|
||||
const readPayload = useCallback(
|
||||
(fallbackError: string) => {
|
||||
const { payload, errors } = buildPayload();
|
||||
if (errors.length === 0) {
|
||||
return payload;
|
||||
}
|
||||
setErrorStatus(errors[0] ?? fallbackError);
|
||||
return null;
|
||||
},
|
||||
[buildPayload, setErrorStatus],
|
||||
);
|
||||
|
||||
const handlePreview = async (): Promise<void> => {
|
||||
setPreviewLoading(true);
|
||||
setStatusMessage(null);
|
||||
const payload = readPayload("Fix config errors before preview.");
|
||||
if (!payload) {
|
||||
setPreviewLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { payload, errors } = buildCanvasPayload(
|
||||
configs,
|
||||
nodes,
|
||||
edges,
|
||||
processors,
|
||||
);
|
||||
if (errors.length > 0) {
|
||||
setStatusMessage({
|
||||
tone: "error",
|
||||
text: errors[0] ?? "Fix config errors before preview.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const result = await previewCanvas(payload);
|
||||
const result = await previewRecipe(payload);
|
||||
const rows = Array.isArray(result.dataset) ? result.dataset.length : 0;
|
||||
setStatusMessage({
|
||||
tone: "success",
|
||||
text: `Preview ready (${rows} rows).`,
|
||||
});
|
||||
setSuccessStatus(`Preview ready (${rows} rows).`);
|
||||
} catch (error) {
|
||||
setStatusMessage({
|
||||
tone: "error",
|
||||
text: error instanceof Error ? error.message : "Preview failed.",
|
||||
});
|
||||
setErrorStatus(error instanceof Error ? error.message : "Preview failed.");
|
||||
} finally {
|
||||
setPreviewLoading(false);
|
||||
}
|
||||
|
|
@ -438,53 +453,35 @@ export function CanvasLabPage(): ReactElement {
|
|||
const handleCopyRecipe = async (): Promise<void> => {
|
||||
setCopied(false);
|
||||
setStatusMessage(null);
|
||||
const { payload, errors } = buildCanvasPayload(
|
||||
configs,
|
||||
nodes,
|
||||
edges,
|
||||
processors,
|
||||
);
|
||||
if (errors.length > 0) {
|
||||
setStatusMessage({
|
||||
tone: "error",
|
||||
text: errors[0] ?? "Fix config errors before copy.",
|
||||
});
|
||||
const payload = readPayload("Fix config errors before copy.");
|
||||
if (!payload) {
|
||||
return;
|
||||
}
|
||||
if (!navigator.clipboard) {
|
||||
setStatusMessage({
|
||||
tone: "error",
|
||||
text: "Clipboard not available.",
|
||||
});
|
||||
setErrorStatus("Clipboard not available.");
|
||||
return;
|
||||
}
|
||||
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.",
|
||||
});
|
||||
setSuccessStatus("Recipe copied to clipboard.");
|
||||
} catch (error) {
|
||||
setStatusMessage({
|
||||
tone: "error",
|
||||
text: error instanceof Error ? error.message : "Copy failed.",
|
||||
});
|
||||
setErrorStatus(error instanceof Error ? error.message : "Copy failed.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleImport = (value: string): string | null => {
|
||||
const result = importCanvasPayload(value);
|
||||
const result = importRecipePayload(value);
|
||||
if (result.errors.length > 0 || !result.snapshot) {
|
||||
return result.errors[0] ?? "Invalid payload.";
|
||||
}
|
||||
loadCanvas(result.snapshot);
|
||||
setStatusMessage({ tone: "success", text: "Recipe imported." });
|
||||
loadRecipe(result.snapshot);
|
||||
setSuccessStatus("Recipe imported.");
|
||||
return null;
|
||||
};
|
||||
|
||||
const handleOpenProcessorsFromSheet = useCallback(() => {
|
||||
const openProcessorsFromSheet = useCallback(() => {
|
||||
if (
|
||||
!processors.some(
|
||||
(processor) => processor.processor_type === "schema_transform",
|
||||
|
|
@ -501,20 +498,12 @@ export function CanvasLabPage(): ReactElement {
|
|||
<div className="mb-6 flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 lg:grid lg:grid-cols-[1fr_auto] lg:items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Canvas Lab
|
||||
</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Create Data Recipe</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Minimal React Flow canvas.
|
||||
Design synthetic-data pipelines with Data Designer.
|
||||
</p>
|
||||
{statusMessage && (
|
||||
<p
|
||||
className={
|
||||
statusMessage.tone === "success"
|
||||
? "mt-2 text-xs text-emerald-600"
|
||||
: "mt-2 text-xs text-rose-600"
|
||||
}
|
||||
>
|
||||
<p className={STATUS_MESSAGE_CLASS[statusMessage.tone]}>
|
||||
{statusMessage.text}
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -577,14 +566,14 @@ export function CanvasLabPage(): ReactElement {
|
|||
<Panel position="top-right" className="m-3">
|
||||
<BlockSheet
|
||||
container={sheetContainer}
|
||||
view={sheetView}
|
||||
sheetView={sheetView}
|
||||
onViewChange={setSheetView}
|
||||
onAddSampler={addSamplerNode}
|
||||
onAddLlm={addLlmNode}
|
||||
onAddModelProvider={addModelProviderNode}
|
||||
onAddModelConfig={addModelConfigNode}
|
||||
onAddExpression={addExpressionNode}
|
||||
onOpenProcessors={handleOpenProcessorsFromSheet}
|
||||
onOpenProcessors={openProcessorsFromSheet}
|
||||
copied={copied}
|
||||
onCopy={handleCopyRecipe}
|
||||
onImport={() => setImportOpen(true)}
|
||||
|
|
@ -592,7 +581,7 @@ export function CanvasLabPage(): ReactElement {
|
|||
</Panel>
|
||||
<ViewportControls
|
||||
interactive={interactive}
|
||||
onToggleInteractive={() => setInteractive((value) => !value)}
|
||||
onToggleInteractive={toggleInteractive}
|
||||
/>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { type Edge, addEdge } from "@xyflow/react";
|
||||
import { DEFAULT_NODE_WIDTH } from "../constants";
|
||||
import type {
|
||||
CanvasNode,
|
||||
RecipeNode,
|
||||
LayoutDirection,
|
||||
LlmConfig,
|
||||
ModelConfig,
|
||||
|
|
@ -15,14 +15,14 @@ import { getConfigUiMode } from "../components/inline/inline-policy";
|
|||
|
||||
type NodeUpdateState = {
|
||||
configs: Record<string, NodeConfig>;
|
||||
nodes: CanvasNode[];
|
||||
nodes: RecipeNode[];
|
||||
nextId: number;
|
||||
nextY: number;
|
||||
};
|
||||
|
||||
type NodeUpdateResult = {
|
||||
configs: Record<string, NodeConfig>;
|
||||
nodes: CanvasNode[];
|
||||
nodes: RecipeNode[];
|
||||
nextId: number;
|
||||
nextY: number;
|
||||
activeConfigId: string;
|
||||
|
|
@ -30,11 +30,11 @@ type NodeUpdateResult = {
|
|||
};
|
||||
|
||||
export function updateNodeData(
|
||||
nodes: CanvasNode[],
|
||||
nodes: RecipeNode[],
|
||||
id: string,
|
||||
config: NodeConfig,
|
||||
layoutDirection: LayoutDirection,
|
||||
): CanvasNode[] {
|
||||
): RecipeNode[] {
|
||||
return nodes.map((node) =>
|
||||
node.id === id
|
||||
? { ...node, data: nodeDataFromConfig(config, layoutDirection) }
|
||||
|
|
@ -52,12 +52,56 @@ function findNodeIdByName(
|
|||
return entry ? entry[0] : null;
|
||||
}
|
||||
|
||||
function addRecipeEdge(edges: Edge[], source: string, target: string): Edge[] {
|
||||
return addEdge(
|
||||
{
|
||||
source,
|
||||
target,
|
||||
sourceHandle: HANDLE_IDS.dataOut,
|
||||
targetHandle: HANDLE_IDS.dataIn,
|
||||
type: "canvas",
|
||||
},
|
||||
edges,
|
||||
);
|
||||
}
|
||||
|
||||
function addSemanticEdge(edges: Edge[], source: string, target: string): Edge[] {
|
||||
return addEdge(
|
||||
{
|
||||
source,
|
||||
target,
|
||||
sourceHandle: HANDLE_IDS.semanticOut,
|
||||
targetHandle: HANDLE_IDS.semanticIn,
|
||||
type: "semantic",
|
||||
},
|
||||
edges,
|
||||
);
|
||||
}
|
||||
|
||||
function removeTargetEdges(edges: Edge[], targetId: string): Edge[] {
|
||||
return edges.filter((edge) => edge.target !== targetId);
|
||||
}
|
||||
|
||||
function removeTargetEdgesBySource(
|
||||
edges: Edge[],
|
||||
configs: Record<string, NodeConfig>,
|
||||
targetId: string,
|
||||
shouldRemove: (source: NodeConfig | undefined) => boolean,
|
||||
): Edge[] {
|
||||
return edges.filter((edge) => {
|
||||
if (edge.target !== targetId) {
|
||||
return true;
|
||||
}
|
||||
return !shouldRemove(configs[edge.source]);
|
||||
});
|
||||
}
|
||||
|
||||
export function buildNodeUpdate(
|
||||
state: NodeUpdateState,
|
||||
config: NodeConfig,
|
||||
layoutDirection: LayoutDirection,
|
||||
): NodeUpdateResult {
|
||||
const node: CanvasNode = {
|
||||
const node: RecipeNode = {
|
||||
id: config.id,
|
||||
type: "builder",
|
||||
position: { x: 0, y: state.nextY },
|
||||
|
|
@ -77,10 +121,10 @@ export function buildNodeUpdate(
|
|||
}
|
||||
|
||||
export function applyLayoutDirectionToNodes(
|
||||
nodes: CanvasNode[],
|
||||
nodes: RecipeNode[],
|
||||
configs: Record<string, NodeConfig>,
|
||||
layoutDirection: LayoutDirection,
|
||||
): CanvasNode[] {
|
||||
): RecipeNode[] {
|
||||
return nodes.map((node) => {
|
||||
const config = configs[node.id];
|
||||
if (config) {
|
||||
|
|
@ -108,18 +152,9 @@ export function syncEdgesForConfigPatch(
|
|||
if (isSubcategoryConfig(current) && hasParentPatch) {
|
||||
const nextParent = (patch as Partial<SamplerConfig>).subcategory_parent ?? "";
|
||||
const parentId = nextParent ? findNodeIdByName(configs, nextParent) : null;
|
||||
nextEdges = nextEdges.filter((edge) => edge.target !== current.id);
|
||||
nextEdges = removeTargetEdges(nextEdges, current.id);
|
||||
if (parentId) {
|
||||
nextEdges = addEdge(
|
||||
{
|
||||
source: parentId,
|
||||
target: current.id,
|
||||
sourceHandle: HANDLE_IDS.dataOut,
|
||||
targetHandle: HANDLE_IDS.dataIn,
|
||||
type: "canvas",
|
||||
},
|
||||
nextEdges,
|
||||
);
|
||||
nextEdges = addRecipeEdge(nextEdges, parentId, current.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,26 +164,16 @@ export function syncEdgesForConfigPatch(
|
|||
);
|
||||
if (current.kind === "model_config" && hasProviderPatch) {
|
||||
const nextProvider = (patch as Partial<ModelConfig>).provider ?? "";
|
||||
nextEdges = nextEdges.filter((edge) => {
|
||||
if (edge.target !== current.id) {
|
||||
return true;
|
||||
}
|
||||
const source = configs[edge.source];
|
||||
return !(source && source.kind === "model_provider");
|
||||
});
|
||||
nextEdges = removeTargetEdgesBySource(
|
||||
nextEdges,
|
||||
configs,
|
||||
current.id,
|
||||
(source) => Boolean(source && source.kind === "model_provider"),
|
||||
);
|
||||
if (nextProvider) {
|
||||
const providerId = findNodeIdByName(configs, nextProvider);
|
||||
if (providerId) {
|
||||
nextEdges = addEdge(
|
||||
{
|
||||
source: providerId,
|
||||
target: current.id,
|
||||
sourceHandle: HANDLE_IDS.semanticOut,
|
||||
targetHandle: HANDLE_IDS.semanticIn,
|
||||
type: "semantic",
|
||||
},
|
||||
nextEdges,
|
||||
);
|
||||
nextEdges = addSemanticEdge(nextEdges, providerId, current.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -164,17 +189,17 @@ export function syncEdgesForConfigPatch(
|
|||
) {
|
||||
const nextReference =
|
||||
(patch as Partial<SamplerConfig>).reference_column_name ?? "";
|
||||
nextEdges = nextEdges.filter((edge) => {
|
||||
if (edge.target !== current.id) {
|
||||
return true;
|
||||
}
|
||||
const source = configs[edge.source];
|
||||
return !(
|
||||
source &&
|
||||
source.kind === "sampler" &&
|
||||
source.sampler_type === "datetime"
|
||||
);
|
||||
});
|
||||
nextEdges = removeTargetEdgesBySource(
|
||||
nextEdges,
|
||||
configs,
|
||||
current.id,
|
||||
(source) =>
|
||||
Boolean(
|
||||
source &&
|
||||
source.kind === "sampler" &&
|
||||
source.sampler_type === "datetime",
|
||||
),
|
||||
);
|
||||
if (nextReference) {
|
||||
const referenceId = findNodeIdByName(configs, nextReference);
|
||||
const source = referenceId ? configs[referenceId] : null;
|
||||
|
|
@ -184,16 +209,7 @@ export function syncEdgesForConfigPatch(
|
|||
source.kind === "sampler" &&
|
||||
source.sampler_type === "datetime"
|
||||
) {
|
||||
nextEdges = addEdge(
|
||||
{
|
||||
source: referenceId,
|
||||
target: current.id,
|
||||
sourceHandle: HANDLE_IDS.dataOut,
|
||||
targetHandle: HANDLE_IDS.dataIn,
|
||||
type: "canvas",
|
||||
},
|
||||
nextEdges,
|
||||
);
|
||||
nextEdges = addRecipeEdge(nextEdges, referenceId, current.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -205,26 +221,16 @@ export function syncEdgesForConfigPatch(
|
|||
if (current.kind === "llm" && hasModelAliasPatch) {
|
||||
const nextAlias =
|
||||
(patch as Partial<NodeConfig> & { model_alias?: string }).model_alias ?? "";
|
||||
nextEdges = nextEdges.filter((edge) => {
|
||||
if (edge.target !== current.id) {
|
||||
return true;
|
||||
}
|
||||
const source = configs[edge.source];
|
||||
return !(source && source.kind === "model_config");
|
||||
});
|
||||
nextEdges = removeTargetEdgesBySource(
|
||||
nextEdges,
|
||||
configs,
|
||||
current.id,
|
||||
(source) => Boolean(source && source.kind === "model_config"),
|
||||
);
|
||||
if (nextAlias) {
|
||||
const modelConfigId = findNodeIdByName(configs, nextAlias);
|
||||
if (modelConfigId) {
|
||||
nextEdges = addEdge(
|
||||
{
|
||||
source: modelConfigId,
|
||||
target: current.id,
|
||||
sourceHandle: HANDLE_IDS.semanticOut,
|
||||
targetHandle: HANDLE_IDS.semanticIn,
|
||||
type: "semantic",
|
||||
},
|
||||
nextEdges,
|
||||
);
|
||||
nextEdges = addSemanticEdge(nextEdges, modelConfigId, current.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -405,9 +411,18 @@ export function applyRenameToConfigs(
|
|||
if (!from || from === to) {
|
||||
return configs;
|
||||
}
|
||||
return applyConfigTransform(configs, (config) =>
|
||||
applyRenameToConfig(config, from, to),
|
||||
);
|
||||
}
|
||||
|
||||
function applyConfigTransform(
|
||||
configs: Record<string, NodeConfig>,
|
||||
transform: (config: NodeConfig) => NodeConfig,
|
||||
): Record<string, NodeConfig> {
|
||||
let next = configs;
|
||||
for (const [id, config] of Object.entries(configs)) {
|
||||
const updated = applyRenameToConfig(config, from, to);
|
||||
const updated = transform(config);
|
||||
if (updated !== config) {
|
||||
if (next === configs) {
|
||||
next = { ...configs };
|
||||
|
|
@ -425,15 +440,5 @@ export function applyRemovalToConfigs(
|
|||
if (!ref) {
|
||||
return configs;
|
||||
}
|
||||
let next = configs;
|
||||
for (const [id, config] of Object.entries(configs)) {
|
||||
const updated = applyRemovalToConfig(config, ref);
|
||||
if (updated !== config) {
|
||||
if (next === configs) {
|
||||
next = { ...configs };
|
||||
}
|
||||
next[id] = updated;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
return applyConfigTransform(configs, (config) => applyRemovalToConfig(config, ref));
|
||||
}
|
||||
|
|
@ -10,17 +10,21 @@ import {
|
|||
} from "@xyflow/react";
|
||||
import { create } from "zustand";
|
||||
import type {
|
||||
CanvasNode,
|
||||
CanvasProcessorConfig,
|
||||
RecipeNode,
|
||||
RecipeProcessorConfig,
|
||||
LayoutDirection,
|
||||
LlmType,
|
||||
NodeConfig,
|
||||
SamplerType,
|
||||
} from "../types";
|
||||
import { getBlockDefinition } from "../blocks/registry";
|
||||
import {
|
||||
getBlockDefinition,
|
||||
type BlockKind,
|
||||
type BlockType,
|
||||
} from "../blocks/registry";
|
||||
import { isCategoryConfig, isSubcategoryConfig } from "../utils";
|
||||
import { applyCanvasConnection, isValidCanvasConnection } from "../utils/graph";
|
||||
import type { CanvasSnapshot } from "../utils/import";
|
||||
import { applyRecipeConnection, isValidRecipeConnection } from "../utils/graph";
|
||||
import type { RecipeSnapshot } from "../utils/import";
|
||||
import { getLayoutedElements } from "../utils/layout";
|
||||
import {
|
||||
applyRemovalToConfig,
|
||||
|
|
@ -31,17 +35,17 @@ import {
|
|||
syncEdgesForConfigPatch,
|
||||
syncSubcategoryConfigsForCategoryUpdate,
|
||||
updateNodeData,
|
||||
} from "./canvas-lab-helpers";
|
||||
} from "./recipe-studio-helpers";
|
||||
|
||||
type SheetView = "root" | "sampler" | "llm" | "expression" | "processor";
|
||||
|
||||
type CanvasLabState = {
|
||||
nodes: CanvasNode[];
|
||||
type RecipeStudioState = {
|
||||
nodes: RecipeNode[];
|
||||
edges: Edge[];
|
||||
auxNodePositions: Record<string, XYPosition>;
|
||||
auxNodeSizes: Record<string, { width: number; height: number }>;
|
||||
configs: Record<string, NodeConfig>;
|
||||
processors: CanvasProcessorConfig[];
|
||||
processors: RecipeProcessorConfig[];
|
||||
sheetView: SheetView;
|
||||
activeConfigId: string | null;
|
||||
dialogOpen: boolean;
|
||||
|
|
@ -49,7 +53,7 @@ type CanvasLabState = {
|
|||
nextId: number;
|
||||
nextY: number;
|
||||
setSheetView: (view: SheetView) => void;
|
||||
setProcessors: (processors: CanvasProcessorConfig[]) => void;
|
||||
setProcessors: (processors: RecipeProcessorConfig[]) => void;
|
||||
setDialogOpen: (open: boolean) => void;
|
||||
selectConfig: (id: string) => void;
|
||||
openConfig: (id: string) => void;
|
||||
|
|
@ -61,7 +65,7 @@ type CanvasLabState = {
|
|||
addModelConfigNode: () => void;
|
||||
addExpressionNode: () => void;
|
||||
updateConfig: (id: string, patch: Partial<NodeConfig>) => void;
|
||||
loadCanvas: (snapshot: CanvasSnapshot) => void;
|
||||
loadRecipe: (snapshot: RecipeSnapshot) => void;
|
||||
setAuxNodePosition: (id: string, position: XYPosition) => void;
|
||||
setAuxNodeSize: (
|
||||
id: string,
|
||||
|
|
@ -72,13 +76,28 @@ type CanvasLabState = {
|
|||
defaults: Record<string, XYPosition>,
|
||||
) => void;
|
||||
syncAuxNodeSizes: (activeIds: string[]) => void;
|
||||
onNodesChange: (changes: NodeChange<CanvasNode>[]) => void;
|
||||
onNodesChange: (changes: NodeChange<RecipeNode>[]) => void;
|
||||
onEdgesChange: (changes: EdgeChange<Edge>[]) => void;
|
||||
onConnect: (connection: Connection) => void;
|
||||
isValidConnection: IsValidConnection;
|
||||
};
|
||||
|
||||
export const useCanvasLabStore = create<CanvasLabState>((set, get) => ({
|
||||
function buildAddedNodeState(
|
||||
state: RecipeStudioState,
|
||||
kind: BlockKind,
|
||||
type: BlockType,
|
||||
): Partial<RecipeStudioState> | RecipeStudioState {
|
||||
const id = `n${state.nextId}`;
|
||||
const existing = Object.values(state.configs);
|
||||
const definition = getBlockDefinition(kind, type);
|
||||
if (!definition) {
|
||||
return state;
|
||||
}
|
||||
const config = definition.createConfig(id, existing);
|
||||
return buildNodeUpdate(state, config, state.layoutDirection);
|
||||
}
|
||||
|
||||
export const useRecipeStudioStore = create<RecipeStudioState>((set, get) => ({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
auxNodePositions: {},
|
||||
|
|
@ -123,67 +142,16 @@ export const useCanvasLabStore = create<CanvasLabState>((set, get) => ({
|
|||
),
|
||||
};
|
||||
}),
|
||||
addSamplerNode: (type) => {
|
||||
set((state) => {
|
||||
const id = `n${state.nextId}`;
|
||||
const existing = Object.values(state.configs);
|
||||
const definition = getBlockDefinition("sampler", type);
|
||||
if (!definition) {
|
||||
return state;
|
||||
}
|
||||
const config = definition.createConfig(id, existing);
|
||||
return buildNodeUpdate(state, config, state.layoutDirection);
|
||||
});
|
||||
},
|
||||
addLlmNode: (type) => {
|
||||
set((state) => {
|
||||
const id = `n${state.nextId}`;
|
||||
const existing = Object.values(state.configs);
|
||||
const definition = getBlockDefinition("llm", type);
|
||||
if (!definition) {
|
||||
return state;
|
||||
}
|
||||
const config = definition.createConfig(id, existing);
|
||||
return buildNodeUpdate(state, config, state.layoutDirection);
|
||||
});
|
||||
},
|
||||
addModelProviderNode: () => {
|
||||
set((state) => {
|
||||
const id = `n${state.nextId}`;
|
||||
const existing = Object.values(state.configs);
|
||||
const definition = getBlockDefinition("llm", "model_provider");
|
||||
if (!definition) {
|
||||
return state;
|
||||
}
|
||||
const config = definition.createConfig(id, existing);
|
||||
return buildNodeUpdate(state, config, state.layoutDirection);
|
||||
});
|
||||
},
|
||||
addModelConfigNode: () => {
|
||||
set((state) => {
|
||||
const id = `n${state.nextId}`;
|
||||
const existing = Object.values(state.configs);
|
||||
const definition = getBlockDefinition("llm", "model_config");
|
||||
if (!definition) {
|
||||
return state;
|
||||
}
|
||||
const config = definition.createConfig(id, existing);
|
||||
return buildNodeUpdate(state, config, state.layoutDirection);
|
||||
});
|
||||
},
|
||||
addExpressionNode: () => {
|
||||
set((state) => {
|
||||
const id = `n${state.nextId}`;
|
||||
const existing = Object.values(state.configs);
|
||||
const definition = getBlockDefinition("expression", "expression");
|
||||
if (!definition) {
|
||||
return state;
|
||||
}
|
||||
const config = definition.createConfig(id, existing);
|
||||
return buildNodeUpdate(state, config, state.layoutDirection);
|
||||
});
|
||||
},
|
||||
loadCanvas: (snapshot) =>
|
||||
addSamplerNode: (type) =>
|
||||
set((state) => buildAddedNodeState(state, "sampler", type)),
|
||||
addLlmNode: (type) => set((state) => buildAddedNodeState(state, "llm", type)),
|
||||
addModelProviderNode: () =>
|
||||
set((state) => buildAddedNodeState(state, "llm", "model_provider")),
|
||||
addModelConfigNode: () =>
|
||||
set((state) => buildAddedNodeState(state, "llm", "model_config")),
|
||||
addExpressionNode: () =>
|
||||
set((state) => buildAddedNodeState(state, "expression", "expression")),
|
||||
loadRecipe: (snapshot) =>
|
||||
set((state) => ({
|
||||
configs: snapshot.configs,
|
||||
nodes: applyLayoutDirectionToNodes(
|
||||
|
|
@ -281,7 +249,7 @@ export const useCanvasLabStore = create<CanvasLabState>((set, get) => ({
|
|||
return state;
|
||||
}),
|
||||
updateConfig: (id, patch) => {
|
||||
const applyUpdate = (state: CanvasLabState) => {
|
||||
const applyUpdate = (state: RecipeStudioState) => {
|
||||
const current = state.configs[id];
|
||||
if (!current) {
|
||||
return state;
|
||||
|
|
@ -320,7 +288,7 @@ export const useCanvasLabStore = create<CanvasLabState>((set, get) => ({
|
|||
},
|
||||
onNodesChange: (changes) => {
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: store update
|
||||
const applyNodesChange = (state: CanvasLabState) => {
|
||||
const applyNodesChange = (state: RecipeStudioState) => {
|
||||
const removedIds = changes
|
||||
.filter((change) => change.type === "remove")
|
||||
.map((change) => change.id);
|
||||
|
|
@ -367,7 +335,7 @@ export const useCanvasLabStore = create<CanvasLabState>((set, get) => ({
|
|||
}
|
||||
}
|
||||
|
||||
const nodes = applyNodeChanges<CanvasNode>(changes, state.nodes);
|
||||
const nodes = applyNodeChanges<RecipeNode>(changes, state.nodes);
|
||||
return { nodes, edges, configs };
|
||||
};
|
||||
set(applyNodesChange);
|
||||
|
|
@ -403,7 +371,7 @@ export const useCanvasLabStore = create<CanvasLabState>((set, get) => ({
|
|||
},
|
||||
onConnect: (connection) => {
|
||||
set((state) => {
|
||||
const result = applyCanvasConnection(
|
||||
const result = applyRecipeConnection(
|
||||
connection,
|
||||
state.configs,
|
||||
state.edges,
|
||||
|
|
@ -414,7 +382,7 @@ export const useCanvasLabStore = create<CanvasLabState>((set, get) => ({
|
|||
});
|
||||
},
|
||||
isValidConnection: (connection) =>
|
||||
isValidCanvasConnection(
|
||||
isValidRecipeConnection(
|
||||
{
|
||||
source: connection.source ?? null,
|
||||
target: connection.target ?? null,
|
||||
|
|
@ -18,7 +18,7 @@ export type ExpressionDtype = "str" | "int" | "float" | "bool";
|
|||
|
||||
export type LayoutDirection = "LR" | "TB";
|
||||
|
||||
export type CanvasNodeData = {
|
||||
export type RecipeNodeData = {
|
||||
title: string;
|
||||
name: string;
|
||||
kind: "sampler" | "llm" | "expression" | "model_provider" | "model_config";
|
||||
|
|
@ -32,7 +32,7 @@ export type CanvasNodeData = {
|
|||
layoutDirection?: LayoutDirection;
|
||||
};
|
||||
|
||||
export type CanvasNode = Node<CanvasNodeData, "builder">;
|
||||
export type RecipeNode = Node<RecipeNodeData, "builder">;
|
||||
|
||||
export type CategoryConditionalParams = {
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
|
|
@ -171,7 +171,7 @@ export type SchemaTransformProcessorConfig = {
|
|||
template: string;
|
||||
};
|
||||
|
||||
export type CanvasProcessorConfig = SchemaTransformProcessorConfig;
|
||||
export type RecipeProcessorConfig = SchemaTransformProcessorConfig;
|
||||
|
||||
export type NodeConfig =
|
||||
| SamplerConfig
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export {
|
||||
applyRecipeConnection,
|
||||
isValidRecipeConnection,
|
||||
} from "./graph/recipe-graph-connection";
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import type { Edge, Node, XYPosition } from "@xyflow/react";
|
||||
import type { CanvasAuxNodeData } from "../../components/canvas-aux-node";
|
||||
import type { RecipeGraphAuxNodeData } from "../../components/recipe-graph-aux-node";
|
||||
import { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from "../../constants";
|
||||
import type { CanvasNode, LayoutDirection, NodeConfig } from "../../types";
|
||||
import type { RecipeNode, LayoutDirection, NodeConfig } from "../../types";
|
||||
import { getLlmJudgeScoreHandleId, HANDLE_IDS } from "../handles";
|
||||
|
||||
type DisplayGraphInput = {
|
||||
nodes: CanvasNode[];
|
||||
nodes: RecipeNode[];
|
||||
edges: Edge[];
|
||||
configs: Record<string, NodeConfig>;
|
||||
layoutDirection: LayoutDirection;
|
||||
|
|
@ -14,7 +14,7 @@ type DisplayGraphInput = {
|
|||
};
|
||||
|
||||
export type DisplayGraph = {
|
||||
nodes: Array<Node<CanvasNode["data"] | CanvasAuxNodeData>>;
|
||||
nodes: Array<Node<RecipeNode["data"] | RecipeGraphAuxNodeData>>;
|
||||
edges: Edge[];
|
||||
auxNodeIds: string[];
|
||||
auxDefaults: Record<string, XYPosition>;
|
||||
|
|
@ -23,7 +23,7 @@ export type DisplayGraph = {
|
|||
type AuxNodeItem = {
|
||||
key: string;
|
||||
targetHandle: string;
|
||||
data: CanvasAuxNodeData;
|
||||
data: RecipeGraphAuxNodeData;
|
||||
};
|
||||
|
||||
function getNodeWidth(node: Node): number {
|
||||
|
|
@ -92,7 +92,7 @@ export function deriveDisplayGraph({
|
|||
style: { ...node.style, width: DEFAULT_NODE_WIDTH },
|
||||
};
|
||||
});
|
||||
const auxNodes: Node<CanvasAuxNodeData>[] = [];
|
||||
const auxNodes: Node<RecipeGraphAuxNodeData>[] = [];
|
||||
const auxEdges: Edge[] = [];
|
||||
const auxDefaults: Record<string, XYPosition> = {};
|
||||
const auxNodeIds: string[] = [];
|
||||
|
|
@ -126,7 +126,7 @@ function isCompetingIncomingEdge(
|
|||
return source.kind === "sampler" && source.sampler_type === "datetime";
|
||||
}
|
||||
|
||||
export function isValidCanvasConnection(
|
||||
export function isValidRecipeConnection(
|
||||
connection: Connection,
|
||||
configs: Record<string, NodeConfig>,
|
||||
): boolean {
|
||||
|
|
@ -151,12 +151,12 @@ export function isValidCanvasConnection(
|
|||
return isDataLane(connection);
|
||||
}
|
||||
|
||||
export function applyCanvasConnection(
|
||||
export function applyRecipeConnection(
|
||||
connection: Connection,
|
||||
configs: Record<string, NodeConfig>,
|
||||
edges: Edge[],
|
||||
): { edges: Edge[]; configs?: Record<string, NodeConfig> } {
|
||||
if (!isValidCanvasConnection(connection, configs)) {
|
||||
if (!isValidRecipeConnection(connection, configs)) {
|
||||
return { edges };
|
||||
}
|
||||
const source = connection.source ? configs[connection.source] : null;
|
||||
|
|
@ -11,7 +11,7 @@ export const HANDLE_IDS = {
|
|||
llmInputOut: "llm-input-out",
|
||||
} as const;
|
||||
|
||||
export type CanvasHandleId = (typeof HANDLE_IDS)[keyof typeof HANDLE_IDS];
|
||||
export type RecipeHandleId = (typeof HANDLE_IDS)[keyof typeof HANDLE_IDS];
|
||||
|
||||
export function getLlmJudgeScoreHandleId(index: number): string {
|
||||
return `llm-judge-score-in-${index}`;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { CanvasProcessorConfig, NodeConfig } from "../../types";
|
||||
import type { RecipeProcessorConfig, NodeConfig } from "../../types";
|
||||
import { buildEdges } from "./edges";
|
||||
import { isRecord, parseJson, readString } from "./helpers";
|
||||
import {
|
||||
|
|
@ -21,11 +21,11 @@ type UiInput = {
|
|||
edges?: unknown;
|
||||
};
|
||||
|
||||
function parseProcessors(input: unknown): CanvasProcessorConfig[] {
|
||||
function parseProcessors(input: unknown): RecipeProcessorConfig[] {
|
||||
if (!Array.isArray(input)) {
|
||||
return [];
|
||||
}
|
||||
const processors: CanvasProcessorConfig[] = [];
|
||||
const processors: RecipeProcessorConfig[] = [];
|
||||
input.forEach((item, index) => {
|
||||
if (!isRecord(item)) {
|
||||
return;
|
||||
|
|
@ -55,7 +55,7 @@ function parseProcessors(input: unknown): CanvasProcessorConfig[] {
|
|||
return processors;
|
||||
}
|
||||
|
||||
export function importCanvasPayload(input: string): ImportResult {
|
||||
export function importRecipePayload(input: string): ImportResult {
|
||||
const parsed = parseJson(input);
|
||||
if (!parsed.data || !isRecord(parsed.data)) {
|
||||
return {
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { importRecipePayload } from "./importer";
|
||||
export type { RecipeSnapshot, ImportResult } from "./types";
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
import type { Edge } from "@xyflow/react";
|
||||
import type {
|
||||
CanvasNode,
|
||||
CanvasProcessorConfig,
|
||||
RecipeNode,
|
||||
RecipeProcessorConfig,
|
||||
NodeConfig,
|
||||
} from "../../types";
|
||||
|
||||
export type CanvasSnapshot = {
|
||||
export type RecipeSnapshot = {
|
||||
configs: Record<string, NodeConfig>;
|
||||
nodes: CanvasNode[];
|
||||
nodes: RecipeNode[];
|
||||
edges: Edge[];
|
||||
processors: CanvasProcessorConfig[];
|
||||
processors: RecipeProcessorConfig[];
|
||||
nextId: number;
|
||||
nextY: number;
|
||||
};
|
||||
|
||||
export type ImportResult = {
|
||||
errors: string[];
|
||||
snapshot: CanvasSnapshot | null;
|
||||
snapshot: RecipeSnapshot | null;
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { CanvasNode, NodeConfig } from "../../types";
|
||||
import type { RecipeNode, NodeConfig } from "../../types";
|
||||
import { DEFAULT_NODE_WIDTH } from "../../constants";
|
||||
import { nodeDataFromConfig } from "../index";
|
||||
import { isRecord, readString } from "./helpers";
|
||||
|
|
@ -54,7 +54,7 @@ export function parseUi(
|
|||
export function buildNodes(
|
||||
configs: NodeConfig[],
|
||||
layouts: Map<string, { x: number; y: number; width?: number }>,
|
||||
): CanvasNode[] {
|
||||
): RecipeNode[] {
|
||||
return configs.map((config, index) => {
|
||||
const fallbackLayout: { x: number; y: number; width?: number } = {
|
||||
x: 0,
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type {
|
||||
CanvasNodeData,
|
||||
RecipeNodeData,
|
||||
ExpressionConfig,
|
||||
ExpressionDtype,
|
||||
LayoutDirection,
|
||||
|
|
@ -334,7 +334,7 @@ export function labelForExpression(type: ExpressionDtype): string {
|
|||
export function nodeDataFromConfig(
|
||||
config: NodeConfig,
|
||||
layoutDirection: LayoutDirection = "LR",
|
||||
): CanvasNodeData {
|
||||
): RecipeNodeData {
|
||||
if (config.kind === "sampler") {
|
||||
return {
|
||||
title: "Sampler",
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Edge } from "@xyflow/react";
|
||||
import type {
|
||||
CanvasProcessorConfig,
|
||||
CanvasNode,
|
||||
RecipeProcessorConfig,
|
||||
RecipeNode,
|
||||
ModelConfig,
|
||||
ModelProviderConfig,
|
||||
NodeConfig,
|
||||
|
|
@ -15,7 +15,7 @@ import {
|
|||
buildProcessors,
|
||||
buildSamplerColumn,
|
||||
} from "./builders";
|
||||
import type { CanvasPayloadResult } from "./types";
|
||||
import type { RecipePayloadResult } from "./types";
|
||||
import {
|
||||
isSemanticRelation,
|
||||
validateModelAliasLinks,
|
||||
|
|
@ -25,7 +25,7 @@ import {
|
|||
validateUsedProviders,
|
||||
} from "./validate";
|
||||
|
||||
function getNodeWidth(node: CanvasNode): number | null {
|
||||
function getNodeWidth(node: RecipeNode): number | null {
|
||||
if (typeof node.width === "number" && Number.isFinite(node.width)) {
|
||||
return node.width;
|
||||
}
|
||||
|
|
@ -42,12 +42,12 @@ function getNodeWidth(node: CanvasNode): number | null {
|
|||
}
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: payload build
|
||||
export function buildCanvasPayload(
|
||||
export function buildRecipePayload(
|
||||
configs: Record<string, NodeConfig>,
|
||||
nodes: CanvasNode[],
|
||||
nodes: RecipeNode[],
|
||||
edges: Edge[],
|
||||
processors: CanvasProcessorConfig[] = [],
|
||||
): CanvasPayloadResult {
|
||||
processors: RecipeProcessorConfig[] = [],
|
||||
): RecipePayloadResult {
|
||||
const errors: string[] = [];
|
||||
const columns: Record<string, unknown>[] = [];
|
||||
const modelAliases = new Set<string>();
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type {
|
||||
CanvasProcessorConfig,
|
||||
RecipeProcessorConfig,
|
||||
CategoryConditionalParams,
|
||||
ExpressionConfig,
|
||||
LlmConfig,
|
||||
|
|
@ -349,7 +349,7 @@ export function buildExpressionColumn(
|
|||
}
|
||||
|
||||
export function buildProcessors(
|
||||
processors: CanvasProcessorConfig[],
|
||||
processors: RecipeProcessorConfig[],
|
||||
errors: string[],
|
||||
): Record<string, unknown>[] {
|
||||
const output: Record<string, unknown>[] = [];
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { buildRecipePayload } from "./build-payload";
|
||||
export type { RecipePayload, RecipePayloadResult } from "./types";
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export type CanvasPayload = {
|
||||
export type RecipePayload = {
|
||||
recipe: {
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
model_providers: Record<string, unknown>[];
|
||||
|
|
@ -19,7 +19,7 @@ export type CanvasPayload = {
|
|||
};
|
||||
};
|
||||
|
||||
export type CanvasPayloadResult = {
|
||||
export type RecipePayloadResult = {
|
||||
errors: string[];
|
||||
payload: CanvasPayload;
|
||||
payload: RecipePayload;
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { CanvasProcessorConfig } from "../types";
|
||||
import type { RecipeProcessorConfig } from "../types";
|
||||
|
||||
export function buildDefaultSchemaTransform(): CanvasProcessorConfig {
|
||||
export function buildDefaultSchemaTransform(): RecipeProcessorConfig {
|
||||
return {
|
||||
id: "schema-transform-1",
|
||||
// biome-ignore lint/style/useNamingConvention: api schema
|
||||
|
|
@ -17,7 +17,7 @@ export default defineConfig({
|
|||
target: "http://127.0.0.1:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/preview-api": {
|
||||
"/preview": {
|
||||
target: "http://127.0.0.1:8004",
|
||||
changeOrigin: true,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue