owenkaplinsky commited on
Commit
63f07e3
·
1 Parent(s): 21aa146

Fix if/elif/else generation

Browse files
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(IFN: value)) // N starts at 0; you can make as many N as you want
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": "Specific MCP input slot name when type is 'input'. Use 'RN', where N is the number of the output slot you want to put something into.",
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
- statements += statementCode;
 
 
 
 
 
 
 
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 && currentValue) {
638
  currentKey = currentKey.trim();
639
- currentValue = currentValue.trim();
640
-
641
- // Parse the value
642
- if (currentValue.match(/^\w+\s*\(inputs\(/)) {
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
- // This is a string (remove quotes if present)
653
- result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- // Find an appropriate input to connect to
709
- // Try common statement inputs first
710
- const statementInputs = ['BODY', 'DO', 'THEN', 'ELSE', 'STACK'];
711
  let connected = false;
712
-
713
- for (const inputName of statementInputs) {
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:', inputName);
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:', inputName);
736
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  }