owenkaplinsky commited on
Commit
a7bfb03
·
1 Parent(s): c4b5da1
project/blocks.txt CHANGED
@@ -13,7 +13,7 @@ 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))
17
 
18
  # Text
19
  text(inputs(TEXT: 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))
project/chat.py CHANGED
@@ -457,9 +457,8 @@ To call a tool, use this exact format (no newline after the opening backticks):
457
 
458
  ---
459
 
460
- ### Running MCPs
461
- You can execute MCPs directly.
462
- End your message (and say nothing after) with:
463
 
464
  ```run
465
  create_mcp(input_name=value)
@@ -480,6 +479,15 @@ blockId
480
 
481
  You can delete any block except the main `create_mcp` block.
482
 
 
 
 
 
 
 
 
 
 
483
  ---
484
 
485
  ### Creating Blocks
@@ -488,17 +496,16 @@ To create a block, specify its type and parameters (if any).
488
  End your message (and say nothing after) with:
489
 
490
  ```create
491
- block_type(parameters)
 
 
 
 
 
 
492
  ```
493
 
494
- Examples:
495
- - `print_text("Hello World")` - creates a print block with text
496
- - `text("some text")` - creates a text block
497
- - `math_number(42)` - creates a number block
498
- - `logic_boolean(true)` - creates a boolean block
499
- - `mcp_tool("tool_name")` - creates an MCP tool block
500
- - `controls_if()` - creates an if block
501
- - `lists_create_empty()` - creates an empty list block
502
 
503
  List of blocks:
504
 
@@ -506,15 +513,15 @@ List of blocks:
506
 
507
  ---
508
 
 
 
 
 
 
509
  Additionally, if you ever send a message without a tool call, your response will end. So, if you want to
510
  call more tools after something you have to keep calling them. Any pause in tool callings ends the loop.
511
 
512
- REMEMBER, AS YOU SEE BELOW, THE NAME MUST BE DIRECTLY AFTER THE ``` AND CANNOT HAVE A NEW LINE IN BETWEEN
513
- THIS IS A REQUIREMENT. NAME MUST BE ON THE EXACT SAME LINE AS THE BACKTICKS.
514
-
515
- ```name
516
- (arguments_here)
517
- ```
518
  """
519
 
520
  def chat_with_context(message, history):
@@ -569,7 +576,7 @@ THIS IS A REQUIREMENT. NAME MUST BE ON THE EXACT SAME LINE AS THE BACKTICKS.
569
 
570
  try:
571
  response = client.chat.completions.create(
572
- model="gpt-3.5-turbo",
573
  messages=[
574
  {"role": "system", "content": full_system_prompt},
575
  *temp_history,
@@ -582,21 +589,21 @@ THIS IS A REQUIREMENT. NAME MUST BE ON THE EXACT SAME LINE AS THE BACKTICKS.
582
  # Define action patterns and their handlers
583
  action_patterns = {
584
  'run': {
585
- 'pattern': r'```run\n(.+?)\n```',
586
  'label': 'MCP',
587
  'result_label': 'MCP Execution Result',
588
  'handler': lambda content: execute_mcp(content),
589
  'next_prompt': "Please respond to the MCP execution result above and provide any relevant information to the user. If you need to run another MCP, delete, or create blocks, you can do so."
590
  },
591
  'delete': {
592
- 'pattern': r'```delete\n(.+?)\n```',
593
  'label': 'DELETE',
594
  'result_label': 'Delete Operation',
595
  'handler': lambda content: delete_block(content.strip()),
596
  'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP, delete more code, or create blocks, you can do so."
597
  },
598
  'create': {
599
- 'pattern': r'```create\n(.+?)\n```',
600
  'label': 'CREATE',
601
  'result_label': 'Create Operation',
602
  'handler': lambda content: create_block(content.strip()),
 
457
 
458
  ---
459
 
460
+ ### Running MCP
461
+ You can execute the MCP directly:
 
462
 
463
  ```run
464
  create_mcp(input_name=value)
 
479
 
480
  You can delete any block except the main `create_mcp` block.
481
 
482
+ Remember, you give the blockId. Never put the code for it!
483
+ You can see the ID to the left of each block it will be a jarble of characters
484
+ looking something like:
485
+
486
+ `blockId | code`
487
+
488
+ Each block has its own ID, and you need to use the ID specifically from
489
+ the correct block.
490
+
491
  ---
492
 
493
  ### Creating Blocks
 
496
  End your message (and say nothing after) with:
497
 
498
  ```create
499
+ block_name(inputs(value_name: value))
500
+ ```
501
+
502
+ If you want to create a block inside of a block, do it like this:
503
+
504
+ ```create
505
+ block_name(inputs(value_name: block_name2(inputs(value_name2: value))))
506
  ```
507
 
508
+ Where you specify inputs() per block, even if it's inside of another block.
 
 
 
 
 
 
 
509
 
510
  List of blocks:
511
 
 
513
 
514
  ---
515
 
516
+ Remember, this is not the standard tool format. You must follow the one outlined above.
517
+
518
+ Never say that you are going to run a tool and don't run it. You need to put tool calls in the same
519
+ message or your messages will end (see below).
520
+
521
  Additionally, if you ever send a message without a tool call, your response will end. So, if you want to
522
  call more tools after something you have to keep calling them. Any pause in tool callings ends the loop.
523
 
524
+ For deleting blocks, don't forget that you use the **blockId** and **not** the code for it.
 
 
 
 
 
525
  """
526
 
527
  def chat_with_context(message, history):
 
576
 
577
  try:
578
  response = client.chat.completions.create(
579
+ model="gpt-4o-2024-08-06",
580
  messages=[
581
  {"role": "system", "content": full_system_prompt},
582
  *temp_history,
 
589
  # Define action patterns and their handlers
590
  action_patterns = {
591
  'run': {
592
+ 'pattern': r'```(?:\n)?run\n(.+?)\n```',
593
  'label': 'MCP',
594
  'result_label': 'MCP Execution Result',
595
  'handler': lambda content: execute_mcp(content),
596
  'next_prompt': "Please respond to the MCP execution result above and provide any relevant information to the user. If you need to run another MCP, delete, or create blocks, you can do so."
597
  },
598
  'delete': {
599
+ 'pattern': r'```(?:\n)?delete\n(.+?)\n```',
600
  'label': 'DELETE',
601
  'result_label': 'Delete Operation',
602
  'handler': lambda content: delete_block(content.strip()),
603
  'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP, delete more code, or create blocks, you can do so."
604
  },
605
  'create': {
606
+ 'pattern': r'```(?:\n)?create\n(.+?)\n```',
607
  'label': 'CREATE',
608
  'result_label': 'Create Operation',
609
  'handler': lambda content: create_block(content.strip()),
project/src/generators/chat.js CHANGED
@@ -143,6 +143,50 @@ forBlock['input_reference'] = function(block, generator) {
143
  // Register the forBlock definitions with the chat generator
144
  Object.assign(chatGenerator.forBlock, forBlock);
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  // Override blockToCode to provide a catch-all handler
147
  const originalBlockToCode = chatGenerator.blockToCode.bind(chatGenerator);
148
  chatGenerator.blockToCode = function(block, opt_thisOnly) {
@@ -230,9 +274,20 @@ chatGenerator.blockToCode = function(block, opt_thisOnly) {
230
  // Return appropriate format based on whether it's a value or statement block
231
  if (block.outputConnection) {
232
  // This is a value block (can be plugged into inputs)
233
- // For value blocks, don't include the ID in the returned value
234
- const valueCode = `${blockType}(inputs(${inputs.join(', ')}))`;
235
- return [valueCode, this.ORDER_ATOMIC];
 
 
 
 
 
 
 
 
 
 
 
236
  } else {
237
  // This is a statement block (has prev/next connections)
238
  const fullCode = code + (statements ? '\n' + statements : '');
 
143
  // Register the forBlock definitions with the chat generator
144
  Object.assign(chatGenerator.forBlock, forBlock);
145
 
146
+ // Override workspaceToCode to include standalone value blocks
147
+ chatGenerator.workspaceToCode = function(workspace) {
148
+ if (!workspace) {
149
+ // Backwards compatibility from before there could be multiple workspaces.
150
+ console.warn('No workspace specified in workspaceToCode call. Guessing.');
151
+ workspace = Blockly.getMainWorkspace();
152
+ }
153
+ let code = [];
154
+ const blocks = workspace.getTopBlocks(true);
155
+ for (let i = 0; i < blocks.length; i++) {
156
+ const block = blocks[i];
157
+ // Process ALL top-level blocks, including value blocks
158
+ if (block.outputConnection) {
159
+ // This is a value block - check if it's connected
160
+ if (!block.outputConnection.isConnected()) {
161
+ // Standalone value block - get its code
162
+ const line = this.blockToCode(block, true);
163
+ if (Array.isArray(line)) {
164
+ // Value blocks return [code, order], extract just the code
165
+ const blockCode = line[0];
166
+ if (blockCode) {
167
+ code.push(blockCode);
168
+ }
169
+ } else if (line) {
170
+ code.push(line);
171
+ }
172
+ }
173
+ } else {
174
+ // Regular statement block
175
+ const line = this.blockToCode(block);
176
+ if (Array.isArray(line)) {
177
+ // Shouldn't happen for statement blocks, but handle it anyway
178
+ code.push(line[0]);
179
+ } else if (line) {
180
+ code.push(line);
181
+ }
182
+ }
183
+ }
184
+ code = code.join('\n'); // Blank line between each section.
185
+ // Strip trailing whitespace
186
+ code = code.replace(/\n\s*$/g, '\n');
187
+ return code;
188
+ };
189
+
190
  // Override blockToCode to provide a catch-all handler
191
  const originalBlockToCode = chatGenerator.blockToCode.bind(chatGenerator);
192
  chatGenerator.blockToCode = function(block, opt_thisOnly) {
 
274
  // Return appropriate format based on whether it's a value or statement block
275
  if (block.outputConnection) {
276
  // This is a value block (can be plugged into inputs)
277
+ // Check if this block is connected to another block's input
278
+ const isConnectedToInput = block.outputConnection && block.outputConnection.isConnected();
279
+
280
+ if (isConnectedToInput) {
281
+ // When used as input to another block, don't include the ID
282
+ const valueCode = `${blockType}(inputs(${inputs.join(', ')}))`;
283
+ return [valueCode, this.ORDER_ATOMIC];
284
+ } else {
285
+ // When standalone (not connected), include the ID
286
+ const standaloneCode = `${block.id} | ${blockType}(inputs(${inputs.join(', ')}))`;
287
+ // For standalone value blocks, we need to return them as statement-like
288
+ // but still maintain the value block return format for Blockly
289
+ return [standaloneCode, this.ORDER_ATOMIC];
290
+ }
291
  } else {
292
  // This is a statement block (has prev/next connections)
293
  const fullCode = code + (statements ? '\n' + statements : '');
project/src/index.js CHANGED
@@ -330,125 +330,208 @@ const setupCreationStream = () => {
330
  let blockId = null;
331
 
332
  try {
333
- // Parse the block specification
334
- // Expected format: "block_type(param1, param2, ...)"
335
- const match = data.block_spec.match(/^(\w+)\s*\((.*)\)$/);
336
-
337
- if (!match) {
338
- throw new Error(`Invalid block specification format: ${data.block_spec}`);
339
- }
340
-
341
- const blockType = match[1];
342
- const paramsStr = match[2].trim();
343
-
344
- // Create the block based on its type
345
- let newBlock = null;
346
-
347
- switch (blockType) {
348
- case 'print_text':
349
- newBlock = ws.newBlock('print_text');
350
- // Set the text value if provided
351
- if (paramsStr) {
352
- // Remove quotes from string parameters
353
- const text = paramsStr.replace(/^["']|["']$/g, '');
354
- newBlock.setFieldValue(text, 'TEXT');
355
- }
356
- break;
 
 
 
357
 
358
- case 'mcp_tool':
359
- newBlock = ws.newBlock('mcp_tool');
360
- // Parse tool name if provided
361
- if (paramsStr) {
362
- const toolName = paramsStr.replace(/^["']|["']$/g, '');
363
- newBlock.setFieldValue(toolName, 'TOOL');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  }
365
- break;
 
 
 
 
 
 
 
 
 
 
366
 
367
- case 'text':
368
- newBlock = ws.newBlock('text');
369
- if (paramsStr) {
370
- const text = paramsStr.replace(/^["']|["']$/g, '');
371
- newBlock.setFieldValue(text, 'TEXT');
372
  }
373
- break;
374
 
375
- case 'math_number':
376
- newBlock = ws.newBlock('math_number');
377
- if (paramsStr) {
378
- const num = parseFloat(paramsStr);
379
- if (!isNaN(num)) {
380
- newBlock.setFieldValue(num, 'NUM');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  }
382
  }
383
- break;
384
 
385
- case 'logic_boolean':
386
- newBlock = ws.newBlock('logic_boolean');
387
- if (paramsStr) {
388
- const bool = paramsStr.toLowerCase() === 'true' ? 'TRUE' : 'FALSE';
389
- newBlock.setFieldValue(bool, 'BOOL');
390
  }
391
- break;
392
-
393
- case 'logic_compare':
394
- newBlock = ws.newBlock('logic_compare');
395
- // Could parse operator if provided
396
- break;
397
-
398
- case 'logic_operation':
399
- newBlock = ws.newBlock('logic_operation');
400
- // Could parse operator if provided
401
- break;
402
 
403
- case 'controls_if':
404
- newBlock = ws.newBlock('controls_if');
405
- break;
406
-
407
- case 'controls_whileUntil':
408
- newBlock = ws.newBlock('controls_whileUntil');
409
- break;
410
 
411
- case 'lists_create_with':
412
- newBlock = ws.newBlock('lists_create_with');
413
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
- case 'lists_create_empty':
416
- newBlock = ws.newBlock('lists_create_empty');
417
- break;
 
 
 
 
 
 
 
 
 
418
 
419
- default:
420
- // Try to create as a generic block type
421
- newBlock = ws.newBlock(blockType);
422
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  }
424
 
 
 
 
425
  if (newBlock) {
426
- // Initialize the block (renders it)
427
- newBlock.initSvg();
428
-
429
- // Position it in a visible area
430
- // Find a good position that doesn't overlap existing blocks
431
- const existingBlocks = ws.getAllBlocks();
432
- let x = 50;
433
- let y = 50;
434
-
435
- // Simple positioning: stack new blocks vertically
436
- if (existingBlocks.length > 0) {
437
- const lastBlock = existingBlocks[existingBlocks.length - 1];
438
- const lastPos = lastBlock.getRelativeToSurfaceXY();
439
- y = lastPos.y + lastBlock.height + 20;
440
- }
441
-
442
- newBlock.moveBy(x, y);
443
-
444
- // Render the block
445
- newBlock.render();
446
-
447
  blockId = newBlock.id;
448
  success = true;
449
- console.log('[SSE CREATE] Successfully created block:', blockId, blockType);
450
  } else {
451
- throw new Error(`Failed to create block of type: ${blockType}`);
452
  }
453
 
454
  } catch (e) {
@@ -570,6 +653,78 @@ const updateCode = () => {
570
  });
571
  };
572
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
  // Update function for the Chat generator (AI Chat tab)
574
  const updateChatCode = () => {
575
  let code = chatGenerator.workspaceToCode(ws);
@@ -582,16 +737,26 @@ const updateChatCode = () => {
582
  codeEl.textContent = code;
583
  }
584
 
585
- // Send to the chat update endpoint
586
- fetch("http://127.0.0.1:7861/update_chat", {
587
- method: "POST",
588
- headers: { "Content-Type": "application/json" },
589
- body: JSON.stringify({ code }),
590
- }).then(() => {
591
- console.log("[Blockly] Sent updated Chat code to backend");
592
- }).catch((err) => {
593
- console.error("[Blockly] Error sending Chat code:", err);
594
- });
 
 
 
 
 
 
 
 
 
 
595
  };
596
 
597
  try {
@@ -657,7 +822,18 @@ if (existingMcpBlocks.length === 0) {
657
  }
658
 
659
  updateCode();
660
- updateChatCode();
 
 
 
 
 
 
 
 
 
 
 
661
 
662
  ws.addChangeListener((e) => {
663
  if (e.isUiEvent) return;
 
330
  let blockId = null;
331
 
332
  try {
333
+ // Parse and create blocks recursively
334
+ function parseAndCreateBlock(spec, shouldPosition = false) {
335
+ // Match block_name(inputs(...))
336
+ const blockMatch = spec.match(/^(\w+)\s*\((.+)\)$/s);
337
+
338
+ if (!blockMatch) {
339
+ throw new Error(`Invalid block specification format: ${spec}`);
340
+ }
341
+
342
+ const blockType = blockMatch[1];
343
+ const content = blockMatch[2].trim();
344
+
345
+ console.log('[SSE CREATE] Parsing block:', blockType, 'with content:', content);
346
+
347
+ // Check if this has inputs() wrapper
348
+ let inputsContent = content;
349
+ if (content.startsWith('inputs(') && content.endsWith(')')) {
350
+ inputsContent = content.slice(7, -1); // Remove 'inputs(' and ')'
351
+ }
352
+
353
+ // Create the block
354
+ const newBlock = ws.newBlock(blockType);
355
+
356
+ if (inputsContent) {
357
+ // Parse the inputs content
358
+ const inputs = parseInputs(inputsContent);
359
+ console.log('[SSE CREATE] Parsed inputs:', inputs);
360
 
361
+ // Set field values and connect child blocks
362
+ for (const [key, value] of Object.entries(inputs)) {
363
+ if (typeof value === 'string') {
364
+ // Check if this is a nested block specification
365
+ if (value.match(/^\w+\s*\(inputs\(/)) {
366
+ // This is a nested block, create it recursively
367
+ const childBlock = parseAndCreateBlock(value);
368
+
369
+ // Connect the child block to the appropriate input
370
+ const input = newBlock.getInput(key);
371
+ if (input && input.connection) {
372
+ childBlock.outputConnection.connect(input.connection);
373
+ }
374
+ } else {
375
+ // This is a simple value, set it as a field
376
+ // Remove quotes if present
377
+ const cleanValue = value.replace(/^["']|["']$/g, '');
378
+
379
+ // Try to set as a field value
380
+ try {
381
+ newBlock.setFieldValue(cleanValue, key);
382
+ } catch (e) {
383
+ console.log(`[SSE CREATE] Could not set field ${key} to ${cleanValue}:`, e);
384
+ }
385
+ }
386
+ } else if (typeof value === 'number') {
387
+ // Set numeric field value
388
+ try {
389
+ newBlock.setFieldValue(value, key);
390
+ } catch (e) {
391
+ console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
392
+ }
393
+ } else if (typeof value === 'boolean') {
394
+ // Set boolean field value
395
+ try {
396
+ newBlock.setFieldValue(value ? 'TRUE' : 'FALSE', key);
397
+ } catch (e) {
398
+ console.log(`[SSE CREATE] Could not set field ${key} to ${value}:`, e);
399
+ }
400
+ }
401
  }
402
+ }
403
+
404
+ // Initialize the block (renders it)
405
+ newBlock.initSvg();
406
+
407
+ // Only position the top-level block
408
+ if (shouldPosition) {
409
+ // Find a good position that doesn't overlap existing blocks
410
+ const existingBlocks = ws.getAllBlocks();
411
+ let x = 50;
412
+ let y = 50;
413
 
414
+ // Simple positioning: stack new blocks vertically
415
+ if (existingBlocks.length > 0) {
416
+ const lastBlock = existingBlocks[existingBlocks.length - 1];
417
+ const lastPos = lastBlock.getRelativeToSurfaceXY();
418
+ y = lastPos.y + lastBlock.height + 20;
419
  }
 
420
 
421
+ newBlock.moveBy(x, y);
422
+ }
423
+
424
+ // Render the block
425
+ newBlock.render();
426
+
427
+ return newBlock;
428
+ }
429
+
430
+ // Helper function to parse inputs(key: value, key2: value2, ...)
431
+ function parseInputs(inputStr) {
432
+ const result = {};
433
+ let currentKey = '';
434
+ let currentValue = '';
435
+ let depth = 0;
436
+ let inQuotes = false;
437
+ let quoteChar = '';
438
+ let readingKey = true;
439
+
440
+ for (let i = 0; i < inputStr.length; i++) {
441
+ const char = inputStr[i];
442
+
443
+ // Handle quotes
444
+ if ((char === '"' || char === "'") && (i === 0 || inputStr[i-1] !== '\\')) {
445
+ if (!inQuotes) {
446
+ inQuotes = true;
447
+ quoteChar = char;
448
+ } else if (char === quoteChar) {
449
+ inQuotes = false;
450
+ quoteChar = '';
451
  }
452
  }
 
453
 
454
+ // Handle parentheses depth (for nested blocks)
455
+ if (!inQuotes) {
456
+ if (char === '(') depth++;
457
+ else if (char === ')') depth--;
 
458
  }
 
 
 
 
 
 
 
 
 
 
 
459
 
460
+ // Handle key-value separation
461
+ if (char === ':' && depth === 0 && !inQuotes && readingKey) {
462
+ readingKey = false;
463
+ currentKey = currentKey.trim();
464
+ continue;
465
+ }
 
466
 
467
+ // Handle comma separation
468
+ if (char === ',' && depth === 0 && !inQuotes && !readingKey) {
469
+ // Store the key-value pair
470
+ currentValue = currentValue.trim();
471
+
472
+ // Parse the value
473
+ if (currentValue.match(/^\w+\s*\(inputs\(/)) {
474
+ // This is a nested block
475
+ result[currentKey] = currentValue;
476
+ } else if (currentValue.match(/^-?\d+(\.\d+)?$/)) {
477
+ // This is a number
478
+ result[currentKey] = parseFloat(currentValue);
479
+ } else if (currentValue === 'true' || currentValue === 'false') {
480
+ // This is a boolean
481
+ result[currentKey] = currentValue === 'true';
482
+ } else {
483
+ // This is a string (remove quotes if present)
484
+ result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
485
+ }
486
+
487
+ // Reset for next key-value pair
488
+ currentKey = '';
489
+ currentValue = '';
490
+ readingKey = true;
491
+ continue;
492
+ }
493
 
494
+ // Accumulate characters
495
+ if (readingKey) {
496
+ currentKey += char;
497
+ } else {
498
+ currentValue += char;
499
+ }
500
+ }
501
+
502
+ // Handle the last key-value pair
503
+ if (currentKey && currentValue) {
504
+ currentKey = currentKey.trim();
505
+ currentValue = currentValue.trim();
506
 
507
+ // Parse the value
508
+ if (currentValue.match(/^\w+\s*\(inputs\(/)) {
509
+ // This is a nested block
510
+ result[currentKey] = currentValue;
511
+ } else if (currentValue.match(/^-?\d+(\.\d+)?$/)) {
512
+ // This is a number
513
+ result[currentKey] = parseFloat(currentValue);
514
+ } else if (currentValue === 'true' || currentValue === 'false') {
515
+ // This is a boolean
516
+ result[currentKey] = currentValue === 'true';
517
+ } else {
518
+ // This is a string (remove quotes if present)
519
+ result[currentKey] = currentValue.replace(/^["']|["']$/g, '');
520
+ }
521
+ }
522
+
523
+ return result;
524
  }
525
 
526
+ // Create the block and all its nested children
527
+ const newBlock = parseAndCreateBlock(data.block_spec, true);
528
+
529
  if (newBlock) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  blockId = newBlock.id;
531
  success = true;
532
+ console.log('[SSE CREATE] Successfully created block with children:', blockId, newBlock.type);
533
  } else {
534
+ throw new Error(`Failed to create block from specification`);
535
  }
536
 
537
  } catch (e) {
 
653
  });
654
  };
655
 
656
+ // Track if chat backend is available
657
+ let chatBackendAvailable = false;
658
+ let chatUpdateQueue = [];
659
+ let chatRetryTimeout = null;
660
+
661
+ // Function to check if chat backend is available
662
+ const checkChatBackend = async () => {
663
+ try {
664
+ const response = await fetch("http://127.0.0.1:7861/update_chat", {
665
+ method: "POST",
666
+ headers: { "Content-Type": "application/json" },
667
+ body: JSON.stringify({ code: "" }),
668
+ });
669
+ if (response.ok) {
670
+ chatBackendAvailable = true;
671
+ console.log("[Blockly] Chat backend is available");
672
+ // Process any queued updates
673
+ processChatUpdateQueue();
674
+ return true;
675
+ }
676
+ } catch (err) {
677
+ chatBackendAvailable = false;
678
+ }
679
+ return false;
680
+ };
681
+
682
+ // Process queued chat updates
683
+ const processChatUpdateQueue = () => {
684
+ if (chatBackendAvailable && chatUpdateQueue.length > 0) {
685
+ const code = chatUpdateQueue.pop(); // Get the latest update
686
+ chatUpdateQueue = []; // Clear the queue
687
+ sendChatUpdate(code);
688
+ }
689
+ };
690
+
691
+ // Send chat update with retry logic
692
+ const sendChatUpdate = async (code, retryCount = 0) => {
693
+ try {
694
+ const response = await fetch("http://127.0.0.1:7861/update_chat", {
695
+ method: "POST",
696
+ headers: { "Content-Type": "application/json" },
697
+ body: JSON.stringify({ code }),
698
+ });
699
+
700
+ if (response.ok) {
701
+ chatBackendAvailable = true;
702
+ console.log("[Blockly] Sent updated Chat code to backend");
703
+ } else {
704
+ throw new Error(`Server responded with status ${response.status}`);
705
+ }
706
+ } catch (err) {
707
+ console.warn(`[Blockly] Chat backend not ready (attempt ${retryCount + 1}):`, err.message);
708
+ chatBackendAvailable = false;
709
+
710
+ // Queue this update for retry
711
+ if (retryCount < 5) {
712
+ const delay = Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff, max 10s
713
+ setTimeout(() => {
714
+ if (!chatBackendAvailable) {
715
+ checkChatBackend().then(available => {
716
+ if (available) {
717
+ sendChatUpdate(code, retryCount + 1);
718
+ } else if (retryCount < 4) {
719
+ sendChatUpdate(code, retryCount + 1);
720
+ }
721
+ });
722
+ }
723
+ }, delay);
724
+ }
725
+ }
726
+ };
727
+
728
  // Update function for the Chat generator (AI Chat tab)
729
  const updateChatCode = () => {
730
  let code = chatGenerator.workspaceToCode(ws);
 
737
  codeEl.textContent = code;
738
  }
739
 
740
+ // If backend is available, send immediately
741
+ if (chatBackendAvailable) {
742
+ sendChatUpdate(code);
743
+ } else {
744
+ // Queue the update and try to establish connection
745
+ chatUpdateQueue.push(code);
746
+
747
+ // Clear any existing retry timeout
748
+ if (chatRetryTimeout) {
749
+ clearTimeout(chatRetryTimeout);
750
+ }
751
+
752
+ // Try to connect to backend
753
+ checkChatBackend();
754
+
755
+ // Set up periodic retry
756
+ chatRetryTimeout = setTimeout(() => {
757
+ checkChatBackend();
758
+ }, 2000);
759
+ }
760
  };
761
 
762
  try {
 
822
  }
823
 
824
  updateCode();
825
+
826
+ // Check if chat backend is available before first update
827
+ checkChatBackend().then(() => {
828
+ updateChatCode();
829
+ });
830
+
831
+ // Also set up periodic health checks for the chat backend
832
+ setInterval(() => {
833
+ if (!chatBackendAvailable) {
834
+ checkChatBackend();
835
+ }
836
+ }, 5000); // Check every 5 seconds if not connected
837
 
838
  ws.addChangeListener((e) => {
839
  if (e.isUiEvent) return;