prthm11 commited on
Commit
0ccd664
·
verified ·
1 Parent(s): 269af14

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -98
app.py CHANGED
@@ -22,7 +22,7 @@ from langchain_core.utils.utils import secret_from_env
22
  from io import BytesIO
23
  from pathlib import Path
24
  import os
25
- from utils.block_relation_builder import block_builder, variable_adder_main
26
  from langchain.chat_models import ChatOpenAI
27
  from langchain_openai import ChatOpenAI
28
  from pydantic import Field, SecretStr
@@ -341,6 +341,174 @@ def find_block_in_all(opcode: str, all_catalogs: list[dict]) -> dict | None:
341
  return blk
342
  return None
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
  # --- Global variable for the block catalog ---
346
  ALL_SCRATCH_BLOCKS_CATALOG = {}
@@ -779,7 +947,7 @@ If you find any "Code-Blocks" then,
779
  print(f"[OVREALL REFINED PSEUDO CODE LOGIC]: {result}")
780
  logger.info("Plan refinement and block relation analysis completed for all plans.")
781
  return state
782
-
783
  def overall_planner_node(state: GameState):
784
  """
785
  Generates a comprehensive action plan for sprites, including detailed Scratch block information.
@@ -1087,7 +1255,7 @@ Each plan must include a **single Scratch Hat Block** (e.g., 'event_whenflagclic
1087
  except Exception as e:
1088
  logger.error(f"Error in OverallPlannerNode: {e}")
1089
  raise
1090
-
1091
  def refined_planner_node(state: GameState):
1092
  """
1093
  Refines the action plan based on validation feedback and game description.
@@ -1179,7 +1347,7 @@ Use sprite names exactly as provided in `sprite_names` (e.g., 'Sprite1', 'soccer
1179
  - `pick random (100,-100)` → `(pick random (100) to (-100))`
1180
  - Missing `end` at script conclusion
1181
  - Unwrapped reporter inputs or Boolean tests
1182
- 4. **Opcode Lists**: include relevant Scratch opcodes grouped under `motion`, `control`, `operator`, `sensing`, `looks`, `sounds`, `events`, and `data`. List only the non-empty categories. Use exact opcodes .
1183
  5. Few Example of content of logics inside for a specific plan as scratch pseudo-code:
1184
  - example 1[continues moving objects]:
1185
  ```
@@ -1396,7 +1564,7 @@ Use sprite names exactly as provided in `sprite_names` (e.g., 'Sprite1', 'soccer
1396
  except Exception as e:
1397
  logger.error(f"Error in RefinedPlannerNode: {e}")
1398
  raise
1399
-
1400
  def plan_opcode_counter_node(state: Dict[str, Any]) -> Dict[str, Any]:
1401
  """
1402
  For each plan in state["action_plan"], calls the LLM agent
@@ -1493,13 +1661,14 @@ Events: {opcodes_from_plan["events"]}
1493
  Data: {opcodes_from_plan["data"]}
1494
 
1495
  ── Your Task ──
1496
- 1. Analyze the “Logic” steps and choose exactly which opcodes are needed.
1497
- 2. Return a top-level JSON object with a single key: "opcode_counts".
1498
- 3. The value of "opcode_counts" should be a list of objects, where each object has "opcode": "<opcode_name>" and "count": <integer>.
1499
- 4. Ensure the list includes the hat block for this plan (e.g., event_whenflagclicked, event_whenkeypressed, event_whenbroadcastreceived) with a count of 1.
1500
- 5. The order of opcodes within the "opcode_counts" list does not matter.
1501
- 6. If any plan logic is None do not generate the opcode_counts for it.
1502
- 7. Use only double quotes and ensure valid JSON.
 
1503
 
1504
  Example output:
1505
  **example 1**
@@ -1589,91 +1758,10 @@ Example output:
1589
  logger.info("=== OPCODE COUTER LOGIC completed ===")
1590
  return state
1591
 
1592
- # Node 10:Function based block builder node
1593
- def overall_block_builder_node_2(state: dict):
1594
- logger.info("--- Running OverallBlockBuilderNode ---")
1595
-
1596
- project_json = state["project_json"]
1597
- targets = project_json["targets"]
1598
- # --- Sprite and Stage Target Mapping ---
1599
- sprite_map = {target["name"]: target for target in targets if not target["isStage"]}
1600
- stage_target = next((target for target in targets if target["isStage"]), None)
1601
- if stage_target:
1602
- sprite_map[stage_target["name"]] = stage_target
1603
-
1604
- action_plan = state.get("action_plan", {})
1605
- print("[Overall Action Plan received at the block generator]:", json.dumps(action_plan, indent=2))
1606
- if not action_plan:
1607
- logger.warning("No action plan found in state. Skipping OverallBlockBuilderNode.")
1608
- return state
1609
-
1610
- # Initialize offsets for script placement on the Scratch canvas
1611
- script_y_offset = {}
1612
- script_x_offset_per_sprite = {name: 0 for name in sprite_map.keys()}
1613
-
1614
- # This handles potential variations in the action_plan structure.
1615
- if action_plan.get("action_overall_flow", {}) == {}:
1616
- plan_data = action_plan.items()
1617
- else:
1618
- plan_data = action_plan.get("action_overall_flow", {}).items()
1619
-
1620
- # --- Extract global project context for LLM ---
1621
- all_sprite_names = list(sprite_map.keys())
1622
- all_variable_names = {}
1623
- all_list_names = {}
1624
- all_broadcast_messages = {}
1625
-
1626
- for target in targets:
1627
- for var_id, var_info in target.get("variables", {}).items():
1628
- all_variable_names[var_info[0]] = var_id # Store name -> ID mapping (e.g., "myVariable": "myVarId123")
1629
- for list_id, list_info in target.get("lists", {}).items():
1630
- all_list_names[list_info[0]] = list_id # Store name -> ID mapping
1631
- for broadcast_id, broadcast_name in target.get("broadcasts", {}).items():
1632
- all_broadcast_messages[broadcast_name] = broadcast_id # Store name -> ID mapping
1633
-
1634
- # --- Process each sprite's action plan ---
1635
- for sprite_name, sprite_actions_data in plan_data:
1636
- if sprite_name in sprite_map:
1637
- current_sprite_target = sprite_map[sprite_name]
1638
- if "blocks" not in current_sprite_target:
1639
- current_sprite_target["blocks"] = {}
1640
-
1641
- if sprite_name not in script_y_offset:
1642
- script_y_offset[sprite_name] = 0
1643
-
1644
- for plan_entry in sprite_actions_data.get("plans", []):
1645
- logic_sequence = str(plan_entry["logic"])
1646
- opcode_counts = plan_entry.get("opcode_counts", {})
1647
-
1648
- try:
1649
- generated_blocks=block_builder(opcode_counts,logic_sequence)
1650
- if "blocks" in generated_blocks and isinstance(generated_blocks["blocks"], dict):
1651
- logger.warning(f"LLM returned nested 'blocks' key for {sprite_name}. Unwrapping.")
1652
- generated_blocks = generated_blocks["blocks"]
1653
-
1654
- # Update block positions for top-level script
1655
- for block_id, block_data in generated_blocks.items():
1656
- if block_data.get("topLevel"):
1657
- block_data["x"] = script_x_offset_per_sprite.get(sprite_name, 0)
1658
- block_data["y"] = script_y_offset[sprite_name]
1659
- script_y_offset[sprite_name] += 150 # Increment for next script
1660
-
1661
- current_sprite_target["blocks"].update(generated_blocks)
1662
- print(f"[current_sprite_target block updated]: {current_sprite_target['blocks']}")
1663
- state["iteration_count"] = 0
1664
- logger.info(f"Action blocks added for sprite '{sprite_name}' by OverallBlockBuilderNode.")
1665
- except Exception as e:
1666
- logger.error(f"Error generating blocks for sprite '{sprite_name}': {e}")
1667
-
1668
- state["project_json"] = project_json
1669
- # with open("debug_state.json", "w", encoding="utf-8") as f:
1670
- # json.dump(state, f, indent=2, ensure_ascii=False)
1671
-
1672
- return state
1673
- # Node 10:Function based block builder node
1674
- def overall_block_builder_node_2(state: dict):
1675
  logger.info("--- Running OverallBlockBuilderNode ---")
1676
-
1677
  project_json = state["project_json"]
1678
  targets = project_json["targets"]
1679
  # --- Sprite and Stage Target Mapping ---
@@ -1762,12 +1850,17 @@ def overall_block_builder_node_2(state: dict):
1762
 
1763
  return state
1764
 
1765
- # # Node 11: Variable adder node
1766
  def variable_adder_node(state: GameState):
1767
  project_json = state["project_json"]
1768
  try:
1769
  updated_project_json = variable_adder_main(project_json)
1770
- state["project_json"]=updated_project_json
 
 
 
 
 
1771
  return state
1772
  except Exception as e:
1773
  logger.error(f"Error in variable adder node while updating project_json': {e}")
 
22
  from io import BytesIO
23
  from pathlib import Path
24
  import os
25
+ from utils.block_relation_builder import block_builder#, variable_adder_main
26
  from langchain.chat_models import ChatOpenAI
27
  from langchain_openai import ChatOpenAI
28
  from pydantic import Field, SecretStr
 
341
  return blk
342
  return None
343
 
344
+ def variable_intialization(project_data):
345
+ """
346
+ Updates variable and broadcast definitions in a Scratch project JSON,
347
+ populating the 'variables' and 'broadcasts' sections of the Stage target
348
+ and extracting initial values for variables.
349
+
350
+ Args:
351
+ project_data (dict): The loaded JSON data of the Scratch project.
352
+
353
+ Returns:
354
+ dict: The updated project JSON data.
355
+ """
356
+
357
+ stage_target = None
358
+ for target in project_data['targets']:
359
+ if target.get('isStage'):
360
+ stage_target = target
361
+ break
362
+
363
+ if stage_target is None:
364
+ print("Error: Stage target not found in the project data.")
365
+ return project_data
366
+
367
+ # Ensure 'variables' and 'broadcasts' exist in the Stage target
368
+ if "variables" not in stage_target:
369
+ stage_target["variables"] = {}
370
+ if "broadcasts" not in stage_target:
371
+ stage_target["broadcasts"] = {}
372
+
373
+ # Helper function to recursively find and update variable/broadcast fields
374
+ def process_dict(obj):
375
+ if isinstance(obj, dict):
376
+ # Check for "data_setvariableto" opcode to extract initial values
377
+ if obj.get("opcode") == "data_setvariableto":
378
+ variable_field = obj.get("fields", {}).get("VARIABLE")
379
+ value_input = obj.get("inputs", {}).get("VALUE")
380
+
381
+ if variable_field and isinstance(variable_field, list) and len(variable_field) == 2:
382
+ var_name = variable_field[0]
383
+ var_id = variable_field[1]
384
+
385
+ initial_value = ""
386
+ if value_input and isinstance(value_input, list) and len(value_input) > 1 and \
387
+ isinstance(value_input[1], list) and len(value_input[1]) > 1:
388
+ if value_input[1][0] == 10:
389
+ initial_value = str(value_input[1][1])
390
+ elif value_input[1][0] == 12 and len(value_input) > 2 and isinstance(value_input[2], list) and value_input[2][0] == 10:
391
+ initial_value = str(value_input[2][1])
392
+ elif isinstance(value_input[1], (str, int, float)):
393
+ initial_value = str(value_input[1])
394
+ stage_target["variables"][var_id] = [var_name, initial_value]
395
+
396
+ for key, value in obj.items():
397
+ # Process broadcast definitions in 'inputs' (BROADCAST_INPUT)
398
+ if key == "BROADCAST_INPUT" and isinstance(value, list) and len(value) == 2 and \
399
+ isinstance(value[1], list) and len(value[1]) == 3 and value[1][0] == 11:
400
+ broadcast_name = value[1][1]
401
+ broadcast_id = value[1][2]
402
+ stage_target["broadcasts"][broadcast_id] = broadcast_name
403
+
404
+ # Process broadcast definitions in 'fields' (BROADCAST_OPTION)
405
+ elif key == "BROADCAST_OPTION" and isinstance(value, list) and len(value) == 2:
406
+ broadcast_name = value[0]
407
+ broadcast_id = value[1]
408
+ stage_target["broadcasts"][broadcast_id] = broadcast_name
409
+
410
+ # Recursively call for nested dictionaries or lists
411
+ process_dict(value)
412
+
413
+ elif isinstance(obj, list):
414
+ for i, item in enumerate(obj):
415
+ # Process variable references in 'inputs' (like [12, "score", "id"])
416
+ if isinstance(item, list) and len(item) == 3 and item[0] == 12:
417
+ var_name = item[1]
418
+ var_id = item[2]
419
+ if var_id not in stage_target["variables"]:
420
+ stage_target["variables"][var_id] = [var_name, ""]
421
+ process_dict(item)
422
+
423
+ # Iterate through all targets to process their blocks
424
+ for target in project_data['targets']:
425
+ if "blocks" in target:
426
+ for block_id, block_data in target["blocks"].items():
427
+ process_dict(block_data)
428
+
429
+ return project_data
430
+
431
+ def deduplicate_variables(project_data):
432
+ """
433
+ Removes duplicate variable entries in the 'variables' dictionary of the Stage target,
434
+ prioritizing entries with non-empty values.
435
+
436
+ Args:
437
+ project_data (dict): The loaded JSON data of the Scratch project.
438
+
439
+ Returns:
440
+ dict: The updated project JSON data with deduplicated variables.
441
+ """
442
+
443
+ stage_target = None
444
+ for target in project_data['targets']:
445
+ if target.get('isStage'):
446
+ stage_target = target
447
+ break
448
+
449
+ if stage_target is None:
450
+ print("Error: Stage target not found in the project data.")
451
+ return project_data
452
+
453
+ if "variables" not in stage_target:
454
+ return project_data # No variables to deduplicate
455
+
456
+ # Use a temporary dictionary to store the preferred variable entry by name
457
+ # Format: {variable_name: [variable_id, variable_name, variable_value]}
458
+ resolved_variables = {}
459
+
460
+ for var_id, var_info in stage_target["variables"].items():
461
+ var_name = var_info[0]
462
+ var_value = var_info[1]
463
+
464
+ if var_name not in resolved_variables:
465
+ # If the variable name is not yet seen, add it
466
+ resolved_variables[var_name] = [var_id, var_name, var_value]
467
+ else:
468
+ # If the variable name is already seen, decide which one to keep
469
+ existing_id, existing_name, existing_value = resolved_variables[var_name]
470
+
471
+ # Prioritize the entry with a non-empty value
472
+ if var_value != "" and existing_value == "":
473
+ resolved_variables[var_name] = [var_id, var_name, var_value]
474
+ # If both have non-empty values, or both are empty, keep the current one (arbitrary choice, but consistent)
475
+ # The current logic will effectively keep the last one encountered that has a value,
476
+ # or the very last one if all are empty.
477
+ elif var_value != "" and existing_value != "":
478
+ # If there are multiple non-empty values for the same variable name
479
+ # this keeps the one from the most recent iteration.
480
+ # For the given example, this will correctly keep "5".
481
+ resolved_variables[var_name] = [var_id, var_name, var_value]
482
+ elif var_value == "" and existing_value == "":
483
+ # If both are empty, just keep the current one (arbitrary)
484
+ resolved_variables[var_name] = [var_id, var_name, var_value]
485
+
486
+
487
+ # Reconstruct the 'variables' dictionary using the resolved entries
488
+ new_variables_dict = {}
489
+ for var_name, var_data in resolved_variables.items():
490
+ var_id_to_keep = var_data[0]
491
+ var_name_to_keep = var_data[1]
492
+ var_value_to_keep = var_data[2]
493
+ new_variables_dict[var_id_to_keep] = [var_name_to_keep, var_value_to_keep]
494
+
495
+ stage_target["variables"] = new_variables_dict
496
+
497
+ return project_data
498
+
499
+ def variable_adder_main(project_data):
500
+ try:
501
+ declare_variable_json= variable_intialization(project_data)
502
+ print("declare_variable_json------->",declare_variable_json)
503
+ except Exception as e:
504
+ print(f"Error error in the variable initialization opcodes: {e}")
505
+ try:
506
+ processed_json= deduplicate_variables(declare_variable_json)
507
+ print("processed_json------->",processed_json)
508
+ return processed_json
509
+ except Exception as e:
510
+ print(f"Error error in the variable initialization opcodes: {e}")
511
+
512
 
513
  # --- Global variable for the block catalog ---
514
  ALL_SCRATCH_BLOCKS_CATALOG = {}
 
947
  print(f"[OVREALL REFINED PSEUDO CODE LOGIC]: {result}")
948
  logger.info("Plan refinement and block relation analysis completed for all plans.")
949
  return state
950
+ # Node 2: planner node
951
  def overall_planner_node(state: GameState):
952
  """
953
  Generates a comprehensive action plan for sprites, including detailed Scratch block information.
 
1255
  except Exception as e:
1256
  logger.error(f"Error in OverallPlannerNode: {e}")
1257
  raise
1258
+ # Node 3: refiner node
1259
  def refined_planner_node(state: GameState):
1260
  """
1261
  Refines the action plan based on validation feedback and game description.
 
1347
  - `pick random (100,-100)` → `(pick random (100) to (-100))`
1348
  - Missing `end` at script conclusion
1349
  - Unwrapped reporter inputs or Boolean tests
1350
+ 4. **Opcode Lists**: include relevant Scratch opcodes grouped under `motion`, `control`, `operator`, `sensing`, `looks`, `sounds`, `events`, and `data`. List only the non-empty categories. Use exact opcodes from the given Scratch 3.0 Block Reference.
1351
  5. Few Example of content of logics inside for a specific plan as scratch pseudo-code:
1352
  - example 1[continues moving objects]:
1353
  ```
 
1564
  except Exception as e:
1565
  logger.error(f"Error in RefinedPlannerNode: {e}")
1566
  raise
1567
+ # Node 4: opcode counter node
1568
  def plan_opcode_counter_node(state: Dict[str, Any]) -> Dict[str, Any]:
1569
  """
1570
  For each plan in state["action_plan"], calls the LLM agent
 
1661
  Data: {opcodes_from_plan["data"]}
1662
 
1663
  ── Your Task ──
1664
+ 1. Analyze the “Logic” steps and choose exactly which opcodes are needed.
1665
+ 2. Use exact opcodes from the given Scratch 3.0 Block Reference and verfiy the proper opcode are used available in the Scratch 3.0 Block Reference.
1666
+ 3. Return a top-level JSON object with a single key: "opcode_counts".
1667
+ 4. The value of "opcode_counts" should be a list of objects, where each object has "opcode": "<opcode_name>" and "count": <integer>.
1668
+ 5. Ensure the list includes the hat block for this plan (e.g., event_whenflagclicked, event_whenkeypressed, event_whenbroadcastreceived) with a count of 1.
1669
+ 6. The order of opcodes within the "opcode_counts" list does not matter.
1670
+ 7. If any plan logic is None do not generate the opcode_counts for it.
1671
+ 8. Use only double quotes and ensure valid JSON.
1672
 
1673
  Example output:
1674
  **example 1**
 
1758
  logger.info("=== OPCODE COUTER LOGIC completed ===")
1759
  return state
1760
 
1761
+ # Node 5: block_builder_node
1762
+ def overall_block_builder_node_2(state: GameState):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1763
  logger.info("--- Running OverallBlockBuilderNode ---")
1764
+ print("--- Running OverallBlockBuilderNode ---")
1765
  project_json = state["project_json"]
1766
  targets = project_json["targets"]
1767
  # --- Sprite and Stage Target Mapping ---
 
1850
 
1851
  return state
1852
 
1853
+ # Node 6: variable adder node
1854
  def variable_adder_node(state: GameState):
1855
  project_json = state["project_json"]
1856
  try:
1857
  updated_project_json = variable_adder_main(project_json)
1858
+ if updated_project_json is not None:
1859
+ print("Variable added inside the project successfully!")
1860
+ state["project_json"]=updated_project_json
1861
+ else:
1862
+ print("Variable adder unable to add any variable inside the project!")
1863
+ state["project_json"]=project_json
1864
  return state
1865
  except Exception as e:
1866
  logger.error(f"Error in variable adder node while updating project_json': {e}")