owenkaplinsky commited on
Commit
009a2d4
·
1 Parent(s): e53c90b

Add more blocks; blocks inside blocks

Browse files
README.md CHANGED
@@ -10,7 +10,13 @@ The interface has three main areas. The canvas on the left is where you build by
10
 
11
  In the Development tab, you see your generated Python code alongside a test interface. The interface automatically creates input fields matching your tool's parameters. After you arrange your blocks, click Refresh to update the test interface, enter values, and click Submit to run your code. Results appear in the output fields.
12
 
13
- The AI Chat tab is the core feature of Agent-Blockly. It connects you to an AI assistant that understands your exact block configuration and can help you build, modify, and debug your workflows. You communicate in natural language. You can ask it to add validation to an input, fix a broken API call, restructure your logic, or explain what your current blocks do. The assistant reads your blocks, understands your intent, and can modify your workspace directly in real time. You can also ask it to run your tool with sample inputs to see how it behaves. The chat interface is not just for asking questions - it's an active collaborator that reshapes your blocks as you iterate on your design.
 
 
 
 
 
 
14
 
15
  The File menu handles creating new projects, opening existing ones, and downloading your work. You can download just the generated Python code or the entire project as a JSON file. The Edit menu provides standard undo, redo, and a cleanup button to reorganize blocks. The Examples menu includes pre-built projects you can load to understand how to structure your own. You need to set your OpenAI API key through Settings before the system can execute language model calls or power the chat.
16
 
 
10
 
11
  In the Development tab, you see your generated Python code alongside a test interface. The interface automatically creates input fields matching your tool's parameters. After you arrange your blocks, click Refresh to update the test interface, enter values, and click Submit to run your code. Results appear in the output fields.
12
 
13
+ The AI Chat tab is the centerpiece of Agent-Blockly. It connects you with an AI assistant that fully understands your current block configuration and helps you build, modify, and debug your workflows through natural language. You can ask it to add input validation, fix a broken API call, reorganize your logic, or explain how your blocks work.
14
+
15
+ The assistant doesn’t just answer questions - it actively collaborates with you. It can edit your workspace in real time, run your tool with sample inputs, and show you how it behaves. It’s aware of your project’s structure and can make precise, context-aware changes as you iterate.
16
+
17
+ The AI is capable of complex manipulations, such as inserting blocks inside others, creating nested structures, and arranging deeply recursive workflows.
18
+
19
+ While it can handle a wide range of tasks, multi-step or highly complex changes work best when broken into smaller requests. Taking things one step at a time leads to more accurate and reliable results.
20
 
21
  The File menu handles creating new projects, opening existing ones, and downloading your work. You can download just the generated Python code or the entire project as a JSON file. The Edit menu provides standard undo, redo, and a cleanup button to reorganize blocks. The Examples menu includes pre-built projects you can load to understand how to structure your own. You need to set your OpenAI API key through Settings before the system can execute language model calls or power the chat.
22
 
project/blocks.txt CHANGED
@@ -3,24 +3,46 @@ llm_call(inputs(MODEL: "gpt-3.5-turbo-0125/gpt-4o-2024-08-06/gpt-5-mini-2025-08-
3
 
4
  # JSON and API
5
  in_json(inputs(NAME: value, JSON: value)) // Name is the value you want to extract from the JSON
 
 
6
 
7
  # Logic
 
8
  logic_negate(inputs())
9
  logic_boolean(inputs(BOOL: "TRUE/FALSE"))
10
  logic_null(inputs())
11
  logic_compare(inputs(OP: "EQ/NEQ/LT/LTE/GT/GTE", A: value, B: value))
12
  logic_operation(inputs(OP: "AND/OR", A: value, B: value))
13
 
 
 
 
 
 
14
  # Math
15
  math_number(inputs(NUM: value))
16
  math_arithmetic(inputs(OP: "ADD/MINUS/MULTIPLY/DIVIDE/POWER", A: value, B: value)) // A and B are required. They must be blocks, so you need to add math_number or an alternative
 
 
 
 
 
 
17
 
18
  # Text
19
  text(inputs(TEXT: value))
20
  text_length(VALUE: value)
21
  text_join(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
 
 
 
 
 
 
22
 
23
  # Lists
24
  lists_length(inputs(VALUE: value))
25
- lists_isEmpty(inputs(VALUE: value))
26
- lists_reverse(inputs(LIST: value))
 
 
 
3
 
4
  # JSON and API
5
  in_json(inputs(NAME: value, JSON: value)) // Name is the value you want to extract from the JSON
6
+ make_json(inputs(KEYN: value, FIELDN: value)) // N starts at 0; you can make as many N as you want. Each key goes with one field
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())
14
  logic_compare(inputs(OP: "EQ/NEQ/LT/LTE/GT/GTE", A: value, B: value))
15
  logic_operation(inputs(OP: "AND/OR", A: value, B: value))
16
 
17
+ # Loops
18
+ controls_repeat_ext(inputs(TIMES: value))
19
+ controls_whileUntil(inputs(MODE: "WHILE/UNTIL", BOOL: value))
20
+ controls_flow_statements(inputs(FLOW: "CONTINUE/BREAK")) // Must go **inside** of a loop
21
+
22
  # Math
23
  math_number(inputs(NUM: value))
24
  math_arithmetic(inputs(OP: "ADD/MINUS/MULTIPLY/DIVIDE/POWER", A: value, B: value)) // A and B are required. They must be blocks, so you need to add math_number or an alternative
25
+ math_single(inputs(OP: "ROOT/ABS/NEG/LN/LOG10/EXP/POW10", NUM: value)) // EXP is e^num
26
+ math_number_property(inputs(PROPERTY: "EVEN/ODD/PRIME/WHOLE/POSITIVE/NEGATIVE", NUMBER_TO_CHECK: value)) // Boolean output
27
+ math_round(inputs(OP: "ROUND/ROUNDUP/ROUNDDOWN", NUM: value))
28
+ math_modulo(inputs(DIVIDEND: value, DIVISOR: value))
29
+ math_constrain(VALUE: value, LOW: value, HIGH: value)
30
+ math_random_int(inputs(FROM: value, TO: value))
31
 
32
  # Text
33
  text(inputs(TEXT: value))
34
  text_length(VALUE: value)
35
  text_join(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
36
+ text_isEmpty(inputs(VALUE: value)) // Boolean output
37
+ text_changeCase(inputs(CASE: "UPPERCASE/LOWERCASE/TITLECASE", TEXT: value))
38
+ text_reverse(inputs(TEXT: value))
39
+ text_count(inputs(SUB: value, TEXT: value)) // Integer output
40
+ text_replace(inputs(FROM: value, TO: value, TEXT: value))
41
+ text_trim(inputs(MODE: "BOTH/LEFT/RIGHT", TEXT: value)) // Trim spaces from text
42
 
43
  # Lists
44
  lists_length(inputs(VALUE: value))
45
+ lists_isEmpty(inputs(VALUE: value)) // Boolean output
46
+ lists_reverse(inputs(LIST: value))
47
+ lists_create_with(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
48
+ lists_sort(inputs(TYPE: "NUMERIC/TEXT/IGNORE_CASE", DIRECTION: "1/-1")) // For direction, 1 is ascending, -1 is descending
project/chat.py CHANGED
@@ -227,10 +227,12 @@ def delete_block(block_id):
227
  traceback.print_exc()
228
  return f"Error deleting block: {str(e)}"
229
 
230
- def create_block(block_spec):
231
  """Create a block in the Blockly workspace"""
232
  try:
233
  print(f"[CREATE REQUEST] Attempting to create block: {block_spec}")
 
 
234
 
235
  # Generate a unique request ID
236
  import uuid
@@ -240,8 +242,11 @@ def create_block(block_spec):
240
  if request_id in creation_results:
241
  creation_results.pop(request_id)
242
 
243
- # Add to creation queue
244
- creation_queue.put({"request_id": request_id, "block_spec": block_spec})
 
 
 
245
  print(f"[CREATE REQUEST] Added to queue with ID: {request_id}")
246
 
247
  # Wait for result with timeout
@@ -485,6 +490,28 @@ List of blocks:
485
  {blocks_context}
486
 
487
  YOU CANNOT CREATE A MCP BLOCK OR EDIT ITS INPUTS. YOU MUST TELL THE USER TO DO SO.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  """
489
 
490
  tools = [
@@ -517,6 +544,10 @@ YOU CANNOT CREATE A MCP BLOCK OR EDIT ITS INPUTS. YOU MUST TELL THE USER TO DO S
517
  "type": "string",
518
  "description": "The create block command using the custom DSL format.",
519
  },
 
 
 
 
520
  },
521
  "required": ["command"],
522
  },
@@ -630,7 +661,8 @@ YOU CANNOT CREATE A MCP BLOCK OR EDIT ITS INPUTS. YOU MUST TELL THE USER TO DO S
630
 
631
  elif function_name == "create_block":
632
  command = function_args.get("command", "")
633
- tool_result = create_block(command)
 
634
  result_label = "Create Operation"
635
 
636
  elif function_name == "run_mcp":
@@ -657,7 +689,7 @@ YOU CANNOT CREATE A MCP BLOCK OR EDIT ITS INPUTS. YOU MUST TELL THE USER TO DO S
657
  # Update history with the tool call and result
658
  temp_history.append({"role": "user", "content": current_prompt})
659
  temp_history.append({"role": "assistant", "content": ai_response, "tool_calls": response_message.tool_calls})
660
- temp_history.append({"role": "tool", "tool_call_id": tool_call.id, "content": tool_result})
661
 
662
  # Set up next prompt to have the model respond to the tool result
663
  current_prompt = f"The tool has been executed with the result shown above. Please respond appropriately to the user based on this result."
 
227
  traceback.print_exc()
228
  return f"Error deleting block: {str(e)}"
229
 
230
+ def create_block(block_spec, under_block_id=None):
231
  """Create a block in the Blockly workspace"""
232
  try:
233
  print(f"[CREATE REQUEST] Attempting to create block: {block_spec}")
234
+ if under_block_id:
235
+ print(f"[CREATE REQUEST] Under block ID: {under_block_id}")
236
 
237
  # Generate a unique request ID
238
  import uuid
 
242
  if request_id in creation_results:
243
  creation_results.pop(request_id)
244
 
245
+ # Add to creation queue with optional under_block_id
246
+ queue_data = {"request_id": request_id, "block_spec": block_spec}
247
+ if under_block_id:
248
+ queue_data["under_block_id"] = under_block_id
249
+ creation_queue.put(queue_data)
250
  print(f"[CREATE REQUEST] Added to queue with ID: {request_id}")
251
 
252
  # Wait for result with timeout
 
490
  {blocks_context}
491
 
492
  YOU CANNOT CREATE A MCP BLOCK OR EDIT ITS INPUTS. YOU MUST TELL THE USER TO DO SO.
493
+
494
+ For creating blocks, keep in mind: for `value`, if you want to put a string, you need to
495
+ put a string block inside of it - you can't just put the string. For example:
496
+
497
+ text_isEmpty(inputs(VALUE: "text"))
498
+
499
+ This wouldn't work - you can't put a raw string in, you need to create a text block
500
+ recursively inside of this. Same thing for numbers and so on.
501
+
502
+ To put a block inside another (like an if inside a repeat), use two create block commands:
503
+ first for the outer block, then for the inner block under it. This only applies to
504
+ non-outputting blocks; outputting blocks (like a number in an addition) can be nested
505
+ in one command.
506
+
507
+ For blocks that allow infinite things (like ...N) you do not need to provide any inputs
508
+ if you want it to be blank.
509
+
510
+ ---
511
+
512
+ Don't share the DSL (DSL being the custom Blockly language) info with the user such
513
+ as when creating a block unless they ask about it. It's not sensitive so you can
514
+ share it with the user if they ask.
515
  """
516
 
517
  tools = [
 
544
  "type": "string",
545
  "description": "The create block command using the custom DSL format.",
546
  },
547
+ "under": {
548
+ "type": "string",
549
+ "description": "The ID of the block that you want to place this under.",
550
+ },
551
  },
552
  "required": ["command"],
553
  },
 
661
 
662
  elif function_name == "create_block":
663
  command = function_args.get("command", "")
664
+ under_block_id = function_args.get("under", None)
665
+ tool_result = create_block(command, under_block_id)
666
  result_label = "Create Operation"
667
 
668
  elif function_name == "run_mcp":
 
689
  # Update history with the tool call and result
690
  temp_history.append({"role": "user", "content": current_prompt})
691
  temp_history.append({"role": "assistant", "content": ai_response, "tool_calls": response_message.tool_calls})
692
+ temp_history.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(tool_result)})
693
 
694
  # Set up next prompt to have the model respond to the tool result
695
  current_prompt = f"The tool has been executed with the result shown above. Please respond appropriately to the user based on this result."
project/src/generators/chat.js CHANGED
@@ -247,7 +247,7 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
247
  // Then get all value inputs (connected blocks)
248
  const inputList = block.inputList || [];
249
  for (const input of inputList) {
250
- if (input.type === Blockly.inputs.inputTypes.VALUE && input.connection) {
251
  const inputName = input.name;
252
  const inputValue = this.valueToCode(block, inputName, this.ORDER_ATOMIC);
253
 
@@ -263,7 +263,7 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
263
  // Handle statement inputs (for blocks that have a body)
264
  let statements = '';
265
  for (const input of inputList) {
266
- if (input.type === Blockly.inputs.inputTypes.STATEMENT && input.connection) {
267
  const statementCode = this.statementToCode(block, input.name);
268
  if (statementCode) {
269
  statements += statementCode;
 
247
  // Then get all value inputs (connected blocks)
248
  const inputList = block.inputList || [];
249
  for (const input of inputList) {
250
+ if (input.type === Blockly.INPUT_VALUE && input.connection) {
251
  const inputName = input.name;
252
  const inputValue = this.valueToCode(block, inputName, this.ORDER_ATOMIC);
253
 
 
263
  // Handle statement inputs (for blocks that have a body)
264
  let statements = '';
265
  for (const input of inputList) {
266
+ if (input.type === Blockly.NEXT_STATEMENT && input.connection) {
267
  const statementCode = this.statementToCode(block, input.name);
268
  if (statementCode) {
269
  statements += statementCode;
project/src/generators/python.js CHANGED
@@ -1,8 +1,21 @@
1
  import { Order } from 'blockly/python';
 
2
 
3
  export const forBlock = Object.create(null);
4
 
5
  forBlock['create_mcp'] = function (block, generator) {
 
 
 
 
 
 
 
 
 
 
 
 
6
  const typedInputs = [];
7
  let i = 0;
8
 
@@ -138,6 +151,18 @@ demo.launch(mcp_server=True)
138
  };
139
 
140
  forBlock['func_def'] = function (block, generator) {
 
 
 
 
 
 
 
 
 
 
 
 
141
  const name = block.getFieldValue('NAME');
142
  const typedInputs = [];
143
  let i = 0;
 
1
  import { Order } from 'blockly/python';
2
+ import * as Blockly from 'blockly';
3
 
4
  export const forBlock = Object.create(null);
5
 
6
  forBlock['create_mcp'] = function (block, generator) {
7
+ // Ensure the generator is properly initialized for nested blocks like loops
8
+ if (!generator.nameDB_) {
9
+ generator.nameDB_ = new Blockly.Names(generator.RESERVED_WORDS_ || []);
10
+ }
11
+
12
+ // Ensure getDistinctName is available for control flow blocks
13
+ if (!generator.getDistinctName) {
14
+ generator.getDistinctName = function(name, type) {
15
+ return this.nameDB_.getDistinctName(name, type);
16
+ };
17
+ }
18
+
19
  const typedInputs = [];
20
  let i = 0;
21
 
 
151
  };
152
 
153
  forBlock['func_def'] = function (block, generator) {
154
+ // Ensure the generator is properly initialized for nested blocks like loops
155
+ if (!generator.nameDB_) {
156
+ generator.nameDB_ = new Blockly.Names(generator.RESERVED_WORDS_ || []);
157
+ }
158
+
159
+ // Ensure getDistinctName is available for control flow blocks
160
+ if (!generator.getDistinctName) {
161
+ generator.getDistinctName = function(name, type) {
162
+ return this.nameDB_.getDistinctName(name, type);
163
+ };
164
+ }
165
+
166
  const name = block.getFieldValue('NAME');
167
  const typedInputs = [];
168
  let i = 0;
project/src/index.js CHANGED
@@ -9,6 +9,37 @@ import '@blockly/toolbox-search';
9
  import DarkTheme from '@blockly/theme-dark';
10
  import './index.css';
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  // Register the blocks and generator with Blockly
13
  Blockly.common.defineBlocks(blocks);
14
  Object.assign(pythonGenerator.forBlock, forBlock);
@@ -364,44 +395,107 @@ const setupCreationStream = () => {
364
  const inputs = parseInputs(inputsContent);
365
  console.log('[SSE CREATE] Parsed inputs:', inputs);
366
 
367
- // Set field values and connect child blocks
368
- for (const [key, value] of Object.entries(inputs)) {
369
- if (typeof value === 'string') {
370
- // Check if this is a nested block specification
371
- if (value.match(/^\w+\s*\(inputs\(/)) {
372
- // This is a nested block, create it recursively
373
- const childBlock = parseAndCreateBlock(value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
 
375
- // Connect the child block to the appropriate input
376
- const input = newBlock.getInput(key);
377
- if (input && input.connection) {
378
- childBlock.outputConnection.connect(input.connection);
379
- }
380
- } else {
381
- // This is a simple value, set it as a field
382
- // Remove quotes if present
383
- const cleanValue = value.replace(/^["']|["']$/g, '');
384
 
385
- // Try to set as a field value
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  try {
387
- newBlock.setFieldValue(cleanValue, key);
388
  } catch (e) {
389
- console.log(`[SSE CREATE] Could not set field ${key} to ${cleanValue}:`, e);
 
 
 
 
 
 
 
390
  }
391
- }
392
- } else if (typeof value === 'number') {
393
- // Set numeric field value
394
- try {
395
- newBlock.setFieldValue(value, key);
396
- } catch (e) {
397
- console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
398
- }
399
- } else if (typeof value === 'boolean') {
400
- // Set boolean field value
401
- try {
402
- newBlock.setFieldValue(value ? 'TRUE' : 'FALSE', key);
403
- } catch (e) {
404
- console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
405
  }
406
  }
407
  }
@@ -534,6 +628,71 @@ const setupCreationStream = () => {
534
 
535
  if (newBlock) {
536
  blockId = newBlock.id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  success = true;
538
  console.log('[SSE CREATE] Successfully created block with children:', blockId, newBlock.type);
539
  } else {
@@ -597,6 +756,10 @@ const observer = new ResizeObserver(() => {
597
  observer.observe(blocklyDiv);
598
 
599
  const updateCode = () => {
 
 
 
 
600
  // Instead of using workspaceToCode which processes ALL blocks,
601
  // manually process only blocks connected to create_mcp or func_def
602
  let code = '';
 
9
  import DarkTheme from '@blockly/theme-dark';
10
  import './index.css';
11
 
12
+ // Initialize the Python generator's Names database if it doesn't exist
13
+ if (!pythonGenerator.nameDB_) {
14
+ pythonGenerator.init = function(workspace) {
15
+ // Call parent init if it exists
16
+ if (Blockly.Generator.prototype.init) {
17
+ Blockly.Generator.prototype.init.call(this, workspace);
18
+ }
19
+
20
+ // Initialize the Names database for variable name generation
21
+ if (!this.nameDB_) {
22
+ this.nameDB_ = new Blockly.Names(this.RESERVED_WORDS_);
23
+ } else {
24
+ this.nameDB_.reset();
25
+ }
26
+
27
+ // Add reserved Python keywords
28
+ const reservedWords = ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
29
+ 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
30
+ 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
31
+ 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
32
+ 'try', 'while', 'with', 'yield'];
33
+
34
+ for (const word of reservedWords) {
35
+ this.nameDB_.setVariableMap(word, word);
36
+ }
37
+
38
+ // Initialize function name database
39
+ this.functionNames_ = this.nameDB_;
40
+ };
41
+ }
42
+
43
  // Register the blocks and generator with Blockly
44
  Blockly.common.defineBlocks(blocks);
45
  Object.assign(pythonGenerator.forBlock, forBlock);
 
395
  const inputs = parseInputs(inputsContent);
396
  console.log('[SSE CREATE] Parsed inputs:', inputs);
397
 
398
+ // Special handling for make_json block
399
+ if (blockType === 'make_json') {
400
+ // Count FIELD entries to determine how many fields we need
401
+ let fieldCount = 0;
402
+ const fieldValues = {};
403
+ const keyValues = {};
404
+
405
+ for (const [key, value] of Object.entries(inputs)) {
406
+ const fieldMatch = key.match(/^FIELD(\d+)$/);
407
+ const keyMatch = key.match(/^KEY(\d+)$/);
408
+
409
+ if (fieldMatch) {
410
+ const index = parseInt(fieldMatch[1]);
411
+ fieldCount = Math.max(fieldCount, index + 1);
412
+ fieldValues[index] = value;
413
+ } else if (keyMatch) {
414
+ const index = parseInt(keyMatch[1]);
415
+ keyValues[index] = value;
416
+ }
417
+ }
418
+
419
+ // Set up the mutator state
420
+ if (fieldCount > 0) {
421
+ newBlock.fieldCount_ = fieldCount;
422
+ newBlock.fieldKeys_ = [];
423
+
424
+ // Create the inputs through the mutator
425
+ for (let i = 0; i < fieldCount; i++) {
426
+ const keyValue = keyValues[i];
427
+ const key = (typeof keyValue === 'string' && !keyValue.match(/^\w+\s*\(inputs\(/))
428
+ ? keyValue.replace(/^["']|["']$/g, '')
429
+ : `key${i}`;
430
 
431
+ newBlock.fieldKeys_[i] = key;
 
 
 
 
 
 
 
 
432
 
433
+ // Create the input
434
+ const input = newBlock.appendValueInput('FIELD' + i);
435
+ const field = new Blockly.FieldTextInput(key);
436
+ field.setValidator((newValue) => {
437
+ newBlock.fieldKeys_[i] = newValue || `key${i}`;
438
+ return newValue;
439
+ });
440
+ input.appendField(field, 'KEY' + i);
441
+ input.appendField(':');
442
+ }
443
+
444
+ // Now connect the field values
445
+ for (let i = 0; i < fieldCount; i++) {
446
+ const value = fieldValues[i];
447
+ if (value && typeof value === 'string' && value.match(/^\w+\s*\(inputs\(/)) {
448
+ // This is a nested block, create it recursively
449
+ const childBlock = parseAndCreateBlock(value);
450
+
451
+ // Connect the child block to the FIELD input
452
+ const input = newBlock.getInput('FIELD' + i);
453
+ if (input && input.connection && childBlock.outputConnection) {
454
+ childBlock.outputConnection.connect(input.connection);
455
+ }
456
+ }
457
+ }
458
+ }
459
+ } else {
460
+ // Normal block handling
461
+ for (const [key, value] of Object.entries(inputs)) {
462
+ if (typeof value === 'string') {
463
+ // Check if this is a nested block specification
464
+ if (value.match(/^\w+\s*\(inputs\(/)) {
465
+ // This is a nested block, create it recursively
466
+ const childBlock = parseAndCreateBlock(value);
467
+
468
+ // Connect the child block to the appropriate input
469
+ const input = newBlock.getInput(key);
470
+ if (input && input.connection && childBlock.outputConnection) {
471
+ childBlock.outputConnection.connect(input.connection);
472
+ }
473
+ } else {
474
+ // This is a simple value, set it as a field
475
+ // Remove quotes if present
476
+ const cleanValue = value.replace(/^["']|["']$/g, '');
477
+
478
+ // Try to set as a field value
479
+ try {
480
+ newBlock.setFieldValue(cleanValue, key);
481
+ } catch (e) {
482
+ console.log(`[SSE CREATE] Could not set field ${key} to ${cleanValue}:`, e);
483
+ }
484
+ }
485
+ } else if (typeof value === 'number') {
486
+ // Set numeric field value
487
  try {
488
+ newBlock.setFieldValue(value, key);
489
  } catch (e) {
490
+ console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
491
+ }
492
+ } else if (typeof value === 'boolean') {
493
+ // Set boolean field value
494
+ try {
495
+ newBlock.setFieldValue(value ? 'TRUE' : 'FALSE', key);
496
+ } catch (e) {
497
+ console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
498
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  }
500
  }
501
  }
 
628
 
629
  if (newBlock) {
630
  blockId = newBlock.id;
631
+
632
+ // If under_block_id is specified, attach the new block under the parent
633
+ if (data.under_block_id) {
634
+ const parentBlock = ws.getBlockById(data.under_block_id);
635
+ if (parentBlock) {
636
+ console.log('[SSE CREATE] Attaching to parent block:', data.under_block_id);
637
+
638
+ // Find an appropriate input to connect to
639
+ // Try common statement inputs first
640
+ const statementInputs = ['BODY', 'DO', 'THEN', 'ELSE', 'STACK'];
641
+ let connected = false;
642
+
643
+ for (const inputName of statementInputs) {
644
+ const input = parentBlock.getInput(inputName);
645
+ if (input && input.type === Blockly.NEXT_STATEMENT) {
646
+ // Check if something is already connected
647
+ if (input.connection && !input.connection.targetBlock()) {
648
+ // Connect directly
649
+ if (newBlock.previousConnection) {
650
+ input.connection.connect(newBlock.previousConnection);
651
+ connected = true;
652
+ console.log('[SSE CREATE] Connected to input:', inputName);
653
+ break;
654
+ }
655
+ } else if (input.connection && input.connection.targetBlock()) {
656
+ // Find the last block in the stack
657
+ let lastBlock = input.connection.targetBlock();
658
+ while (lastBlock.nextConnection && lastBlock.nextConnection.targetBlock()) {
659
+ lastBlock = lastBlock.nextConnection.targetBlock();
660
+ }
661
+ // Connect to the end of the stack
662
+ if (lastBlock.nextConnection && newBlock.previousConnection) {
663
+ lastBlock.nextConnection.connect(newBlock.previousConnection);
664
+ connected = true;
665
+ console.log('[SSE CREATE] Connected to end of stack in input:', inputName);
666
+ break;
667
+ }
668
+ }
669
+ }
670
+ }
671
+
672
+ // If not connected to statement input, try value inputs
673
+ if (!connected) {
674
+ // Try all inputs
675
+ const inputs = parentBlock.inputList;
676
+ for (const input of inputs) {
677
+ if (input.type === Blockly.INPUT_VALUE && input.connection && !input.connection.targetBlock()) {
678
+ if (newBlock.outputConnection) {
679
+ input.connection.connect(newBlock.outputConnection);
680
+ connected = true;
681
+ console.log('[SSE CREATE] Connected to value input:', input.name);
682
+ break;
683
+ }
684
+ }
685
+ }
686
+ }
687
+
688
+ if (!connected) {
689
+ console.warn('[SSE CREATE] Could not find suitable connection point on parent block');
690
+ }
691
+ } else {
692
+ console.warn('[SSE CREATE] Parent block not found:', data.under_block_id);
693
+ }
694
+ }
695
+
696
  success = true;
697
  console.log('[SSE CREATE] Successfully created block with children:', blockId, newBlock.type);
698
  } else {
 
756
  observer.observe(blocklyDiv);
757
 
758
  const updateCode = () => {
759
+ // Initialize the Python generator with the workspace before generating code
760
+ // This ensures the Names database is properly set up for control flow blocks
761
+ pythonGenerator.init(ws);
762
+
763
  // Instead of using workspaceToCode which processes ALL blocks,
764
  // manually process only blocks connected to create_mcp or func_def
765
  let code = '';