owenkaplinsky commited on
Commit
c4b5da1
·
1 Parent(s): 024cccc

Add AI block creation

Browse files
project/blocks.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI
2
+ llm_call(inputs(MODEL: "gpt-3.5-turbo-0125/gpt-4o-2024-08-06/gpt-5-mini-2025-08-07/gpt-5-2025-08-07/gpt-4o-search-preview-2025-03-11", PROMPT: 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
+
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))
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))
project/chat.py CHANGED
@@ -24,6 +24,19 @@ latest_blockly_chat_code = ""
24
  deletion_queue = queue.Queue()
25
  deletion_results = {}
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  # FastAPI App
28
  app = FastAPI()
29
 
@@ -200,9 +213,9 @@ def delete_block(block_id):
200
  result = deletion_results.pop(block_id)
201
  print(f"[DELETE RESULT] Received result for {block_id}: success={result.get('success')}, error={result.get('error')}")
202
  if result["success"]:
203
- return f"Successfully deleted block {block_id}"
204
  else:
205
- return f"Failed to delete block {block_id}: {result.get('error', 'Unknown error')}"
206
  time.sleep(check_interval)
207
 
208
  print(f"[DELETE TIMEOUT] No response received for block {block_id} after {timeout} seconds")
@@ -214,6 +227,124 @@ def delete_block(block_id):
214
  traceback.print_exc()
215
  return f"Error deleting block: {str(e)}"
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  # Server-Sent Events endpoint for deletion requests
218
  @app.get("/delete_stream")
219
  async def delete_stream():
@@ -291,7 +422,7 @@ async def deletion_result(request: Request):
291
 
292
  def create_gradio_interface():
293
  # Hardcoded system prompt
294
- SYSTEM_PROMPT = """You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
295
  MCP lets AI systems define tools with specific inputs and outputs that any LLM can call.
296
 
297
  You’ll receive the workspace state in this format:
@@ -347,7 +478,44 @@ To delete one, end your message with:
347
  blockId
348
  ```
349
 
350
- You can delete any block except the main `create_mcp` block."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
  def chat_with_context(message, history):
353
  # Check if API key is set and create/update client
@@ -360,10 +528,12 @@ You can delete any block except the main `create_mcp` block."""
360
  try:
361
  client = OpenAI(api_key=api_key)
362
  except Exception as e:
363
- return f"Error initializing OpenAI client: {str(e)}"
 
364
 
365
  if not client or not api_key:
366
- return "OpenAI API key not configured. Please set it in File > Settings in the Blockly interface."
 
367
 
368
  # Get the chat context from the global variable
369
  global latest_blockly_chat_code
@@ -416,14 +586,21 @@ You can delete any block except the main `create_mcp` block."""
416
  'label': 'MCP',
417
  'result_label': 'MCP Execution Result',
418
  'handler': lambda content: execute_mcp(content),
419
- '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 or delete code, you can do so."
420
  },
421
  'delete': {
422
  'pattern': r'```delete\n(.+?)\n```',
423
  'label': 'DELETE',
424
  'result_label': 'Delete Operation',
425
  'handler': lambda content: delete_block(content.strip()),
426
- 'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP or delete more code, you can do so."
 
 
 
 
 
 
 
427
  }
428
  }
429
 
@@ -440,17 +617,23 @@ You can delete any block except the main `create_mcp` block."""
440
 
441
  print(f"[{config['label']} DETECTED] Processing: {action_content}")
442
 
 
 
 
 
 
 
 
443
  # Execute the action
444
  action_result = config['handler'](action_content)
445
 
446
  print(f"[{config['label']} RESULT] {action_result}")
447
 
448
- # Add to accumulated response
449
  if accumulated_response:
450
  accumulated_response += "\n\n"
451
- if displayed_response:
452
- accumulated_response += displayed_response + "\n\n"
453
  accumulated_response += f"**{config['result_label']}:** {action_result}"
 
454
 
455
  # Update history for next iteration
456
  temp_history.append({"role": "user", "content": current_prompt})
@@ -468,19 +651,20 @@ You can delete any block except the main `create_mcp` block."""
468
  if accumulated_response:
469
  accumulated_response += "\n\n"
470
  accumulated_response += ai_response
 
471
  break
472
 
473
  except Exception as e:
474
  if accumulated_response:
475
- return f"{accumulated_response}\n\nError in iteration {current_iteration}: {str(e)}"
476
  else:
477
- return f"Error: {str(e)}"
 
478
 
479
  # If we hit max iterations, add a note
480
  if current_iteration >= max_iterations:
481
  accumulated_response += f"\n\n*(Reached maximum of {max_iterations} consecutive responses)*"
482
-
483
- return accumulated_response if accumulated_response else "No response generated"
484
 
485
  # Create the standard ChatInterface
486
  demo = gr.ChatInterface(
 
24
  deletion_queue = queue.Queue()
25
  deletion_results = {}
26
 
27
+ # Queue for creation requests and results storage
28
+ creation_queue = queue.Queue()
29
+ creation_results = {}
30
+
31
+ blocks_context = ""
32
+ try:
33
+ file_path = os.path.join(os.path.dirname(__file__), "blocks.txt")
34
+ with open(file_path, "r", encoding="utf-8") as f:
35
+ blocks_context = f.read().strip()
36
+ except Exception as e:
37
+ print(f"[WARN] Could not read blocks.txt: {e}")
38
+ blocks_context = "(No external block data available.)"
39
+
40
  # FastAPI App
41
  app = FastAPI()
42
 
 
213
  result = deletion_results.pop(block_id)
214
  print(f"[DELETE RESULT] Received result for {block_id}: success={result.get('success')}, error={result.get('error')}")
215
  if result["success"]:
216
+ return f"[TOOL] Successfully deleted block {block_id}"
217
  else:
218
+ return f"[TOOL] Failed to delete block {block_id}: {result.get('error', 'Unknown error')}"
219
  time.sleep(check_interval)
220
 
221
  print(f"[DELETE TIMEOUT] No response received for block {block_id} after {timeout} seconds")
 
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
237
+ request_id = str(uuid.uuid4())
238
+
239
+ # Clear any old results for this request ID first
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
248
+ import time
249
+ timeout = 8 # 8 seconds timeout
250
+ start_time = time.time()
251
+ check_interval = 0.05 # Check more frequently
252
+
253
+ while time.time() - start_time < timeout:
254
+ if request_id in creation_results:
255
+ result = creation_results.pop(request_id)
256
+ print(f"[CREATE RESULT] Received result for {request_id}: success={result.get('success')}, error={result.get('error')}")
257
+ if result["success"]:
258
+ return f"[TOOL] Successfully created block: {result.get('block_id', 'unknown')}"
259
+ else:
260
+ return f"[TOOL] Failed to create block: {result.get('error', 'Unknown error')}"
261
+ time.sleep(check_interval)
262
+
263
+ print(f"[CREATE TIMEOUT] No response received for request {request_id} after {timeout} seconds")
264
+ return f"Timeout waiting for block creation confirmation"
265
+
266
+ except Exception as e:
267
+ print(f"[CREATE ERROR] {e}")
268
+ import traceback
269
+ traceback.print_exc()
270
+ return f"Error creating block: {str(e)}"
271
+
272
+ # Server-Sent Events endpoint for creation requests
273
+ @app.get("/create_stream")
274
+ async def create_stream():
275
+ """Stream creation requests to the frontend using Server-Sent Events"""
276
+
277
+ async def clear_sent_request(sent_requests, request_id, delay):
278
+ """Clear request_id from sent_requests after delay seconds"""
279
+ await asyncio.sleep(delay)
280
+ if request_id in sent_requests:
281
+ sent_requests.discard(request_id)
282
+
283
+ async def event_generator():
284
+ sent_requests = set() # Track sent requests to avoid duplicates
285
+ heartbeat_counter = 0
286
+
287
+ while True:
288
+ try:
289
+ # Check for creation requests (non-blocking)
290
+ if not creation_queue.empty():
291
+ creation_request = creation_queue.get_nowait()
292
+ request_id = creation_request.get("request_id")
293
+
294
+ # Avoid sending duplicate requests too quickly
295
+ if request_id not in sent_requests:
296
+ sent_requests.add(request_id)
297
+ print(f"[SSE CREATE SEND] Sending creation request with ID: {request_id}")
298
+ yield f"data: {json.dumps(creation_request)}\n\n"
299
+
300
+ # Clear from sent_requests after 10 seconds
301
+ asyncio.create_task(clear_sent_request(sent_requests, request_id, 10))
302
+ else:
303
+ print(f"[SSE CREATE SKIP] Skipping duplicate request for ID: {request_id}")
304
+
305
+ await asyncio.sleep(0.1) # Small delay between messages
306
+ else:
307
+ # Send a heartbeat every 30 seconds to keep connection alive
308
+ heartbeat_counter += 1
309
+ if heartbeat_counter >= 300: # 300 * 0.1 = 30 seconds
310
+ yield f"data: {json.dumps({'heartbeat': True})}\n\n"
311
+ heartbeat_counter = 0
312
+ await asyncio.sleep(0.1)
313
+ except queue.Empty:
314
+ await asyncio.sleep(0.1)
315
+ except Exception as e:
316
+ print(f"[SSE CREATE ERROR] {e}")
317
+ await asyncio.sleep(1)
318
+
319
+ return StreamingResponse(
320
+ event_generator(),
321
+ media_type="text/event-stream",
322
+ headers={
323
+ "Cache-Control": "no-cache",
324
+ "Connection": "keep-alive",
325
+ "X-Accel-Buffering": "no",
326
+ }
327
+ )
328
+
329
+ # Endpoint to receive creation results from frontend
330
+ @app.post("/creation_result")
331
+ async def creation_result(request: Request):
332
+ """Receive creation results from the frontend"""
333
+ data = await request.json()
334
+ request_id = data.get("request_id")
335
+ success = data.get("success")
336
+ error = data.get("error")
337
+ block_id = data.get("block_id")
338
+
339
+ print(f"[CREATION RESULT RECEIVED] request_id={request_id}, success={success}, error={error}, block_id={block_id}")
340
+
341
+ if request_id:
342
+ # Store the result for the create_block function to retrieve
343
+ creation_results[request_id] = data
344
+ print(f"[CREATION RESULT STORED] Results dict now has {len(creation_results)} items")
345
+
346
+ return {"received": True}
347
+
348
  # Server-Sent Events endpoint for deletion requests
349
  @app.get("/delete_stream")
350
  async def delete_stream():
 
422
 
423
  def create_gradio_interface():
424
  # Hardcoded system prompt
425
+ SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
426
  MCP lets AI systems define tools with specific inputs and outputs that any LLM can call.
427
 
428
  You’ll receive the workspace state in this format:
 
478
  blockId
479
  ```
480
 
481
+ You can delete any block except the main `create_mcp` block.
482
+
483
+ ---
484
+
485
+ ### Creating Blocks
486
+ You can create new blocks in the workspace.
487
+ 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
+
505
+ {blocks_context}
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):
521
  # Check if API key is set and create/update client
 
528
  try:
529
  client = OpenAI(api_key=api_key)
530
  except Exception as e:
531
+ yield f"Error initializing OpenAI client: {str(e)}"
532
+ return
533
 
534
  if not client or not api_key:
535
+ yield "OpenAI API key not configured. Please set it in File > Settings in the Blockly interface."
536
+ return
537
 
538
  # Get the chat context from the global variable
539
  global latest_blockly_chat_code
 
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()),
603
+ 'next_prompt': "Please respond to the create operation result above. If you need to run an MCP, delete code, or create more blocks, you can do so."
604
  }
605
  }
606
 
 
617
 
618
  print(f"[{config['label']} DETECTED] Processing: {action_content}")
619
 
620
+ # Yield the displayed response first if it exists
621
+ if displayed_response:
622
+ if accumulated_response:
623
+ accumulated_response += "\n\n"
624
+ accumulated_response += displayed_response
625
+ yield accumulated_response
626
+
627
  # Execute the action
628
  action_result = config['handler'](action_content)
629
 
630
  print(f"[{config['label']} RESULT] {action_result}")
631
 
632
+ # Yield the action result
633
  if accumulated_response:
634
  accumulated_response += "\n\n"
 
 
635
  accumulated_response += f"**{config['result_label']}:** {action_result}"
636
+ yield accumulated_response
637
 
638
  # Update history for next iteration
639
  temp_history.append({"role": "user", "content": current_prompt})
 
651
  if accumulated_response:
652
  accumulated_response += "\n\n"
653
  accumulated_response += ai_response
654
+ yield accumulated_response
655
  break
656
 
657
  except Exception as e:
658
  if accumulated_response:
659
+ yield f"{accumulated_response}\n\nError in iteration {current_iteration}: {str(e)}"
660
  else:
661
+ yield f"Error: {str(e)}"
662
+ return
663
 
664
  # If we hit max iterations, add a note
665
  if current_iteration >= max_iterations:
666
  accumulated_response += f"\n\n*(Reached maximum of {max_iterations} consecutive responses)*"
667
+ yield accumulated_response
 
668
 
669
  # Create the standard ChatInterface
670
  demo = gr.ChatInterface(
project/src/index.js CHANGED
@@ -299,6 +299,208 @@ const setupDeletionStream = () => {
299
  // Start the SSE connection
300
  setupDeletionStream();
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  // Observe any size change to the blockly container
303
  const observer = new ResizeObserver(() => {
304
  Blockly.svgResize(ws);
 
299
  // Start the SSE connection
300
  setupDeletionStream();
301
 
302
+ // Set up SSE connection for creation requests
303
+ const setupCreationStream = () => {
304
+ const eventSource = new EventSource('http://127.0.0.1:7861/create_stream');
305
+ const processedRequests = new Set(); // Track processed creation requests
306
+
307
+ eventSource.onmessage = (event) => {
308
+ try {
309
+ const data = JSON.parse(event.data);
310
+
311
+ // Skip heartbeat messages
312
+ if (data.heartbeat) return;
313
+
314
+ // Skip if we've already processed this request
315
+ if (data.request_id && processedRequests.has(data.request_id)) {
316
+ console.log('[SSE CREATE] Skipping duplicate creation request:', data.request_id);
317
+ return;
318
+ }
319
+ if (data.request_id) {
320
+ processedRequests.add(data.request_id);
321
+ // Clear after 10 seconds to allow retries if needed
322
+ setTimeout(() => processedRequests.delete(data.request_id), 10000);
323
+ }
324
+
325
+ if (data.block_spec && data.request_id) {
326
+ console.log('[SSE CREATE] Received creation request:', data.request_id, data.block_spec);
327
+
328
+ let success = false;
329
+ let error = null;
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) {
455
+ error = e.toString();
456
+ console.error('[SSE CREATE] Error creating block:', e);
457
+ }
458
+
459
+ // Send result back to backend immediately
460
+ console.log('[SSE CREATE] Sending creation result:', {
461
+ request_id: data.request_id,
462
+ success,
463
+ error,
464
+ block_id: blockId
465
+ });
466
+
467
+ fetch('http://127.0.0.1:7861/creation_result', {
468
+ method: 'POST',
469
+ headers: { 'Content-Type': 'application/json' },
470
+ body: JSON.stringify({
471
+ request_id: data.request_id,
472
+ success: success,
473
+ error: error,
474
+ block_id: blockId
475
+ })
476
+ }).then(response => {
477
+ console.log('[SSE CREATE] Creation result sent successfully');
478
+ }).catch(err => {
479
+ console.error('[SSE CREATE] Error sending creation result:', err);
480
+ });
481
+ }
482
+ } catch (err) {
483
+ console.error('[SSE CREATE] Error processing message:', err);
484
+ }
485
+ };
486
+
487
+ eventSource.onerror = (error) => {
488
+ console.error('[SSE CREATE] Connection error:', error);
489
+ // Reconnect after 5 seconds
490
+ setTimeout(() => {
491
+ console.log('[SSE CREATE] Attempting to reconnect...');
492
+ setupCreationStream();
493
+ }, 5000);
494
+ };
495
+
496
+ eventSource.onopen = () => {
497
+ console.log('[SSE CREATE] Connected to creation stream');
498
+ };
499
+ };
500
+
501
+ // Start the creation SSE connection
502
+ setupCreationStream();
503
+
504
  // Observe any size change to the blockly container
505
  const observer = new ResizeObserver(() => {
506
  Blockly.svgResize(ws);
project/src/toolbox.js CHANGED
@@ -96,10 +96,6 @@ export const toolbox = {
96
  kind: 'block',
97
  type: 'logic_null',
98
  },
99
- {
100
- kind: 'block',
101
- type: 'logic_ternary',
102
- },
103
  ],
104
  },
105
  {
 
96
  kind: 'block',
97
  type: 'logic_null',
98
  },
 
 
 
 
99
  ],
100
  },
101
  {