feat(recipe-studio): enhance edge synchronization logic with layout direction support
This commit is contained in:
parent
6997919c65
commit
2473043fe1
5 changed files with 103 additions and 27 deletions
|
|
@ -1,10 +1,12 @@
|
|||
import { type Edge, addEdge } from "@xyflow/react";
|
||||
import type {
|
||||
LayoutDirection,
|
||||
ModelConfig,
|
||||
NodeConfig,
|
||||
SamplerConfig,
|
||||
ValidatorConfig,
|
||||
} from "../../types";
|
||||
import { applyRecipeConnection } from "../../utils/graph";
|
||||
import { isCategoryConfig, isSubcategoryConfig } from "../../utils";
|
||||
import { HANDLE_IDS } from "../../utils/handles";
|
||||
|
||||
|
|
@ -31,19 +33,6 @@ function addRecipeEdge(edges: Edge[], source: string, target: string): Edge[] {
|
|||
);
|
||||
}
|
||||
|
||||
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 addValidatorSemanticEdge(
|
||||
edges: Edge[],
|
||||
source: string,
|
||||
|
|
@ -84,6 +73,7 @@ export function syncEdgesForConfigPatch(
|
|||
patch: Partial<NodeConfig>,
|
||||
configs: Record<string, NodeConfig>,
|
||||
edges: Edge[],
|
||||
layoutDirection: LayoutDirection,
|
||||
): Edge[] {
|
||||
let nextEdges = edges;
|
||||
|
||||
|
|
@ -106,6 +96,9 @@ export function syncEdgesForConfigPatch(
|
|||
);
|
||||
if (current.kind === "model_config" && hasProviderPatch) {
|
||||
const nextProvider = (patch as Partial<ModelConfig>).provider ?? "";
|
||||
if (nextProvider.trim() === current.provider.trim()) {
|
||||
return nextEdges;
|
||||
}
|
||||
nextEdges = removeTargetEdgesBySource(
|
||||
nextEdges,
|
||||
configs,
|
||||
|
|
@ -115,7 +108,18 @@ export function syncEdgesForConfigPatch(
|
|||
if (nextProvider) {
|
||||
const providerId = findNodeIdByName(configs, nextProvider);
|
||||
if (providerId) {
|
||||
nextEdges = addSemanticEdge(nextEdges, providerId, current.id);
|
||||
const result = applyRecipeConnection(
|
||||
{
|
||||
source: providerId,
|
||||
sourceHandle: HANDLE_IDS.semanticOut,
|
||||
target: current.id,
|
||||
targetHandle: HANDLE_IDS.semanticIn,
|
||||
},
|
||||
configs,
|
||||
nextEdges,
|
||||
layoutDirection,
|
||||
);
|
||||
nextEdges = result.edges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,6 +167,9 @@ export function syncEdgesForConfigPatch(
|
|||
if (current.kind === "llm" && hasModelAliasPatch) {
|
||||
const nextAlias =
|
||||
(patch as Partial<NodeConfig> & { model_alias?: string }).model_alias ?? "";
|
||||
if (nextAlias.trim() === current.model_alias.trim()) {
|
||||
return nextEdges;
|
||||
}
|
||||
nextEdges = removeTargetEdgesBySource(
|
||||
nextEdges,
|
||||
configs,
|
||||
|
|
@ -172,7 +179,18 @@ export function syncEdgesForConfigPatch(
|
|||
if (nextAlias) {
|
||||
const modelConfigId = findNodeIdByName(configs, nextAlias);
|
||||
if (modelConfigId) {
|
||||
nextEdges = addSemanticEdge(nextEdges, modelConfigId, current.id);
|
||||
const result = applyRecipeConnection(
|
||||
{
|
||||
source: modelConfigId,
|
||||
sourceHandle: HANDLE_IDS.semanticOut,
|
||||
target: current.id,
|
||||
targetHandle: HANDLE_IDS.semanticIn,
|
||||
},
|
||||
configs,
|
||||
nextEdges,
|
||||
layoutDirection,
|
||||
);
|
||||
nextEdges = result.edges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,18 @@ function isConfigToLlmEdge(edge: Edge, configs: Record<string, NodeConfig>): boo
|
|||
return source?.kind === "model_config" && target?.kind === "llm";
|
||||
}
|
||||
|
||||
function isDataToLlmEdge(edge: Edge, configs: Record<string, NodeConfig>): boolean {
|
||||
const source = configs[edge.source];
|
||||
const target = configs[edge.target];
|
||||
return Boolean(
|
||||
source &&
|
||||
target &&
|
||||
source.kind !== "model_config" &&
|
||||
target.kind === "llm" &&
|
||||
edge.type !== "semantic",
|
||||
);
|
||||
}
|
||||
|
||||
function usageKey(nodeId: string, handleId: string): string {
|
||||
return `${nodeId}::${handleId}`;
|
||||
}
|
||||
|
|
@ -236,15 +248,20 @@ export function optimizeModelInfraEdgeHandles(
|
|||
const targetHandleBefore = normalizeRecipeHandleId(edge.targetHandle);
|
||||
const isModelSemantic =
|
||||
isProviderToConfigEdge(edge, configs) || isConfigToLlmEdge(edge, configs);
|
||||
if (!isModelSemantic) {
|
||||
const isLlmDataTarget = isDataToLlmEdge(edge, configs);
|
||||
if (!isModelSemantic && !isLlmDataTarget) {
|
||||
nextEdges.push(edge);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sourceHandleBefore) {
|
||||
decrementUsage(sourceUsage, edge.source, sourceHandleBefore);
|
||||
}
|
||||
if (targetHandleBefore) {
|
||||
if (isModelSemantic) {
|
||||
if (sourceHandleBefore) {
|
||||
decrementUsage(sourceUsage, edge.source, sourceHandleBefore);
|
||||
}
|
||||
if (targetHandleBefore) {
|
||||
decrementUsage(targetUsage, edge.target, targetHandleBefore);
|
||||
}
|
||||
} else if (targetHandleBefore) {
|
||||
decrementUsage(targetUsage, edge.target, targetHandleBefore);
|
||||
}
|
||||
|
||||
|
|
@ -265,12 +282,22 @@ export function optimizeModelInfraEdgeHandles(
|
|||
continue;
|
||||
}
|
||||
|
||||
const sourceCandidates = getConfigSourceHandleCandidates(direction);
|
||||
const targetCandidates = sortPreferredLlmTargetHandles(
|
||||
direction,
|
||||
nodesById.get(edge.source),
|
||||
nodesById.get(edge.target),
|
||||
);
|
||||
if (isLlmDataTarget) {
|
||||
const targetHandle = pickHandleByUsage(targetCandidates, edge.target, targetUsage);
|
||||
incrementUsage(targetUsage, edge.target, targetHandle);
|
||||
nextEdges.push({
|
||||
...edge,
|
||||
targetHandle,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceCandidates = getConfigSourceHandleCandidates(direction);
|
||||
const sourceHandle = pickHandleByUsage(sourceCandidates, edge.source, sourceUsage);
|
||||
const targetHandle = pickHandleByUsage(targetCandidates, edge.target, targetUsage);
|
||||
nextEdges.push(
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ function connectSemantic(
|
|||
configs: Record<string, NodeConfig>,
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
layoutDirection: LayoutDirection,
|
||||
): { edges: Edge[]; configs: Record<string, NodeConfig> } {
|
||||
const result = applyRecipeConnection(
|
||||
{
|
||||
|
|
@ -233,6 +234,7 @@ function connectSemantic(
|
|||
},
|
||||
configs,
|
||||
edges,
|
||||
layoutDirection,
|
||||
);
|
||||
return {
|
||||
edges: result.edges,
|
||||
|
|
@ -460,6 +462,7 @@ export const useRecipeStudioStore = create<RecipeStudioState>((set, get) => ({
|
|||
configs,
|
||||
context.newNodeId,
|
||||
unboundModelConfigs[0].id,
|
||||
state.layoutDirection,
|
||||
);
|
||||
edges = next.edges;
|
||||
configs = next.configs;
|
||||
|
|
@ -513,6 +516,7 @@ export const useRecipeStudioStore = create<RecipeStudioState>((set, get) => ({
|
|||
configs,
|
||||
providers[0].id,
|
||||
context.newNodeId,
|
||||
state.layoutDirection,
|
||||
);
|
||||
edges = next.edges;
|
||||
configs = next.configs;
|
||||
|
|
@ -523,6 +527,7 @@ export const useRecipeStudioStore = create<RecipeStudioState>((set, get) => ({
|
|||
configs,
|
||||
context.newNodeId,
|
||||
unboundLlms[0].id,
|
||||
state.layoutDirection,
|
||||
);
|
||||
edges = next.edges;
|
||||
configs = next.configs;
|
||||
|
|
@ -624,7 +629,13 @@ export const useRecipeStudioStore = create<RecipeStudioState>((set, get) => ({
|
|||
next,
|
||||
state.layoutDirection,
|
||||
);
|
||||
const edges = syncEdgesForConfigPatch(current, patch, configs, state.edges);
|
||||
const edges = syncEdgesForConfigPatch(
|
||||
current,
|
||||
patch,
|
||||
configs,
|
||||
state.edges,
|
||||
state.layoutDirection,
|
||||
);
|
||||
configs = syncSubcategoryConfigsForCategoryUpdate(
|
||||
current,
|
||||
next,
|
||||
|
|
@ -698,6 +709,7 @@ export const useRecipeStudioStore = create<RecipeStudioState>((set, get) => ({
|
|||
connection,
|
||||
state.configs,
|
||||
state.edges,
|
||||
state.layoutDirection,
|
||||
);
|
||||
return result.configs
|
||||
? { edges: result.edges, configs: result.configs }
|
||||
|
|
|
|||
|
|
@ -87,6 +87,13 @@ function normalizeEdge(
|
|||
isDataTargetHandle(targetHandleNormalized)
|
||||
? targetHandleNormalized ?? semanticTargetDefault
|
||||
: semanticTargetDefault;
|
||||
// LLM nodes only expose data lane handles; coerce legacy semantic handles.
|
||||
if (source?.kind === "llm" && isSemanticSourceHandle(sourceHandle)) {
|
||||
sourceHandle = semanticSourceDefault;
|
||||
}
|
||||
if (target?.kind === "llm" && isSemanticTargetHandle(targetHandle)) {
|
||||
targetHandle = semanticTargetDefault;
|
||||
}
|
||||
} else {
|
||||
sourceHandle = isDataSourceHandle(sourceHandleNormalized)
|
||||
? sourceHandleNormalized ?? getDefaultDataSourceHandle(layoutDirection)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { type Connection, type Edge, addEdge } from "@xyflow/react";
|
||||
import type { NodeConfig, SamplerConfig } from "../../types";
|
||||
import type { LayoutDirection, NodeConfig, SamplerConfig } from "../../types";
|
||||
import {
|
||||
HANDLE_IDS,
|
||||
isDataSourceHandle,
|
||||
|
|
@ -197,20 +197,30 @@ function chooseModelSemanticHandles(
|
|||
source: NodeConfig,
|
||||
target: NodeConfig,
|
||||
edges: Edge[],
|
||||
layoutDirection: LayoutDirection,
|
||||
): Connection {
|
||||
if (!isModelSemanticRelation(source, target)) {
|
||||
return connection;
|
||||
}
|
||||
|
||||
const sourceCandidates = [HANDLE_IDS.semanticOut, HANDLE_IDS.semanticOutBottom];
|
||||
const sourceCandidates =
|
||||
source.kind === "model_config" && target.kind === "llm"
|
||||
? layoutDirection === "TB"
|
||||
? [HANDLE_IDS.semanticOut]
|
||||
: [HANDLE_IDS.semanticOutBottom]
|
||||
: layoutDirection === "TB"
|
||||
? [HANDLE_IDS.semanticOut, HANDLE_IDS.semanticOutBottom]
|
||||
: [HANDLE_IDS.semanticOutBottom, HANDLE_IDS.semanticOut];
|
||||
const targetCandidates =
|
||||
target.kind === "model_config"
|
||||
? [HANDLE_IDS.semanticIn, HANDLE_IDS.semanticInTop]
|
||||
? layoutDirection === "TB"
|
||||
? [HANDLE_IDS.semanticIn, HANDLE_IDS.semanticInTop]
|
||||
: [HANDLE_IDS.semanticInTop, HANDLE_IDS.semanticIn]
|
||||
: [
|
||||
HANDLE_IDS.dataIn,
|
||||
HANDLE_IDS.dataInTop,
|
||||
HANDLE_IDS.dataInRight,
|
||||
HANDLE_IDS.dataInBottom,
|
||||
HANDLE_IDS.dataIn,
|
||||
HANDLE_IDS.dataInRight,
|
||||
];
|
||||
|
||||
const sourceHandle = pickLeastUsedHandle(
|
||||
|
|
@ -281,6 +291,7 @@ export function applyRecipeConnection(
|
|||
connection: Connection,
|
||||
configs: Record<string, NodeConfig>,
|
||||
edges: Edge[],
|
||||
layoutDirection: LayoutDirection = "LR",
|
||||
): { edges: Edge[]; configs?: Record<string, NodeConfig> } {
|
||||
if (!isValidRecipeConnection(connection, configs)) {
|
||||
return { edges };
|
||||
|
|
@ -322,6 +333,7 @@ export function applyRecipeConnection(
|
|||
source,
|
||||
target,
|
||||
nextBaseEdges,
|
||||
layoutDirection,
|
||||
);
|
||||
const nextEdges = addEdge(
|
||||
{ ...resolvedConnection, type: semanticRelation ? "semantic" : "canvas" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue