Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
63f07e3
1
Parent(s):
21aa146
Fix if/elif/else generation
Browse files- project/blocks.txt +1 -1
- project/chat.py +22 -8
- project/src/generators/chat.js +77 -1
- project/src/index.js +165 -25
project/blocks.txt
CHANGED
|
@@ -7,7 +7,7 @@ make_json(inputs(KEYN: value, FIELDN: value)) // N starts at 0; you can make as
|
|
| 7 |
call_api(inputs(METHOD: "GET/POST/PUT/DELETE", URL: value, HEADERS: value)) // HEADERS is optional
|
| 8 |
|
| 9 |
# Logic
|
| 10 |
-
controls_if(inputs(
|
| 11 |
logic_negate(inputs())
|
| 12 |
logic_boolean(inputs(BOOL: "TRUE/FALSE"))
|
| 13 |
logic_null(inputs())
|
|
|
|
| 7 |
call_api(inputs(METHOD: "GET/POST/PUT/DELETE", URL: value, HEADERS: value)) // HEADERS is optional
|
| 8 |
|
| 9 |
# Logic
|
| 10 |
+
controls_if(inputs(IF: value, IFELSEN0: value, ELSE)) // IF is REQUIRED (the condition). IFELSEN0, IFELSEN1, etc are OPTIONAL (additional else-if conditions). ELSE is OPTIONAL (no value needed, just include the word). DO NOT use input_name with controls_if creation; specify all conditions in the inputs. After creating, use input_name to place statements: "DO0" (IF then-statements), "DO1" (first ELSE-IF then-statements), "ELSE" (final else statements)
|
| 11 |
logic_negate(inputs())
|
| 12 |
logic_boolean(inputs(BOOL: "TRUE/FALSE"))
|
| 13 |
logic_null(inputs())
|
project/chat.py
CHANGED
|
@@ -137,12 +137,6 @@ def delete_block(block_id):
|
|
| 137 |
|
| 138 |
def create_block(block_spec, blockID=None, placement_type=None, input_name=None):
|
| 139 |
try:
|
| 140 |
-
print(f"[CREATE REQUEST] Attempting to create block: {block_spec}")
|
| 141 |
-
if blockID:
|
| 142 |
-
print(f"[CREATE REQUEST] Placement type: {placement_type}, block ID: {blockID}")
|
| 143 |
-
if input_name:
|
| 144 |
-
print(f"[CREATE REQUEST] Input name: {input_name}")
|
| 145 |
-
|
| 146 |
# Generate a unique request ID
|
| 147 |
import uuid
|
| 148 |
request_id = str(uuid.uuid4())
|
|
@@ -160,7 +154,6 @@ def create_block(block_spec, blockID=None, placement_type=None, input_name=None)
|
|
| 160 |
if input_name:
|
| 161 |
queue_data["input_name"] = input_name
|
| 162 |
creation_queue.put(queue_data)
|
| 163 |
-
print(f"[CREATE REQUEST] Added to queue with ID: {request_id}")
|
| 164 |
|
| 165 |
# Wait for result with timeout
|
| 166 |
import time
|
|
@@ -616,12 +609,33 @@ def create_gradio_interface():
|
|
| 616 |
- Any block that outputs a result for something else to use
|
| 617 |
|
| 618 |
### How to Place Blocks
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
|
| 620 |
**Placement types** - use `blockID` and `type` parameters:
|
| 621 |
|
| 622 |
- `type: "under"` - For statement blocks inside containers. Create the container first, then create statement blocks using the container's ID.
|
| 623 |
Example: Create a loop first, then use `blockID: loopID, type: "under"` to place code inside it.
|
| 624 |
Also for stackable blocks: create one, get its ID, then create the next one with the previous block's ID and `type: "under"`.
|
|
|
|
|
|
|
| 625 |
|
| 626 |
- `type: "input"` - ONLY for value blocks placed in MCP output slots. Provide `input_name` with the output slot name (R0, R1, R2, etc).
|
| 627 |
Example: `text(inputs(TEXT: "hello"))` with `type: "input", input_name: "R0"` places the text block in the MCP's first output slot.
|
|
@@ -756,7 +770,7 @@ def create_gradio_interface():
|
|
| 756 |
},
|
| 757 |
"input_name": {
|
| 758 |
"type": "string",
|
| 759 |
-
"description": "
|
| 760 |
},
|
| 761 |
},
|
| 762 |
"required": ["command"],
|
|
|
|
| 137 |
|
| 138 |
def create_block(block_spec, blockID=None, placement_type=None, input_name=None):
|
| 139 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
# Generate a unique request ID
|
| 141 |
import uuid
|
| 142 |
request_id = str(uuid.uuid4())
|
|
|
|
| 154 |
if input_name:
|
| 155 |
queue_data["input_name"] = input_name
|
| 156 |
creation_queue.put(queue_data)
|
|
|
|
| 157 |
|
| 158 |
# Wait for result with timeout
|
| 159 |
import time
|
|
|
|
| 609 |
- Any block that outputs a result for something else to use
|
| 610 |
|
| 611 |
### How to Place Blocks
|
| 612 |
+
**IF/ELSE Blocks:**
|
| 613 |
+
|
| 614 |
+
The entire IF/ELSE structure must be created in one `create_block` call.
|
| 615 |
+
|
| 616 |
+
**Structure:**
|
| 617 |
+
`controls_if(inputs(IF: cond, IFELSEN0: cond2, IFELSEN1: cond3, ELSE))`
|
| 618 |
+
|
| 619 |
+
- `IF:` first condition (required)
|
| 620 |
+
- `IFELSEN#:` else-if conditions (optional)
|
| 621 |
+
- `ELSE` keyword (optional, no value)
|
| 622 |
+
|
| 623 |
+
**Do NOT:**
|
| 624 |
+
- Add ELSE or ELSE-IF later using `input_name`
|
| 625 |
+
- Give ELSE a value
|
| 626 |
+
|
| 627 |
+
**Correct placement after creation:**
|
| 628 |
+
- `input_name: "DO0"`: IF branch
|
| 629 |
+
- `input_name: "DO1"`: first ELSE-IF branch
|
| 630 |
+
- `input_name: "ELSE"`: ELSE branch
|
| 631 |
|
| 632 |
**Placement types** - use `blockID` and `type` parameters:
|
| 633 |
|
| 634 |
- `type: "under"` - For statement blocks inside containers. Create the container first, then create statement blocks using the container's ID.
|
| 635 |
Example: Create a loop first, then use `blockID: loopID, type: "under"` to place code inside it.
|
| 636 |
Also for stackable blocks: create one, get its ID, then create the next one with the previous block's ID and `type: "under"`.
|
| 637 |
+
**Optional: use `input_name` to specify which statement input to place the block in (e.g., "DO0", "DO1", "ELSE" for IF blocks).**
|
| 638 |
+
Example: `create_block(text_append(...), blockID: ifBlockID, type: "under", input_name: "DO0")` places the block in the first THEN branch of an IF block.
|
| 639 |
|
| 640 |
- `type: "input"` - ONLY for value blocks placed in MCP output slots. Provide `input_name` with the output slot name (R0, R1, R2, etc).
|
| 641 |
Example: `text(inputs(TEXT: "hello"))` with `type: "input", input_name: "R0"` places the text block in the MCP's first output slot.
|
|
|
|
| 770 |
},
|
| 771 |
"input_name": {
|
| 772 |
"type": "string",
|
| 773 |
+
"description": "ONLY for two cases: placing value blocks into MCP output slots using 'R<N>', and placing statement blocks into specific branches of controls_if (DO0, DO1, ELSE). It must NEVER be used for anything else.",
|
| 774 |
},
|
| 775 |
},
|
| 776 |
"required": ["command"],
|
project/src/generators/chat.js
CHANGED
|
@@ -223,6 +223,75 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
|
|
| 223 |
if (num !== null && num !== undefined) {
|
| 224 |
inputs.push(`NUM: ${num}`);
|
| 225 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
} else {
|
| 227 |
// Generic field value extraction for other blocks
|
| 228 |
// Get all inputs to check for fields
|
|
@@ -266,7 +335,14 @@ chatGenerator.blockToCode = function (block, opt_thisOnly) {
|
|
| 266 |
if (input.type === Blockly.NEXT_STATEMENT && input.connection) {
|
| 267 |
const statementCode = this.statementToCode(block, input.name);
|
| 268 |
if (statementCode) {
|
| 269 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
}
|
| 271 |
}
|
| 272 |
}
|
|
|
|
| 223 |
if (num !== null && num !== undefined) {
|
| 224 |
inputs.push(`NUM: ${num}`);
|
| 225 |
}
|
| 226 |
+
} else if (blockType === 'controls_if') {
|
| 227 |
+
// Special handling for if/else blocks
|
| 228 |
+
// Extract all condition values in the proper format: IF, IFELSEN0, IFELSEN1, etc.
|
| 229 |
+
const ifCount = (block.inputList.filter(input => input.name && input.name.match(/^IF\d+$/)).length) || 1;
|
| 230 |
+
|
| 231 |
+
// Get the first IF condition
|
| 232 |
+
const if0Input = block.getInput('IF0');
|
| 233 |
+
if (if0Input && if0Input.connection) {
|
| 234 |
+
const condValue = this.valueToCode(block, 'IF0', this.ORDER_ATOMIC);
|
| 235 |
+
if (condValue) {
|
| 236 |
+
inputs.push(`IF: ${condValue}`);
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
// Get all additional IF conditions (IF1, IF2, etc.) as IFELSEN0, IFELSEN1, etc.
|
| 241 |
+
for (let i = 1; i < ifCount; i++) {
|
| 242 |
+
const ifInput = block.getInput('IF' + i);
|
| 243 |
+
if (ifInput && ifInput.connection) {
|
| 244 |
+
const condValue = this.valueToCode(block, 'IF' + i, this.ORDER_ATOMIC);
|
| 245 |
+
if (condValue) {
|
| 246 |
+
inputs.push(`IFELSEN${i - 1}: ${condValue}`);
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
// Check if ELSE exists (look for DO blocks and see if there's an ELSE)
|
| 252 |
+
const hasElse = block.getInput('ELSE') !== null && block.getInput('ELSE') !== undefined;
|
| 253 |
+
|
| 254 |
+
// If ELSE exists, add it to inputs (it's just a marker, no condition value)
|
| 255 |
+
if (hasElse) {
|
| 256 |
+
inputs.push(`ELSE`);
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
// Generate the controls_if call with conditions
|
| 260 |
+
let code = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
|
| 261 |
+
|
| 262 |
+
// Now get all the statement blocks with proper formatting
|
| 263 |
+
// DO0, DO1, etc. are indented under the if
|
| 264 |
+
// ELSE blocks are labeled with "Else:" prefix
|
| 265 |
+
for (let i = 0; i < ifCount; i++) {
|
| 266 |
+
const doInput = block.getInput('DO' + i);
|
| 267 |
+
if (doInput) {
|
| 268 |
+
const doCode = this.statementToCode(block, 'DO' + i);
|
| 269 |
+
if (doCode) {
|
| 270 |
+
// Indent each line of the statement code
|
| 271 |
+
const indentedCode = doCode.split('\n').map(line => line ? ' ' + line : '').join('\n');
|
| 272 |
+
code += '\n' + indentedCode;
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
// Get ELSE block if it exists - format it with "Else:" label
|
| 278 |
+
if (hasElse) {
|
| 279 |
+
const elseCode = this.statementToCode(block, 'ELSE');
|
| 280 |
+
if (elseCode) {
|
| 281 |
+
code += '\nElse:\n';
|
| 282 |
+
// Indent each line of the else code
|
| 283 |
+
const indentedCode = elseCode.split('\n').map(line => line ? ' ' + line : '').join('\n');
|
| 284 |
+
code += indentedCode;
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
// Handle the next block in the sequence for statement chaining
|
| 289 |
+
if (!opt_thisOnly) {
|
| 290 |
+
const nextCode = this.scrub_(block, code, opt_thisOnly);
|
| 291 |
+
return nextCode;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
return code + '\n';
|
| 295 |
} else {
|
| 296 |
// Generic field value extraction for other blocks
|
| 297 |
// Get all inputs to check for fields
|
|
|
|
| 335 |
if (input.type === Blockly.NEXT_STATEMENT && input.connection) {
|
| 336 |
const statementCode = this.statementToCode(block, input.name);
|
| 337 |
if (statementCode) {
|
| 338 |
+
// Indent statement code (4 spaces) if this block will be a statement block
|
| 339 |
+
if (!block.outputConnection) {
|
| 340 |
+
// Only indent for statement blocks; value blocks handle their own formatting
|
| 341 |
+
const indentedCode = statementCode.split('\n').map(line => line ? ' ' + line : '').join('\n');
|
| 342 |
+
statements += indentedCode;
|
| 343 |
+
} else {
|
| 344 |
+
statements += statementCode;
|
| 345 |
+
}
|
| 346 |
}
|
| 347 |
}
|
| 348 |
}
|
project/src/index.js
CHANGED
|
@@ -490,6 +490,69 @@ const setupUnifiedStream = () => {
|
|
| 490 |
}
|
| 491 |
}
|
| 492 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
} else {
|
| 494 |
// Normal block handling
|
| 495 |
for (const [key, value] of Object.entries(inputs)) {
|
|
@@ -538,6 +601,45 @@ const setupUnifiedStream = () => {
|
|
| 538 |
// Initialize the block (renders it)
|
| 539 |
newBlock.initSvg();
|
| 540 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
// Only position the top-level block
|
| 542 |
if (shouldPosition) {
|
| 543 |
// Find a good position that doesn't overlap existing blocks
|
|
@@ -634,23 +736,29 @@ const setupUnifiedStream = () => {
|
|
| 634 |
}
|
| 635 |
|
| 636 |
// Handle the last key-value pair
|
| 637 |
-
if (currentKey
|
| 638 |
currentKey = currentKey.trim();
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
// This is a nested block
|
| 644 |
-
result[currentKey] = currentValue;
|
| 645 |
-
} else if (currentValue.match(/^-?\d+(\.\d+)?$/)) {
|
| 646 |
-
// This is a number
|
| 647 |
-
result[currentKey] = parseFloat(currentValue);
|
| 648 |
-
} else if (currentValue === 'true' || currentValue === 'false') {
|
| 649 |
-
// This is a boolean
|
| 650 |
-
result[currentKey] = currentValue === 'true';
|
| 651 |
} else {
|
| 652 |
-
|
| 653 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 654 |
}
|
| 655 |
}
|
| 656 |
|
|
@@ -705,13 +813,10 @@ const setupUnifiedStream = () => {
|
|
| 705 |
if (parentBlock) {
|
| 706 |
console.log('[SSE CREATE] Attaching to parent block:', data.blockID);
|
| 707 |
|
| 708 |
-
//
|
| 709 |
-
// Try common statement inputs first
|
| 710 |
-
const statementInputs = ['BODY', 'DO', 'THEN', 'ELSE', 'STACK'];
|
| 711 |
let connected = false;
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
const input = parentBlock.getInput(inputName);
|
| 715 |
if (input && input.type === Blockly.NEXT_STATEMENT) {
|
| 716 |
// Check if something is already connected
|
| 717 |
if (input.connection && !input.connection.targetBlock()) {
|
|
@@ -719,8 +824,7 @@ const setupUnifiedStream = () => {
|
|
| 719 |
if (newBlock.previousConnection) {
|
| 720 |
input.connection.connect(newBlock.previousConnection);
|
| 721 |
connected = true;
|
| 722 |
-
console.log('[SSE CREATE] Connected to input:',
|
| 723 |
-
break;
|
| 724 |
}
|
| 725 |
} else if (input.connection && input.connection.targetBlock()) {
|
| 726 |
// Find the last block in the stack
|
|
@@ -732,8 +836,44 @@ const setupUnifiedStream = () => {
|
|
| 732 |
if (lastBlock.nextConnection && newBlock.previousConnection) {
|
| 733 |
lastBlock.nextConnection.connect(newBlock.previousConnection);
|
| 734 |
connected = true;
|
| 735 |
-
console.log('[SSE CREATE] Connected to end of stack in input:',
|
| 736 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 737 |
}
|
| 738 |
}
|
| 739 |
}
|
|
|
|
| 490 |
}
|
| 491 |
}
|
| 492 |
}
|
| 493 |
+
} else if (blockType === 'controls_if') {
|
| 494 |
+
// Special handling for if/else blocks
|
| 495 |
+
// IF is required, IFELSEN0, IFELSEN1, etc are optional, ELSE is optional
|
| 496 |
+
// Each condition block goes into IF, IFELSEN0, IFELSEN1, etc inputs
|
| 497 |
+
|
| 498 |
+
// Count how many IF/IFELSEN conditions we have
|
| 499 |
+
let conditionCount = 0;
|
| 500 |
+
const conditionBlocks = {};
|
| 501 |
+
let hasElse = false;
|
| 502 |
+
|
| 503 |
+
console.log('[SSE CREATE] controls_if inputs:', inputs);
|
| 504 |
+
|
| 505 |
+
for (const [key, value] of Object.entries(inputs)) {
|
| 506 |
+
if (key === 'IF') {
|
| 507 |
+
conditionBlocks['IF'] = value;
|
| 508 |
+
} else if (key.match(/^IFELSEN\d+$/)) {
|
| 509 |
+
conditionBlocks[key] = value;
|
| 510 |
+
} else if (key === 'ELSE' && value === true) {
|
| 511 |
+
// ELSE is a marker with no value (set to true by parseInputs)
|
| 512 |
+
console.log('[SSE CREATE] Detected ELSE marker');
|
| 513 |
+
hasElse = true;
|
| 514 |
+
}
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
console.log('[SSE CREATE] controls_if parsed: hasElse =', hasElse);
|
| 518 |
+
|
| 519 |
+
// Create inputs for each condition
|
| 520 |
+
if (conditionBlocks['IF']) {
|
| 521 |
+
const ifValue = conditionBlocks['IF'];
|
| 522 |
+
if (typeof ifValue === 'string' && ifValue.match(/^\w+\s*\(inputs\(/)) {
|
| 523 |
+
const childBlock = parseAndCreateBlock(ifValue);
|
| 524 |
+
const input = newBlock.getInput('IF0');
|
| 525 |
+
if (input && input.connection && childBlock.outputConnection) {
|
| 526 |
+
childBlock.outputConnection.connect(input.connection);
|
| 527 |
+
}
|
| 528 |
+
}
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
// Handle IFELSEN conditions
|
| 532 |
+
let elseIfCount = 0;
|
| 533 |
+
for (const [key, value] of Object.entries(conditionBlocks)) {
|
| 534 |
+
const elseIfMatch = key.match(/^IFELSEN(\d+)$/);
|
| 535 |
+
if (elseIfMatch) {
|
| 536 |
+
const elseIfValue = value;
|
| 537 |
+
if (typeof elseIfValue === 'string' && elseIfValue.match(/^\w+\s*\(inputs\(/)) {
|
| 538 |
+
// Blockly uses a mutator to add IF/ELSE IF blocks, so we need to configure that
|
| 539 |
+
// For now, just try to create the structure
|
| 540 |
+
const childBlock = parseAndCreateBlock(elseIfValue);
|
| 541 |
+
|
| 542 |
+
// The inputs will be IF1, IF2, etc. for additional conditions
|
| 543 |
+
const ifInputName = 'IF' + (elseIfCount + 1);
|
| 544 |
+
const input = newBlock.getInput(ifInputName);
|
| 545 |
+
if (input && input.connection && childBlock.outputConnection) {
|
| 546 |
+
childBlock.outputConnection.connect(input.connection);
|
| 547 |
+
}
|
| 548 |
+
elseIfCount++;
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
// Store these for later - we'll apply them after initSvg()
|
| 554 |
+
newBlock.pendingElseifCount_ = elseIfCount;
|
| 555 |
+
newBlock.pendingElseCount_ = hasElse ? 1 : 0;
|
| 556 |
} else {
|
| 557 |
// Normal block handling
|
| 558 |
for (const [key, value] of Object.entries(inputs)) {
|
|
|
|
| 601 |
// Initialize the block (renders it)
|
| 602 |
newBlock.initSvg();
|
| 603 |
|
| 604 |
+
// Apply pending controls_if mutations (must be after initSvg)
|
| 605 |
+
if (newBlock.type === 'controls_if' && (newBlock.pendingElseifCount_ > 0 || newBlock.pendingElseCount_ > 0)) {
|
| 606 |
+
console.log('[SSE CREATE] Applying controls_if mutation:', {
|
| 607 |
+
elseifCount: newBlock.pendingElseifCount_,
|
| 608 |
+
elseCount: newBlock.pendingElseCount_
|
| 609 |
+
});
|
| 610 |
+
|
| 611 |
+
// Use the loadExtraState method if available (Blockly's preferred way)
|
| 612 |
+
if (typeof newBlock.loadExtraState === 'function') {
|
| 613 |
+
const state = {};
|
| 614 |
+
if (newBlock.pendingElseifCount_ > 0) {
|
| 615 |
+
state.elseIfCount = newBlock.pendingElseifCount_;
|
| 616 |
+
}
|
| 617 |
+
if (newBlock.pendingElseCount_ > 0) {
|
| 618 |
+
state.hasElse = true;
|
| 619 |
+
}
|
| 620 |
+
console.log('[SSE CREATE] Using loadExtraState with:', state);
|
| 621 |
+
newBlock.loadExtraState(state);
|
| 622 |
+
} else {
|
| 623 |
+
// Fallback: Set the internal state variables and call updateShape_
|
| 624 |
+
newBlock.elseifCount_ = newBlock.pendingElseifCount_;
|
| 625 |
+
newBlock.elseCount_ = newBlock.pendingElseCount_;
|
| 626 |
+
|
| 627 |
+
if (typeof newBlock.updateShape_ === 'function') {
|
| 628 |
+
console.log('[SSE CREATE] Calling updateShape_ on controls_if');
|
| 629 |
+
newBlock.updateShape_();
|
| 630 |
+
}
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
// Verify the ELSE input was created
|
| 634 |
+
if (newBlock.pendingElseCount_ > 0) {
|
| 635 |
+
const elseInput = newBlock.getInput('ELSE');
|
| 636 |
+
console.log('[SSE CREATE] ELSE input after mutation:', elseInput);
|
| 637 |
+
if (!elseInput) {
|
| 638 |
+
console.error('[SSE CREATE] ELSE input was NOT created!');
|
| 639 |
+
}
|
| 640 |
+
}
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
// Only position the top-level block
|
| 644 |
if (shouldPosition) {
|
| 645 |
// Find a good position that doesn't overlap existing blocks
|
|
|
|
| 736 |
}
|
| 737 |
|
| 738 |
// Handle the last key-value pair
|
| 739 |
+
if (currentKey) {
|
| 740 |
currentKey = currentKey.trim();
|
| 741 |
+
|
| 742 |
+
// If there's no value, this is a flag/marker (like ELSE)
|
| 743 |
+
if (!currentValue) {
|
| 744 |
+
result[currentKey] = true; // Mark it as present
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
} else {
|
| 746 |
+
currentValue = currentValue.trim();
|
| 747 |
+
|
| 748 |
+
// Parse the value
|
| 749 |
+
if (currentValue.match(/^\w+\s*\(inputs\(/)) {
|
| 750 |
+
// This is a nested block
|
| 751 |
+
result[currentKey] = currentValue;
|
| 752 |
+
} else if (currentValue.match(/^-?\d+(\.\d+)?$/)) {
|
| 753 |
+
// This is a number
|
| 754 |
+
result[currentKey] = parseFloat(currentValue);
|
| 755 |
+
} else if (currentValue === 'true' || currentValue === 'false') {
|
| 756 |
+
// This is a boolean
|
| 757 |
+
result[currentKey] = currentValue === 'true';
|
| 758 |
+
} else {
|
| 759 |
+
// This is a string (remove quotes if present)
|
| 760 |
+
result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
|
| 761 |
+
}
|
| 762 |
}
|
| 763 |
}
|
| 764 |
|
|
|
|
| 813 |
if (parentBlock) {
|
| 814 |
console.log('[SSE CREATE] Attaching to parent block:', data.blockID);
|
| 815 |
|
| 816 |
+
// If input_name is specified, try to connect to that specific input first
|
|
|
|
|
|
|
| 817 |
let connected = false;
|
| 818 |
+
if (data.input_name) {
|
| 819 |
+
const input = parentBlock.getInput(data.input_name);
|
|
|
|
| 820 |
if (input && input.type === Blockly.NEXT_STATEMENT) {
|
| 821 |
// Check if something is already connected
|
| 822 |
if (input.connection && !input.connection.targetBlock()) {
|
|
|
|
| 824 |
if (newBlock.previousConnection) {
|
| 825 |
input.connection.connect(newBlock.previousConnection);
|
| 826 |
connected = true;
|
| 827 |
+
console.log('[SSE CREATE] Connected to specified input:', data.input_name);
|
|
|
|
| 828 |
}
|
| 829 |
} else if (input.connection && input.connection.targetBlock()) {
|
| 830 |
// Find the last block in the stack
|
|
|
|
| 836 |
if (lastBlock.nextConnection && newBlock.previousConnection) {
|
| 837 |
lastBlock.nextConnection.connect(newBlock.previousConnection);
|
| 838 |
connected = true;
|
| 839 |
+
console.log('[SSE CREATE] Connected to end of stack in specified input:', data.input_name);
|
| 840 |
+
}
|
| 841 |
+
}
|
| 842 |
+
} else {
|
| 843 |
+
error = `Specified input '${data.input_name}' not found or is not a statement input`;
|
| 844 |
+
console.warn('[SSE CREATE]', error);
|
| 845 |
+
}
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
// If not connected via specified input_name, try common statement inputs
|
| 849 |
+
if (!connected) {
|
| 850 |
+
const statementInputs = ['BODY', 'DO', 'THEN', 'ELSE', 'STACK'];
|
| 851 |
+
|
| 852 |
+
for (const inputName of statementInputs) {
|
| 853 |
+
const input = parentBlock.getInput(inputName);
|
| 854 |
+
if (input && input.type === Blockly.NEXT_STATEMENT) {
|
| 855 |
+
// Check if something is already connected
|
| 856 |
+
if (input.connection && !input.connection.targetBlock()) {
|
| 857 |
+
// Connect directly
|
| 858 |
+
if (newBlock.previousConnection) {
|
| 859 |
+
input.connection.connect(newBlock.previousConnection);
|
| 860 |
+
connected = true;
|
| 861 |
+
console.log('[SSE CREATE] Connected to input:', inputName);
|
| 862 |
+
break;
|
| 863 |
+
}
|
| 864 |
+
} else if (input.connection && input.connection.targetBlock()) {
|
| 865 |
+
// Find the last block in the stack
|
| 866 |
+
let lastBlock = input.connection.targetBlock();
|
| 867 |
+
while (lastBlock.nextConnection && lastBlock.nextConnection.targetBlock()) {
|
| 868 |
+
lastBlock = lastBlock.nextConnection.targetBlock();
|
| 869 |
+
}
|
| 870 |
+
// Connect to the end of the stack
|
| 871 |
+
if (lastBlock.nextConnection && newBlock.previousConnection) {
|
| 872 |
+
lastBlock.nextConnection.connect(newBlock.previousConnection);
|
| 873 |
+
connected = true;
|
| 874 |
+
console.log('[SSE CREATE] Connected to end of stack in input:', inputName);
|
| 875 |
+
break;
|
| 876 |
+
}
|
| 877 |
}
|
| 878 |
}
|
| 879 |
}
|