mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-20 04:17:57 -05:00
Compare commits
3 Commits
v0.5.64
...
improvemen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65f4d3da72 | ||
|
|
c8280cdfff | ||
|
|
f336395f98 |
@@ -26,9 +26,6 @@ import { CLASS_TOOL_METADATA } from '@/stores/panel/copilot/store'
|
||||
import type { SubAgentContentBlock } from '@/stores/panel/copilot/types'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
/**
|
||||
* Parse special tags from content
|
||||
*/
|
||||
/**
|
||||
* Plan step can be either a string or an object with title and plan
|
||||
*/
|
||||
@@ -47,6 +44,56 @@ interface ParsedTags {
|
||||
cleanContent: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plan steps from plan_respond tool calls in subagent blocks.
|
||||
* Returns { steps, isComplete } where steps is in the format expected by PlanSteps component.
|
||||
*/
|
||||
function extractPlanFromBlocks(blocks: SubAgentContentBlock[] | undefined): {
|
||||
steps: Record<string, PlanStep> | undefined
|
||||
isComplete: boolean
|
||||
} {
|
||||
if (!blocks) return { steps: undefined, isComplete: false }
|
||||
|
||||
// Find the plan_respond tool call
|
||||
const planRespondBlock = blocks.find(
|
||||
(b) => b.type === 'subagent_tool_call' && b.toolCall?.name === 'plan_respond'
|
||||
)
|
||||
|
||||
if (!planRespondBlock?.toolCall) {
|
||||
return { steps: undefined, isComplete: false }
|
||||
}
|
||||
|
||||
// Tool call arguments can be in different places depending on the source
|
||||
// Also handle nested data.arguments structure from the schema
|
||||
const tc = planRespondBlock.toolCall as any
|
||||
const args = tc.params || tc.parameters || tc.input || tc.arguments || tc.data?.arguments || {}
|
||||
const stepsArray = args.steps
|
||||
|
||||
if (!Array.isArray(stepsArray) || stepsArray.length === 0) {
|
||||
return { steps: undefined, isComplete: false }
|
||||
}
|
||||
|
||||
// Convert array format to Record<string, PlanStep> format
|
||||
// From: [{ number: 1, title: "..." }, { number: 2, title: "..." }]
|
||||
// To: { "1": "...", "2": "..." }
|
||||
const steps: Record<string, PlanStep> = {}
|
||||
for (const step of stepsArray) {
|
||||
if (step.number !== undefined && step.title) {
|
||||
steps[String(step.number)] = step.title
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the tool call is complete (not pending/executing)
|
||||
const isComplete =
|
||||
planRespondBlock.toolCall.state === ClientToolCallState.success ||
|
||||
planRespondBlock.toolCall.state === ClientToolCallState.error
|
||||
|
||||
return {
|
||||
steps: Object.keys(steps).length > 0 ? steps : undefined,
|
||||
isComplete,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse partial JSON for streaming options.
|
||||
* Attempts to extract complete key-value pairs from incomplete JSON.
|
||||
@@ -654,11 +701,20 @@ function SubAgentThinkingContent({
|
||||
}
|
||||
}
|
||||
|
||||
// Extract plan from plan_respond tool call (preferred) or fall back to <plan> tags
|
||||
const { steps: planSteps, isComplete: planComplete } = extractPlanFromBlocks(blocks)
|
||||
const allParsed = parseSpecialTags(allRawText)
|
||||
|
||||
if (!cleanText.trim() && !allParsed.plan) return null
|
||||
// Prefer plan_respond tool data over <plan> tags
|
||||
const hasPlan =
|
||||
!!(planSteps && Object.keys(planSteps).length > 0) ||
|
||||
!!(allParsed.plan && Object.keys(allParsed.plan).length > 0)
|
||||
const planToRender = planSteps || allParsed.plan
|
||||
const isPlanStreaming = planSteps ? !planComplete : isStreaming
|
||||
|
||||
const hasSpecialTags = !!(allParsed.plan && Object.keys(allParsed.plan).length > 0)
|
||||
if (!cleanText.trim() && !hasPlan) return null
|
||||
|
||||
const hasSpecialTags = hasPlan
|
||||
|
||||
return (
|
||||
<div className='space-y-1.5'>
|
||||
@@ -670,9 +726,7 @@ function SubAgentThinkingContent({
|
||||
hasSpecialTags={hasSpecialTags}
|
||||
/>
|
||||
)}
|
||||
{allParsed.plan && Object.keys(allParsed.plan).length > 0 && (
|
||||
<PlanSteps steps={allParsed.plan} streaming={isStreaming} />
|
||||
)}
|
||||
{hasPlan && planToRender && <PlanSteps steps={planToRender} streaming={isPlanStreaming} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -744,8 +798,19 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
|
||||
}
|
||||
|
||||
const allParsed = parseSpecialTags(allRawText)
|
||||
|
||||
// Extract plan from plan_respond tool call (preferred) or fall back to <plan> tags
|
||||
const { steps: planSteps, isComplete: planComplete } = extractPlanFromBlocks(
|
||||
toolCall.subAgentBlocks
|
||||
)
|
||||
const hasPlan =
|
||||
!!(planSteps && Object.keys(planSteps).length > 0) ||
|
||||
!!(allParsed.plan && Object.keys(allParsed.plan).length > 0)
|
||||
const planToRender = planSteps || allParsed.plan
|
||||
const isPlanStreaming = planSteps ? !planComplete : isStreaming
|
||||
|
||||
const hasSpecialTags = !!(
|
||||
(allParsed.plan && Object.keys(allParsed.plan).length > 0) ||
|
||||
hasPlan ||
|
||||
(allParsed.options && Object.keys(allParsed.options).length > 0)
|
||||
)
|
||||
|
||||
@@ -757,8 +822,6 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
|
||||
const outerLabel = getSubagentCompletionLabel(toolCall.name)
|
||||
const durationText = `${outerLabel} for ${formatDuration(duration)}`
|
||||
|
||||
const hasPlan = allParsed.plan && Object.keys(allParsed.plan).length > 0
|
||||
|
||||
const renderCollapsibleContent = () => (
|
||||
<>
|
||||
{segments.map((segment, index) => {
|
||||
@@ -800,7 +863,7 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
|
||||
return (
|
||||
<div className='w-full space-y-1.5'>
|
||||
{renderCollapsibleContent()}
|
||||
{hasPlan && <PlanSteps steps={allParsed.plan!} streaming={isStreaming} />}
|
||||
{hasPlan && planToRender && <PlanSteps steps={planToRender} streaming={isPlanStreaming} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -832,7 +895,7 @@ const SubagentContentRenderer = memo(function SubagentContentRenderer({
|
||||
</div>
|
||||
|
||||
{/* Plan stays outside the collapsible */}
|
||||
{hasPlan && <PlanSteps steps={allParsed.plan!} />}
|
||||
{hasPlan && planToRender && <PlanSteps steps={planToRender} />}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -1412,7 +1475,11 @@ export function ToolCall({ toolCall: toolCallProp, toolCallId, onStateChange }:
|
||||
if (
|
||||
toolCall.name === 'checkoff_todo' ||
|
||||
toolCall.name === 'mark_todo_in_progress' ||
|
||||
toolCall.name === 'tool_search_tool_regex'
|
||||
toolCall.name === 'tool_search_tool_regex' ||
|
||||
toolCall.name === 'user_memory' ||
|
||||
toolCall.name === 'edit_responsd' ||
|
||||
toolCall.name === 'debug_respond' ||
|
||||
toolCall.name === 'plan_respond'
|
||||
)
|
||||
return null
|
||||
|
||||
|
||||
@@ -209,13 +209,17 @@ export class SetGlobalWorkflowVariablesClientTool extends BaseClientTool {
|
||||
}
|
||||
}
|
||||
|
||||
const variablesArray = Object.values(byName)
|
||||
// Convert byName (keyed by name) to record keyed by ID for the API
|
||||
const variablesRecord: Record<string, any> = {}
|
||||
for (const v of Object.values(byName)) {
|
||||
variablesRecord[v.id] = v
|
||||
}
|
||||
|
||||
// POST full variables array to persist
|
||||
// POST full variables record to persist
|
||||
const res = await fetch(`/api/workflows/${payload.workflowId}/variables`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ variables: variablesArray }),
|
||||
body: JSON.stringify({ variables: variablesRecord }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const txt = await res.text().catch(() => '')
|
||||
|
||||
@@ -817,6 +817,8 @@ function normalizeResponseFormat(value: any): string {
|
||||
interface EdgeHandleValidationResult {
|
||||
valid: boolean
|
||||
error?: string
|
||||
/** The normalized handle to use (e.g., simple 'if' normalized to 'condition-{uuid}') */
|
||||
normalizedHandle?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -851,13 +853,6 @@ function validateSourceHandleForBlock(
|
||||
}
|
||||
|
||||
case 'condition': {
|
||||
if (!sourceHandle.startsWith(EDGE.CONDITION_PREFIX)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Invalid source handle "${sourceHandle}" for condition block. Must start with "${EDGE.CONDITION_PREFIX}"`,
|
||||
}
|
||||
}
|
||||
|
||||
const conditionsValue = sourceBlock?.subBlocks?.conditions?.value
|
||||
if (!conditionsValue) {
|
||||
return {
|
||||
@@ -866,6 +861,8 @@ function validateSourceHandleForBlock(
|
||||
}
|
||||
}
|
||||
|
||||
// validateConditionHandle accepts simple format (if, else-if-0, else),
|
||||
// legacy format (condition-{blockId}-if), and internal ID format (condition-{uuid})
|
||||
return validateConditionHandle(sourceHandle, sourceBlock.id, conditionsValue)
|
||||
}
|
||||
|
||||
@@ -879,13 +876,6 @@ function validateSourceHandleForBlock(
|
||||
}
|
||||
|
||||
case 'router_v2': {
|
||||
if (!sourceHandle.startsWith(EDGE.ROUTER_PREFIX)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `Invalid source handle "${sourceHandle}" for router_v2 block. Must start with "${EDGE.ROUTER_PREFIX}"`,
|
||||
}
|
||||
}
|
||||
|
||||
const routesValue = sourceBlock?.subBlocks?.routes?.value
|
||||
if (!routesValue) {
|
||||
return {
|
||||
@@ -894,6 +884,8 @@ function validateSourceHandleForBlock(
|
||||
}
|
||||
}
|
||||
|
||||
// validateRouterHandle accepts simple format (route-0, route-1),
|
||||
// legacy format (router-{blockId}-route-1), and internal ID format (router-{uuid})
|
||||
return validateRouterHandle(sourceHandle, sourceBlock.id, routesValue)
|
||||
}
|
||||
|
||||
@@ -910,7 +902,12 @@ function validateSourceHandleForBlock(
|
||||
|
||||
/**
|
||||
* Validates condition handle references a valid condition in the block.
|
||||
* Accepts both internal IDs (condition-blockId-if) and semantic keys (condition-blockId-else-if)
|
||||
* Accepts multiple formats:
|
||||
* - Simple format: "if", "else-if-0", "else-if-1", "else"
|
||||
* - Legacy semantic format: "condition-{blockId}-if", "condition-{blockId}-else-if"
|
||||
* - Internal ID format: "condition-{conditionId}"
|
||||
*
|
||||
* Returns the normalized handle (condition-{conditionId}) for storage.
|
||||
*/
|
||||
function validateConditionHandle(
|
||||
sourceHandle: string,
|
||||
@@ -943,48 +940,80 @@ function validateConditionHandle(
|
||||
}
|
||||
}
|
||||
|
||||
const validHandles = new Set<string>()
|
||||
const semanticPrefix = `condition-${blockId}-`
|
||||
let elseIfCount = 0
|
||||
// Build a map of all valid handle formats -> normalized handle (condition-{conditionId})
|
||||
const handleToNormalized = new Map<string, string>()
|
||||
const legacySemanticPrefix = `condition-${blockId}-`
|
||||
let elseIfIndex = 0
|
||||
|
||||
for (const condition of conditions) {
|
||||
if (condition.id) {
|
||||
validHandles.add(`condition-${condition.id}`)
|
||||
}
|
||||
if (!condition.id) continue
|
||||
|
||||
const normalizedHandle = `condition-${condition.id}`
|
||||
const title = condition.title?.toLowerCase()
|
||||
|
||||
// Always accept internal ID format
|
||||
handleToNormalized.set(normalizedHandle, normalizedHandle)
|
||||
|
||||
if (title === 'if') {
|
||||
// Simple format: "if"
|
||||
handleToNormalized.set('if', normalizedHandle)
|
||||
// Legacy format: "condition-{blockId}-if"
|
||||
handleToNormalized.set(`${legacySemanticPrefix}if`, normalizedHandle)
|
||||
} else if (title === 'else if') {
|
||||
// Simple format: "else-if-0", "else-if-1", etc. (0-indexed)
|
||||
handleToNormalized.set(`else-if-${elseIfIndex}`, normalizedHandle)
|
||||
// Legacy format: "condition-{blockId}-else-if" for first, "condition-{blockId}-else-if-2" for second
|
||||
if (elseIfIndex === 0) {
|
||||
handleToNormalized.set(`${legacySemanticPrefix}else-if`, normalizedHandle)
|
||||
} else {
|
||||
handleToNormalized.set(
|
||||
`${legacySemanticPrefix}else-if-${elseIfIndex + 1}`,
|
||||
normalizedHandle
|
||||
)
|
||||
}
|
||||
elseIfIndex++
|
||||
} else if (title === 'else') {
|
||||
// Simple format: "else"
|
||||
handleToNormalized.set('else', normalizedHandle)
|
||||
// Legacy format: "condition-{blockId}-else"
|
||||
handleToNormalized.set(`${legacySemanticPrefix}else`, normalizedHandle)
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedHandle = handleToNormalized.get(sourceHandle)
|
||||
if (normalizedHandle) {
|
||||
return { valid: true, normalizedHandle }
|
||||
}
|
||||
|
||||
// Build list of valid simple format options for error message
|
||||
const simpleOptions: string[] = []
|
||||
elseIfIndex = 0
|
||||
for (const condition of conditions) {
|
||||
const title = condition.title?.toLowerCase()
|
||||
if (title === 'if') {
|
||||
validHandles.add(`${semanticPrefix}if`)
|
||||
simpleOptions.push('if')
|
||||
} else if (title === 'else if') {
|
||||
elseIfCount++
|
||||
validHandles.add(
|
||||
elseIfCount === 1 ? `${semanticPrefix}else-if` : `${semanticPrefix}else-if-${elseIfCount}`
|
||||
)
|
||||
simpleOptions.push(`else-if-${elseIfIndex}`)
|
||||
elseIfIndex++
|
||||
} else if (title === 'else') {
|
||||
validHandles.add(`${semanticPrefix}else`)
|
||||
simpleOptions.push('else')
|
||||
}
|
||||
}
|
||||
|
||||
if (validHandles.has(sourceHandle)) {
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
const validOptions = Array.from(validHandles).slice(0, 5)
|
||||
const moreCount = validHandles.size - validOptions.length
|
||||
let validOptionsStr = validOptions.join(', ')
|
||||
if (moreCount > 0) {
|
||||
validOptionsStr += `, ... and ${moreCount} more`
|
||||
}
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
error: `Invalid condition handle "${sourceHandle}". Valid handles: ${validOptionsStr}`,
|
||||
error: `Invalid condition handle "${sourceHandle}". Valid handles: ${simpleOptions.join(', ')}`,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates router handle references a valid route in the block.
|
||||
* Accepts both internal IDs (router-{routeId}) and semantic keys (router-{blockId}-route-1)
|
||||
* Accepts multiple formats:
|
||||
* - Simple format: "route-0", "route-1", "route-2" (0-indexed)
|
||||
* - Legacy semantic format: "router-{blockId}-route-1" (1-indexed)
|
||||
* - Internal ID format: "router-{routeId}"
|
||||
*
|
||||
* Returns the normalized handle (router-{routeId}) for storage.
|
||||
*/
|
||||
function validateRouterHandle(
|
||||
sourceHandle: string,
|
||||
@@ -1017,47 +1046,48 @@ function validateRouterHandle(
|
||||
}
|
||||
}
|
||||
|
||||
const validHandles = new Set<string>()
|
||||
const semanticPrefix = `router-${blockId}-`
|
||||
// Build a map of all valid handle formats -> normalized handle (router-{routeId})
|
||||
const handleToNormalized = new Map<string, string>()
|
||||
const legacySemanticPrefix = `router-${blockId}-`
|
||||
|
||||
for (let i = 0; i < routes.length; i++) {
|
||||
const route = routes[i]
|
||||
if (!route.id) continue
|
||||
|
||||
// Accept internal ID format: router-{uuid}
|
||||
if (route.id) {
|
||||
validHandles.add(`router-${route.id}`)
|
||||
}
|
||||
const normalizedHandle = `router-${route.id}`
|
||||
|
||||
// Accept 1-indexed route number format: router-{blockId}-route-1, router-{blockId}-route-2, etc.
|
||||
validHandles.add(`${semanticPrefix}route-${i + 1}`)
|
||||
// Always accept internal ID format: router-{uuid}
|
||||
handleToNormalized.set(normalizedHandle, normalizedHandle)
|
||||
|
||||
// Simple format: route-0, route-1, etc. (0-indexed)
|
||||
handleToNormalized.set(`route-${i}`, normalizedHandle)
|
||||
|
||||
// Legacy 1-indexed route number format: router-{blockId}-route-1
|
||||
handleToNormalized.set(`${legacySemanticPrefix}route-${i + 1}`, normalizedHandle)
|
||||
|
||||
// Accept normalized title format: router-{blockId}-{normalized-title}
|
||||
// Normalize: lowercase, replace spaces with dashes, remove special chars
|
||||
if (route.title && typeof route.title === 'string') {
|
||||
const normalizedTitle = route.title
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '')
|
||||
if (normalizedTitle) {
|
||||
validHandles.add(`${semanticPrefix}${normalizedTitle}`)
|
||||
handleToNormalized.set(`${legacySemanticPrefix}${normalizedTitle}`, normalizedHandle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validHandles.has(sourceHandle)) {
|
||||
return { valid: true }
|
||||
const normalizedHandle = handleToNormalized.get(sourceHandle)
|
||||
if (normalizedHandle) {
|
||||
return { valid: true, normalizedHandle }
|
||||
}
|
||||
|
||||
const validOptions = Array.from(validHandles).slice(0, 5)
|
||||
const moreCount = validHandles.size - validOptions.length
|
||||
let validOptionsStr = validOptions.join(', ')
|
||||
if (moreCount > 0) {
|
||||
validOptionsStr += `, ... and ${moreCount} more`
|
||||
}
|
||||
// Build list of valid simple format options for error message
|
||||
const simpleOptions = routes.map((_, i) => `route-${i}`)
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
error: `Invalid router handle "${sourceHandle}". Valid handles: ${validOptionsStr}`,
|
||||
error: `Invalid router handle "${sourceHandle}". Valid handles: ${simpleOptions.join(', ')}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1172,10 +1202,13 @@ function createValidatedEdge(
|
||||
return false
|
||||
}
|
||||
|
||||
// Use normalized handle if available (e.g., 'if' -> 'condition-{uuid}')
|
||||
const finalSourceHandle = sourceValidation.normalizedHandle || sourceHandle
|
||||
|
||||
modifiedState.edges.push({
|
||||
id: crypto.randomUUID(),
|
||||
source: sourceBlockId,
|
||||
sourceHandle,
|
||||
sourceHandle: finalSourceHandle,
|
||||
target: targetBlockId,
|
||||
targetHandle,
|
||||
type: 'default',
|
||||
@@ -1184,7 +1217,11 @@ function createValidatedEdge(
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds connections as edges for a block
|
||||
* Adds connections as edges for a block.
|
||||
* Supports multiple target formats:
|
||||
* - String: "target-block-id"
|
||||
* - Object: { block: "target-block-id", handle?: "custom-target-handle" }
|
||||
* - Array of strings or objects
|
||||
*/
|
||||
function addConnectionsAsEdges(
|
||||
modifiedState: any,
|
||||
@@ -1194,19 +1231,34 @@ function addConnectionsAsEdges(
|
||||
skippedItems?: SkippedItem[]
|
||||
): void {
|
||||
Object.entries(connections).forEach(([sourceHandle, targets]) => {
|
||||
const targetArray = Array.isArray(targets) ? targets : [targets]
|
||||
targetArray.forEach((targetId: string) => {
|
||||
if (targets === null) return
|
||||
|
||||
const addEdgeForTarget = (targetBlock: string, targetHandle?: string) => {
|
||||
createValidatedEdge(
|
||||
modifiedState,
|
||||
blockId,
|
||||
targetId,
|
||||
targetBlock,
|
||||
sourceHandle,
|
||||
'target',
|
||||
targetHandle || 'target',
|
||||
'add_edge',
|
||||
logger,
|
||||
skippedItems
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof targets === 'string') {
|
||||
addEdgeForTarget(targets)
|
||||
} else if (Array.isArray(targets)) {
|
||||
targets.forEach((target: any) => {
|
||||
if (typeof target === 'string') {
|
||||
addEdgeForTarget(target)
|
||||
} else if (target?.block) {
|
||||
addEdgeForTarget(target.block, target.handle)
|
||||
}
|
||||
})
|
||||
} else if (typeof targets === 'object' && targets?.block) {
|
||||
addEdgeForTarget(targets.block, targets.handle)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -269,11 +269,12 @@ function sanitizeSubBlocks(
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert internal condition handle (condition-{uuid}) to semantic format (condition-{blockId}-if)
|
||||
* Convert internal condition handle (condition-{uuid}) to simple format (if, else-if-0, else)
|
||||
* Uses 0-indexed numbering for else-if conditions
|
||||
*/
|
||||
function convertConditionHandleToSemantic(
|
||||
function convertConditionHandleToSimple(
|
||||
handle: string,
|
||||
blockId: string,
|
||||
_blockId: string,
|
||||
block: BlockState
|
||||
): string {
|
||||
if (!handle.startsWith('condition-')) {
|
||||
@@ -300,27 +301,24 @@ function convertConditionHandleToSemantic(
|
||||
return handle
|
||||
}
|
||||
|
||||
// Find the condition by ID and generate semantic handle
|
||||
let elseIfCount = 0
|
||||
// Find the condition by ID and generate simple handle
|
||||
let elseIfIndex = 0
|
||||
for (const condition of conditions) {
|
||||
const title = condition.title?.toLowerCase()
|
||||
if (condition.id === conditionId) {
|
||||
if (title === 'if') {
|
||||
return `condition-${blockId}-if`
|
||||
return 'if'
|
||||
}
|
||||
if (title === 'else if') {
|
||||
elseIfCount++
|
||||
return elseIfCount === 1
|
||||
? `condition-${blockId}-else-if`
|
||||
: `condition-${blockId}-else-if-${elseIfCount}`
|
||||
return `else-if-${elseIfIndex}`
|
||||
}
|
||||
if (title === 'else') {
|
||||
return `condition-${blockId}-else`
|
||||
return 'else'
|
||||
}
|
||||
}
|
||||
// Count else-ifs as we iterate
|
||||
// Count else-ifs as we iterate (for index tracking)
|
||||
if (title === 'else if') {
|
||||
elseIfCount++
|
||||
elseIfIndex++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,9 +327,10 @@ function convertConditionHandleToSemantic(
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert internal router handle (router-{uuid}) to semantic format (router-{blockId}-route-N)
|
||||
* Convert internal router handle (router-{uuid}) to simple format (route-0, route-1)
|
||||
* Uses 0-indexed numbering for routes
|
||||
*/
|
||||
function convertRouterHandleToSemantic(handle: string, blockId: string, block: BlockState): string {
|
||||
function convertRouterHandleToSimple(handle: string, _blockId: string, block: BlockState): string {
|
||||
if (!handle.startsWith('router-')) {
|
||||
return handle
|
||||
}
|
||||
@@ -356,10 +355,10 @@ function convertRouterHandleToSemantic(handle: string, blockId: string, block: B
|
||||
return handle
|
||||
}
|
||||
|
||||
// Find the route by ID and generate semantic handle (1-indexed)
|
||||
// Find the route by ID and generate simple handle (0-indexed)
|
||||
for (let i = 0; i < routes.length; i++) {
|
||||
if (routes[i].id === routeId) {
|
||||
return `router-${blockId}-route-${i + 1}`
|
||||
return `route-${i}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,15 +367,16 @@ function convertRouterHandleToSemantic(handle: string, blockId: string, block: B
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert source handle to semantic format for condition and router blocks
|
||||
* Convert source handle to simple format for condition and router blocks
|
||||
* Outputs: if, else-if-0, else (for conditions) and route-0, route-1 (for routers)
|
||||
*/
|
||||
function convertToSemanticHandle(handle: string, blockId: string, block: BlockState): string {
|
||||
function convertToSimpleHandle(handle: string, blockId: string, block: BlockState): string {
|
||||
if (handle.startsWith('condition-') && block.type === 'condition') {
|
||||
return convertConditionHandleToSemantic(handle, blockId, block)
|
||||
return convertConditionHandleToSimple(handle, blockId, block)
|
||||
}
|
||||
|
||||
if (handle.startsWith('router-') && block.type === 'router_v2') {
|
||||
return convertRouterHandleToSemantic(handle, blockId, block)
|
||||
return convertRouterHandleToSimple(handle, blockId, block)
|
||||
}
|
||||
|
||||
return handle
|
||||
@@ -400,12 +400,12 @@ function extractConnectionsForBlock(
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Group by source handle (converting to semantic format)
|
||||
// Group by source handle (converting to simple format)
|
||||
for (const edge of outgoingEdges) {
|
||||
let handle = edge.sourceHandle || 'source'
|
||||
|
||||
// Convert internal UUID handles to semantic format
|
||||
handle = convertToSemanticHandle(handle, blockId, block)
|
||||
// Convert internal UUID handles to simple format (if, else-if-0, route-0, etc.)
|
||||
handle = convertToSimpleHandle(handle, blockId, block)
|
||||
|
||||
if (!connections[handle]) {
|
||||
connections[handle] = []
|
||||
|
||||
Reference in New Issue
Block a user