prthm11 commited on
Commit
10b2288
·
verified ·
1 Parent(s): def05c9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -447
app.py CHANGED
@@ -141,27 +141,16 @@ for d in (
141
  # JSON_DIR,
142
  ):
143
  d.mkdir(parents=True, exist_ok=True)
144
- # def classify_image_type(description_or_name: str) -> str:
145
- # desc = description_or_name.lower()
146
-
147
- # sprite_keywords = ["sprite", "character", "animal", "person", "creature", "robot", "figure"]
148
- # backdrop_keywords = ["background", "scene", "forest", "city", "room", "sky", "mountain", "village"]
149
- # code_block_keywords = [
150
- # "move", "turn", "wait", "repeat", "if", "else", "broadcast",
151
- # "glide", "change", "forever", "when", "switch", "costume",
152
- # "say", "think", "stop", "clone", "touching", "sensing",
153
- # "scratch", "block", "code", "set", "variable"
154
- # ]
155
-
156
- # if any(kw in desc for kw in code_block_keywords):
157
- # return "code-block"
158
- # elif any(kw in desc for kw in sprite_keywords):
159
- # return "sprite"
160
- # elif any(kw in desc for kw in backdrop_keywords):
161
- # return "backdrop"
162
- # else:
163
- # return "unknown"
164
 
 
 
 
 
 
 
 
 
 
165
  class GameState(TypedDict):
166
  project_json: dict
167
  description: str
@@ -170,10 +159,9 @@ class GameState(TypedDict):
170
  pseudo_code: dict
171
  action_plan: Optional[Dict]
172
  temporary_node: Optional[Dict]
173
-
174
- # class GameState(TypedDict):
175
- # image: str
176
- # pseudo_node: Optional[Dict]
177
 
178
  # Refined SYSTEM_PROMPT with more explicit Scratch JSON rules, especially for variables
179
  SYSTEM_PROMPT = """
@@ -278,25 +266,6 @@ agent_json_resolver = create_react_agent(
278
  prompt=SYSTEM_PROMPT_JSON_CORRECTOR
279
  )
280
 
281
- # # Helper function to load the block catalog from a JSON file
282
- # def _load_block_catalog(file_path: str) -> Dict:
283
- # """Loads the Scratch block catalog from a specified JSON file."""
284
- # try:
285
- # with open(file_path, 'r') as f:
286
- # catalog = json.load(f)
287
- # logger.info(f"Successfully loaded block catalog from {file_path}")
288
- # return catalog
289
- # except FileNotFoundError:
290
- # logger.error(f"Error: Block catalog file not found at {file_path}")
291
- # # Return an empty dict or raise an error, depending on desired behavior
292
- # return {}
293
- # except json.JSONDecodeError as e:
294
- # logger.error(f"Error decoding JSON from {file_path}: {e}")
295
- # return {}
296
- # except Exception as e:
297
- # logger.error(f"An unexpected error occurred while loading {file_path}: {e}")
298
- # return {}
299
-
300
  # Helper function to load the block catalog from a JSON file
301
  def _load_block_catalog(block_type: str) -> Dict:
302
  """
@@ -696,7 +665,6 @@ def extract_json_from_llm_response(raw_response: str) -> dict:
696
  # # 6. Return with the correct data URI prefix
697
  # return f"data:image/png;base64,{clean_b64}"
698
 
699
- # reducing imagebase64 size if greater than something
700
  def reduce_image_size_to_limit(clean_b64_str, max_kb=4000):
701
  """
702
  Reduce an image's size to be as close as possible to max_kb without exceeding it.
@@ -762,7 +730,7 @@ def clean_base64_for_model(raw_b64):
762
  # Log original size
763
  original_size = len(clean_b64.encode("utf-8"))
764
  print(f"Original Base64 size (bytes): {original_size}")
765
- if original_size> 4000000:
766
  # Reduce size to under 4 MB
767
  reduced_b64 = reduce_image_size_to_limit(clean_b64, max_kb=4000)
768
  clean_b64_2 = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", reduced_b64)
@@ -772,7 +740,7 @@ def clean_base64_for_model(raw_b64):
772
  # Return both prefixed and clean reduced versions
773
  return f"data:image/jpeg;base64,{reduced_b64}"
774
  return f"data:image/jpeg;base64,{clean_b64}"
775
-
776
  def format_scratch_pseudo_code(code_string):
777
  """
778
  Parses and formats Scratch pseudo-code with correct indentation,
@@ -823,16 +791,24 @@ def format_scratch_pseudo_code(code_string):
823
 
824
  return '\n'.join(formatted_lines)
825
 
826
-
827
  # Node 1: Logic updating if any issue here
828
  def pseudo_generator_node(state: GameState):
829
  logger.info("--- Running plan_logic_aligner_node ---")
830
  image = state.get("project_image", "")
831
  project_json = state["project_json"]
832
-
 
833
  # MODIFICATION 1: Include 'Stage' in the list of names to plan for.
834
  # It's crucial to ensure 'Stage' is always present for its global role.
835
  target_names = [t["name"] for t in project_json["targets"]]
 
 
 
 
 
 
 
 
836
 
837
  refinement_prompt = f"""
838
  You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
@@ -840,8 +816,15 @@ You are an expert in Scratch 3.0 game development, specializing in understanding
840
  From Image, you also have to detect a value of Key given in Text form "Script for: ". Below is the example
841
  Example: "Script for: Bear", "Script for:" is a key and "Bear" is value and check if there is related target name available.
842
 
843
- **Targets in Game (Sprites and Stage) available in project_json:** {', '.join(target_names)}
 
 
844
 
 
 
 
 
 
845
  --- Scratch 3.0 Block Reference ---
846
  ### Hat Blocks
847
  Description: {hat_description}
@@ -974,7 +957,7 @@ If you find any "Code-Blocks" then,
974
  "type": "image_url",
975
  "image_url": {
976
  # "url": f"data:image/png;base64,{image}"
977
- "url": clean_base64_for_model(image)
978
  }
979
  }
980
 
@@ -1018,10 +1001,12 @@ If you find any "Code-Blocks" then,
1018
 
1019
  # Update the original action_plan in the state with the refined version
1020
  state["pseudo_code"] = result
1021
-
 
1022
  # with open("debug_state.json", "w", encoding="utf-8") as f:
1023
  # json.dump(state, f, indent=2, ensure_ascii=False)
1024
  print(f"[OVREALL REFINED PSEUDO CODE LOGIC]: {result}")
 
1025
  logger.info("Plan refinement and block relation analysis completed for all plans.")
1026
  return state
1027
  # Node 2: planner node
@@ -1929,6 +1914,7 @@ def overall_block_builder_node_2(state: GameState):
1929
 
1930
  # Node 6: variable adder node
1931
  def variable_adder_node(state: GameState):
 
1932
  project_json = state["project_json"]
1933
  try:
1934
  updated_project_json = variable_adder_main(project_json)
@@ -1938,239 +1924,23 @@ def variable_adder_node(state: GameState):
1938
  else:
1939
  print("Variable adder unable to add any variable inside the project!")
1940
  state["project_json"]=project_json
 
1941
  return state
1942
  except Exception as e:
1943
  logger.error(f"Error in variable adder node while updating project_json': {e}")
1944
  raise
1945
 
1946
-
1947
- # scratch_keywords = [
1948
- # "move", "turn", "wait", "repeat", "if", "else", "broadcast",
1949
- # "glide", "change", "forever", "when", "switch",
1950
- # "next costume", "set", "show", "hide", "play sound",
1951
- # "go to", "x position", "y position", "think", "say",
1952
- # "variable", "stop", "clone",
1953
- # "touching", "sensing", "pen", "clear","Scratch","Code","scratch blocks"
1954
- # ]
1955
-
1956
- # Node 6: Logic updating if any issue here
1957
- # def plan_logic_aligner_node(state: GameState):
1958
- # logger.info("--- Running plan_logic_aligner_node ---")
1959
-
1960
- # image = state.get("image", "")
1961
-
1962
- # refinement_prompt = f"""
1963
- # You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
1964
- # "Analyze the Scratch code-block image and generate Pseudo-Code for what this logic appears to be doing."
1965
- # From Image, you also have to detect a value of Key given in Text form "Script for: ". Below is the example
1966
- # Example: "Script for: Bear", "Script for:" is a key and "Bear" is value.
1967
- # --- Scratch 3.0 Block Reference ---
1968
- # ### Hat Blocks
1969
- # Description: {hat_description}
1970
- # Blocks:
1971
- # {hat_opcodes_functionalities}
1972
-
1973
- # ### Boolean Blocks
1974
- # Description: {boolean_description}
1975
- # Blocks:
1976
- # {boolean_opcodes_functionalities}
1977
-
1978
- # ### C Blocks
1979
- # Description: {c_description}
1980
- # Blocks:
1981
- # {c_opcodes_functionalities}
1982
-
1983
- # ### Cap Blocks
1984
- # Description: {cap_description}
1985
- # Blocks:
1986
- # {cap_opcodes_functionalities}
1987
-
1988
- # ### Reporter Blocks
1989
- # Description: {reporter_description}
1990
- # Blocks:
1991
- # {reporter_opcodes_functionalities}
1992
-
1993
- # ### Stack Blocks
1994
- # Description: {stack_description}
1995
- # Blocks:
1996
- # {stack_opcodes_functionalities}
1997
- # -----------------------------------
1998
-
1999
- # Your task is to:
2000
- # If you don't find any "Code-Blocks" then,
2001
- # **Don't generate Pseudo Code, and pass the message "No Code-blocks" find...
2002
- # If you find any "Code-Blocks" then,
2003
- # 1. **Refine the 'logic'**: Make it precise, accurate, and fully aligned with the Game Description. Use Scratch‑consistent verbs and phrasing. **Do NOT** use raw double‑quotes inside the logic string.
2004
-
2005
- # 2. **Structural requirements**:
2006
- # - **Numeric values** `(e.g., 0, 5, 0.2, -130)` **must** be in parentheses: `(0)`, `(5)`, `(0.2)`, `(-130)`.
2007
- # - **AlphaNumeric values** `(e.g., hello, say 5, 4, hi!)` **must** be in parentheses: `(hello)`, `(say 5)`, `(4)`, `(hi!)`.
2008
- # - **Variables** must be in the form `[variable v]` (e.g., `[score v]`), even when used inside expressions two example use `set [score v] to (1)` or `show variable ([speed v])`.
2009
- # - **Dropdown options** must be in the form `[option v]` (e.g., `[Game Start v]`, `[blue sky v]`). example use `when [space v] key pressed`.
2010
- # - **Reporter blocks** used as inputs must be double‑wrapped: `((x position))`, `((y position))`. example use `if <((y position)) = (-130)> then` or `(((x position)) * (1))`.
2011
- # - **Boolean blocks** in conditions must be inside `< >`, including nested ones: `<not <condition>>`, `<<cond1> and <cond2>>`,`<<cond1> or <cond2>>`.
2012
- # - **Other Boolean blocks** in conditions must be inside `< >`, including nested ones or values or variables: `<(block/value/variable) * (block/value/variable)>`,`<(block/value/variable) < (block/value/variable)>`, and example of another variable`<[apple v] contains [a v]?>`.
2013
- # - **Operator expressions** must use explicit Scratch operator blocks, e.g.:
2014
- # ```
2015
- # (([ballSpeed v]) * (1.1))
2016
- # ```
2017
- # - **Every hat block script must end** with a final `end` on its own line.
2018
-
2019
- # 3. **Pseudo‑code formatting**:
2020
- # - Represent each block or nested block on its own line.
2021
- # - Indent nested blocks by 4 spaces under their parent (`forever`, `if`, etc.).
2022
- # - No comments or explanatory text—just the block sequence.
2023
- # - a natural language breakdown of each step taken after the event, formatted as a multi-line string representing pseudo-code. Ensure clarity and granularity—each described action should map closely to a Scratch block or tight sequence.
2024
-
2025
- # 4. **Logic content**:
2026
- # - Build clear flow for mechanics (movement, jumping, flying, scoring, collisions).
2027
- # - Match each action closely to a Scratch block or tight sequence.
2028
- # - Do **NOT** include any justification or comments—only the raw logic.
2029
-
2030
- # 5. **Examples for reference**:
2031
- # **Correct** pattern for a simple start script:
2032
- # ```
2033
- # when green flag clicked
2034
- # switch backdrop to [blue sky v]
2035
- # set [score v] to (0)
2036
- # show variable [score v]
2037
- # broadcast [Game Start v]
2038
- # end
2039
- # ```
2040
- # **Correct** pattern for updating the high score variable handling:
2041
- # ```
2042
- # when I receive [Game Over v]
2043
- # if <((score)) > (([High Score v]))> then
2044
- # set [High Score v] to ([score v])
2045
- # end
2046
- # switch backdrop to [Game Over v]
2047
- # end
2048
- # ```
2049
- # **Correct** pattern for level up and increase difficulty use:
2050
- # ```
2051
- # when I receive [Level Up v]
2052
- # change [level v] by (1)
2053
- # set [ballSpeed v] to ((([ballSpeed v]) * (1.1)))
2054
- # end
2055
- # ```
2056
- # **Correct** pattern for jumping mechanics use:
2057
- # ```
2058
- # when [space v] key pressed
2059
- # if <((y position)) = (-100)> then
2060
- # repeat (5)
2061
- # change y by (100)
2062
- # wait (0.1) seconds
2063
- # change y by (-100)
2064
- # wait (0.1) seconds
2065
- # end
2066
- # end
2067
- # end
2068
- # ```
2069
- # **Correct** pattern for continuos moving objects use:
2070
- # ```
2071
- # when green flag clicked
2072
- # go to x: (240) y: (-100)
2073
- # set [speed v] to (-5)
2074
- # show variable [speed v]
2075
- # forever
2076
- # change x by ([speed v])
2077
- # if <((x position)) < (-240)> then
2078
- # go to x: (240) y: (-100)
2079
- # end
2080
- # end
2081
- # end
2082
- # ```
2083
- # **Correct** pattern for continuos moving objects use:
2084
- # ```
2085
- # when green flag clicked
2086
- # go to x: (240) y: (-100)
2087
- # set [speed v] to (-5)
2088
- # show variable [speed v]
2089
- # forever
2090
- # change x by ([speed v])
2091
- # if <((x position)) < (-240)> then
2092
- # go to x: (240) y: (-100)
2093
- # end
2094
- # end
2095
- # end
2096
- # ```
2097
- # 6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
2098
- # 7. **Output**:
2099
- # Return **only** a JSON object, using double quotes everywhere:
2100
- # ```json
2101
- # {{
2102
- # "refined_logic":{{
2103
- # "name_variable": 'Value of "Sript for: "',
2104
- # "pseudocode":"…your fully‑formatted pseudo‑code here…",
2105
- # }}
2106
- # }}
2107
- # ```
2108
- # """
2109
- # image_input = {
2110
- # "type": "image_url",
2111
- # "image_url": {
2112
- # "url": f"data:image/png;base64,{image}"
2113
- # }
2114
- # }
2115
-
2116
- # content = [
2117
- # {"type": "text", "text": refinement_prompt},
2118
- # image_input
2119
- # ]
2120
-
2121
- # try:
2122
- # # Invoke the main agent for logic refinement and relationship identification
2123
- # response = agent.invoke({"messages": [{"role": "user", "content": content}]})
2124
- # llm_output_raw = response["messages"][-1].content.strip()
2125
-
2126
- # parsed_llm_output = extract_json_from_llm_response(llm_output_raw)
2127
-
2128
- # # result = parsed_llm_output
2129
- # # Extract needed values directly
2130
- # logic_data = parsed_llm_output.get("refined_logic", {})
2131
- # name_variable = logic_data.get("name_variable", "Unknown")
2132
- # pseudocode = logic_data.get("pseudocode", "No logic extracted")
2133
-
2134
- # result = {"pseudo_node": {
2135
- # "name_variable": name_variable,
2136
- # "pseudocode": pseudocode
2137
- # }}
2138
-
2139
- # print(f"result:\n\n {result}")
2140
- # return result
2141
- # except Exception as e:
2142
- # logger.error(f"❌ plan_logic_aligner_node failed: {str(e)}")
2143
- # return {"error": str(e)}
2144
- # except json.JSONDecodeError as error_json:
2145
- # # If JSON parsing fails, use the json resolver agent
2146
- # correction_prompt = (
2147
- # "Your task is to correct the provided JSON string to ensure it is **syntactically perfect and adheres strictly to JSON rules**.\n"
2148
- # "It must be a JSON object with `refined_logic` (string) and `block_relationships` (array of objects).\n"
2149
- # f"- **Error Details**: {error_json}\n\n"
2150
- # "**Strict Instructions for your response:**\n"
2151
- # "1. **ONLY** output the corrected JSON. Do not include any other text or explanations.\n"
2152
- # "2. Ensure all keys and string values are enclosed in **double quotes**. Escape internal quotes (`\\`).\n"
2153
- # "3. No trailing commas. Correct nesting.\n\n"
2154
- # "Here is the problematic JSON string to correct:\n"
2155
- # f"```json\n{llm_output_raw}\n```\n"
2156
- # "Corrected JSON:\n"
2157
- # )
2158
- # try:
2159
- # correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
2160
- # corrected_output = extract_json_from_llm_response(correction_response["messages"][-1].content)
2161
-
2162
- # result = {
2163
- # #"image_path": image_path,
2164
- # "pseudo_code": corrected_output
2165
- # }
2166
-
2167
- # return result
2168
-
2169
- # except Exception as e_corr:
2170
- # logger.error(f"Failed to correct JSON output for even after retry: {e_corr}")
2171
-
2172
- #def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path, image_base_dir: Path):
2173
- #def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path):
2174
 
2175
  # Prepare manipulated sprite JSON structure
2176
  manipulated_json = {}
@@ -2395,7 +2165,7 @@ def similarity_matching(sprites_data: str, project_folder: str) -> str:
2395
  # Copy matched backdrop assets + collect
2396
  # =========================================
2397
  backdrop_data = []
2398
-
2399
  for backdrop_idx, matched_idx in enumerate(most_similar_indices):
2400
  matched_image_path = folder_image_paths[matched_idx]
2401
  matched_folder = os.path.dirname(matched_image_path)
@@ -2405,6 +2175,11 @@ def similarity_matching(sprites_data: str, project_folder: str) -> str:
2405
  if not matched_folder.startswith(backdrop_base_path):
2406
  continue
2407
 
 
 
 
 
 
2408
  logger.info(f"Matched backdrop: {matched_image_path}")
2409
 
2410
  # 1) Copy the matched backdrop image itself
@@ -2464,8 +2239,15 @@ def similarity_matching(sprites_data: str, project_folder: str) -> str:
2464
  # then backdrop as the Stage
2465
  if backdrop_data:
2466
  all_costumes, sounds = [], []
 
2467
  for i, bd in enumerate(backdrop_data):
2468
- all_costumes.extend(bd.get("costumes", []))
 
 
 
 
 
 
2469
  if i == 0:
2470
  sounds = bd.get("sounds", [])
2471
  stage_obj={
@@ -2548,145 +2330,6 @@ def similarity_matching(sprites_data: str, project_folder: str) -> str:
2548
  json.dump(final_project, f, indent=2)
2549
 
2550
  return project_json_path
2551
- # for sprite_idx, matched_idx in enumerate(most_similar_indices):
2552
- # matched_image_path = folder_image_paths[matched_idx]
2553
- # matched_image_path = os.path.normpath(matched_image_path)
2554
- # print(" --------------------------------------1- matched_image_path ---------------------------------------",matched_image_path)
2555
- # matched_folder = os.path.dirname(matched_image_path)
2556
- # #folder_name = os.path.basename(matched_folder)
2557
- # print(" --------------------------------------1- matched_folder ---------------------------------------",matched_folder)
2558
- # if matched_folder in copied_folders:
2559
- # continue
2560
- # copied_folders.add(matched_folder)
2561
- # logger.info(f"Matched image path: {matched_image_path}")
2562
-
2563
- # sprite_json_path = os.path.join(matched_folder, 'sprite.json')
2564
- # print(" --------------------------------------- sprite_json_path ---------------------------------------",sprite_json_path)
2565
- # if not os.path.exists(sprite_json_path):
2566
- # logger.warning(f"sprite.json not found in: {matched_folder}")
2567
- # continue
2568
-
2569
- # with open(sprite_json_path, 'r') as f:
2570
- # sprite_data = json.load(f)
2571
- # # print(f"SPRITE DATA: \n{sprite_data}")
2572
- # # # Copy only non-matched files
2573
- # # for fname in os.listdir(matched_folder):
2574
- # # fpath = os.path.join(matched_folder, fname)
2575
- # # if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'sprite.json'}:
2576
- # # shutil.copy2(fpath, os.path.join(project_folder, fname))
2577
- # # # logger.info(f"Copied Sprite asset: {fname}")
2578
- # project_data.append(sprite_data)
2579
- # print(" --------------------------------------1- project_data ---------------------------------------",project_data)
2580
- # for fname in os.listdir(matched_folder):
2581
- # fpath = os.path.join(matched_folder, fname)
2582
- # dest_path = os.path.join(project_folder, fname)
2583
- # if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'sprite.json'}:
2584
- # shutil.copy2(fpath, dest_path)
2585
- # logger.info(f"🟢 Copied Sprite Asset: {fpath} → {dest_path}")
2586
-
2587
- # # ================================================================== #
2588
- # # Loop through most similar images from Backdrops folder #
2589
- # # → Copy Backdrop assets (excluding matched image + project.json) #
2590
- # # → Load project.json and append its data to project_data #
2591
- # # ================================================================== #
2592
- # backdrop_data = [] # for backdrop-related entries
2593
-
2594
- # for backdrop_idx, matched_idx in enumerate(most_similar_indices):
2595
- # matched_image_path = os.path.normpath(folder_image_paths[matched_idx])
2596
- # print(" --------------------------------------2- matched_image_path ---------------------------------------",matched_image_path)
2597
- # # Check if the match is from the Backdrops folder
2598
- # if matched_image_path.startswith(os.path.normpath(backdrop_images_path)):
2599
- # matched_folder = os.path.dirname(matched_image_path)
2600
- # print(" --------------------------------------2- matched_folder ---------------------------------------",matched_folder)
2601
- # folder_name = os.path.basename(matched_folder)
2602
-
2603
- # logger.info(f"Backdrop matched image: {matched_image_path}")
2604
-
2605
- # # Copy only non-matched files
2606
- # # for fname in os.listdir(matched_folder):
2607
- # # fpath = os.path.join(matched_folder, fname)
2608
- # # if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'project.json'}:
2609
- # # shutil.copy2(fpath, os.path.join(project_folder, fname))
2610
- # # # logger.info(f"Copied Backdrop asset: {fname}")
2611
- # for fname in os.listdir(matched_folder):
2612
- # fpath = os.path.join(matched_folder, fname)
2613
- # dest_path = os.path.join(project_folder, fname)
2614
- # if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'project.json'}:
2615
- # shutil.copy2(fpath, dest_path)
2616
- # logger.info(f"🟡 Copied Backdrop Asset: {fpath} → {dest_path}")
2617
-
2618
-
2619
- # # Append backdrop's project.json
2620
- # backdrop_json_path = os.path.join(matched_folder, 'project.json')
2621
- # print(" --------------------------------------2- backdrop_json_path ---------------------------------------",backdrop_json_path)
2622
- # if os.path.exists(backdrop_json_path):
2623
- # with open(backdrop_json_path, 'r') as f:
2624
- # backdrop_json_data = json.load(f)
2625
- # # print(f"SPRITE DATA: \n{backdrop_json_data}")
2626
- # if "targets" in backdrop_json_data:
2627
- # for target in backdrop_json_data["targets"]:
2628
- # if target.get("isStage") == True:
2629
- # backdrop_data.append(target)
2630
- # else:
2631
- # logger.warning(f"project.json not found in: {matched_folder}")
2632
-
2633
- # # Merge JSON structure
2634
- # final_project = {
2635
- # "targets": [],
2636
- # "monitors": [],
2637
- # "extensions": [],
2638
- # "meta": {
2639
- # "semver": "3.0.0",
2640
- # "vm": "11.3.0",
2641
- # "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
2642
- # }
2643
- # }
2644
-
2645
- # for sprite in project_data:
2646
- # if not sprite.get("isStage", False):
2647
- # final_project["targets"].append(sprite)
2648
-
2649
- # if backdrop_data:
2650
- # all_costumes, sounds = [], []
2651
- # for idx, bd in enumerate(backdrop_data):
2652
- # all_costumes.extend(bd.get("costumes", []))
2653
- # if idx == 0 and "sounds" in bd:
2654
- # sounds = bd["sounds"]
2655
- # final_project["targets"].append({
2656
- # "isStage": True,
2657
- # "name": "Stage",
2658
- # "variables": {},
2659
- # "lists": {},
2660
- # "broadcasts": {},
2661
- # "blocks": {},
2662
- # "comments": {},
2663
- # "currentCostume": 1 if len(all_costumes) > 1 else 0,
2664
- # "costumes": all_costumes,
2665
- # "sounds": sounds,
2666
- # "volume": 100,
2667
- # "layerOrder": 0,
2668
- # "tempo": 60,
2669
- # "videoTransparency": 50,
2670
- # "videoState": "on",
2671
- # "textToSpeechLanguage": None
2672
- # })
2673
-
2674
- # with open(project_json_path, 'w') as f:
2675
- # json.dump(final_project, f, indent=2)
2676
-
2677
- # # logger.info(f"🎉 Final project saved: {project_json_path}")
2678
- # return project_json_path
2679
-
2680
- # def convert_bytes_to_image(pdf_bytes: bytes, dpi: int):
2681
- # images = convert_from_bytes(pdf_bytes, dpi=dpi, poppler_path=poppler_path)
2682
- # # Save each page to an in-memory BytesIO and return a list of BytesIOs
2683
- # buffers = []
2684
- # for img in images:
2685
- # buf = BytesIO()
2686
- # img.save(buf, format="PNG")
2687
- # buf.seek(0)
2688
- # buffers.append(buf)
2689
- # return buffers
2690
 
2691
  def convert_pdf_stream_to_images(pdf_stream: io.BytesIO, dpi=300):
2692
  # Ensure we are at the start of the stream
@@ -2702,7 +2345,7 @@ def convert_pdf_stream_to_images(pdf_stream: io.BytesIO, dpi=300):
2702
 
2703
  def delay_for_tpm_node(state: GameState):
2704
  logger.info("--- Running DelayForTPMNode ---")
2705
- time.sleep(1) # Adjust the delay as needed
2706
  logger.info("Delay completed.")
2707
  return state
2708
 
@@ -2722,18 +2365,37 @@ workflow.add_node("refined_planner", refined_planner_node) # Refines the action
2722
  workflow.add_node("opcode_counter", plan_opcode_counter_node)
2723
  workflow.add_node("block_builder", overall_block_builder_node_2)
2724
  workflow.add_node("variable_initializer", variable_adder_node)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2725
 
2726
- workflow.set_entry_point("pseudo_generator")
2727
- workflow.add_edge("pseudo_generator","time_delay_1")
2728
- workflow.add_edge("time_delay_1","plan_generator")
2729
- workflow.add_edge("plan_generator","time_delay_2")
2730
- # workflow.add_edge("time_delay_2",END)
2731
- workflow.add_edge("time_delay_2","refined_planner")
2732
- workflow.add_edge("refined_planner","time_delay_3")
2733
- workflow.add_edge("time_delay_3","opcode_counter")
2734
- workflow.add_edge("opcode_counter","block_builder")
2735
- workflow.add_edge("block_builder","variable_initializer")
2736
- workflow.add_edge("variable_initializer", END)
2737
  app_graph = workflow.compile()
2738
 
2739
  # ============== Helper function to Upscale an Image ============== #
@@ -2911,6 +2573,11 @@ def process_pdf():
2911
  print(f"-----------------------------Execution Time save_pdf_to_generated_dir() : {total_time}-----------------------------\n")
2912
  # }
2913
 
 
 
 
 
 
2914
  # {
2915
  # Extract & process
2916
  # output_path, result = extract_images_from_pdf(saved_pdf_path)
@@ -2956,23 +2623,26 @@ def process_pdf():
2956
  images = convert_pdf_stream_to_images(pdf_stream, dpi=300)
2957
  else:
2958
  images = convert_from_path(pdf_stream, dpi=300)
2959
-
2960
  #updating logic here [Dev Patel]
2961
- # initial_state_dict = {
2962
- # "project_json": project_skeleton,
2963
- # "description": "The pseudo code for the script",
2964
- # "project_id": project_id,
2965
- # # "project_image": img_b64,
2966
- # "project_image": images,
2967
- # "action_plan": {},
2968
- # "pseudo_code": {},
2969
- # "temporary_node": {},
2970
- # }
 
 
 
2971
 
2972
- # final_state_dict = app_graph.invoke(initial_state_dict) # Pass dictionary
2973
-
2974
- # final_project_json = final_state_dict['project_json'] # Access as dict
2975
- final_project_json = project_skeleton
2976
 
2977
  # Save the *final* filled project JSON, overwriting the skeleton
2978
  with open(project_output, "w") as f:
 
141
  # JSON_DIR,
142
  ):
143
  d.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ # class GameState(TypedDict):
146
+ # project_json: dict
147
+ # description: str
148
+ # project_id: str
149
+ # project_image: str
150
+ # pseudo_code: dict
151
+ # action_plan: Optional[Dict]
152
+ # temporary_node: Optional[Dict]
153
+
154
  class GameState(TypedDict):
155
  project_json: dict
156
  description: str
 
159
  pseudo_code: dict
160
  action_plan: Optional[Dict]
161
  temporary_node: Optional[Dict]
162
+ page_count: int
163
+ processing: bool
164
+ temp_pseudo_code: list
 
165
 
166
  # Refined SYSTEM_PROMPT with more explicit Scratch JSON rules, especially for variables
167
  SYSTEM_PROMPT = """
 
266
  prompt=SYSTEM_PROMPT_JSON_CORRECTOR
267
  )
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  # Helper function to load the block catalog from a JSON file
270
  def _load_block_catalog(block_type: str) -> Dict:
271
  """
 
665
  # # 6. Return with the correct data URI prefix
666
  # return f"data:image/png;base64,{clean_b64}"
667
 
 
668
  def reduce_image_size_to_limit(clean_b64_str, max_kb=4000):
669
  """
670
  Reduce an image's size to be as close as possible to max_kb without exceeding it.
 
730
  # Log original size
731
  original_size = len(clean_b64.encode("utf-8"))
732
  print(f"Original Base64 size (bytes): {original_size}")
733
+ if original_size > 4000000:
734
  # Reduce size to under 4 MB
735
  reduced_b64 = reduce_image_size_to_limit(clean_b64, max_kb=4000)
736
  clean_b64_2 = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", reduced_b64)
 
740
  # Return both prefixed and clean reduced versions
741
  return f"data:image/jpeg;base64,{reduced_b64}"
742
  return f"data:image/jpeg;base64,{clean_b64}"
743
+
744
  def format_scratch_pseudo_code(code_string):
745
  """
746
  Parses and formats Scratch pseudo-code with correct indentation,
 
791
 
792
  return '\n'.join(formatted_lines)
793
 
 
794
  # Node 1: Logic updating if any issue here
795
  def pseudo_generator_node(state: GameState):
796
  logger.info("--- Running plan_logic_aligner_node ---")
797
  image = state.get("project_image", "")
798
  project_json = state["project_json"]
799
+ cnt =state["page_count"]
800
+ print(f"The page number recived at the pseudo_generator node:-----> {cnt}")
801
  # MODIFICATION 1: Include 'Stage' in the list of names to plan for.
802
  # It's crucial to ensure 'Stage' is always present for its global role.
803
  target_names = [t["name"] for t in project_json["targets"]]
804
+ stage_names = [t["name"] for t in project_json["targets"] if t.get("isStage")]
805
+ sprite_names = [t["name"] for t in project_json["targets"] if not t.get("isStage")]
806
+ # Get costumes separately for Stage and Sprites
807
+ stage_costumes = [
808
+ c["name"]
809
+ for t in project_json["targets"] if t.get("isStage")
810
+ for c in t.get("costumes", [])
811
+ ]
812
 
813
  refinement_prompt = f"""
814
  You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
 
816
  From Image, you also have to detect a value of Key given in Text form "Script for: ". Below is the example
817
  Example: "Script for: Bear", "Script for:" is a key and "Bear" is value and check if there is related target name available.
818
 
819
+ **Special Rule for Stage Costumes:**
820
+ If the value of "Script for:" exactly matches ANY costume name in the Stage_costumes list given below,
821
+ then the `"name_variable"` in the output JSON must be set to `"Stage"` (not the costume name).
822
 
823
+ **Targets in Game (Sprites and Stage) available in project_json:**
824
+ Sprite_name:{', '.join(sprite_names)}
825
+ Stage_name: Stage
826
+ Stage_costumes: {', '.join(stage_costumes)}
827
+
828
  --- Scratch 3.0 Block Reference ---
829
  ### Hat Blocks
830
  Description: {hat_description}
 
957
  "type": "image_url",
958
  "image_url": {
959
  # "url": f"data:image/png;base64,{image}"
960
+ "url": clean_base64_for_model(image[cnt])
961
  }
962
  }
963
 
 
1001
 
1002
  # Update the original action_plan in the state with the refined version
1003
  state["pseudo_code"] = result
1004
+ state["temp_pseudo_code"] += [result]
1005
+ Data = state["temp_pseudo_code"]
1006
  # with open("debug_state.json", "w", encoding="utf-8") as f:
1007
  # json.dump(state, f, indent=2, ensure_ascii=False)
1008
  print(f"[OVREALL REFINED PSEUDO CODE LOGIC]: {result}")
1009
+ print(f"[OVREALL LISTS OF LOGICS]: {Data}")
1010
  logger.info("Plan refinement and block relation analysis completed for all plans.")
1011
  return state
1012
  # Node 2: planner node
 
1914
 
1915
  # Node 6: variable adder node
1916
  def variable_adder_node(state: GameState):
1917
+ logger.info("--- Running Variable Adder Node ---")
1918
  project_json = state["project_json"]
1919
  try:
1920
  updated_project_json = variable_adder_main(project_json)
 
1924
  else:
1925
  print("Variable adder unable to add any variable inside the project!")
1926
  state["project_json"]=project_json
1927
+ state["page_count"] +=1
1928
  return state
1929
  except Exception as e:
1930
  logger.error(f"Error in variable adder node while updating project_json': {e}")
1931
  raise
1932
 
1933
+ # Node 7: variable adder node
1934
+ def processed_page_node(state: GameState):
1935
+ logger.info("--- Processing the Pages Node ---")
1936
+ image = state.get("project_image", "")
1937
+ cnt =state["page_count"]
1938
+ print(f"The page processed for page:--------------> {cnt}")
1939
+ if cnt<len(image):
1940
+ state["processing"]= True
1941
+ else:
1942
+ state["processing"]= False
1943
+ return state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1944
 
1945
  # Prepare manipulated sprite JSON structure
1946
  manipulated_json = {}
 
2165
  # Copy matched backdrop assets + collect
2166
  # =========================================
2167
  backdrop_data = []
2168
+ copied_backdrop_folders = set()
2169
  for backdrop_idx, matched_idx in enumerate(most_similar_indices):
2170
  matched_image_path = folder_image_paths[matched_idx]
2171
  matched_folder = os.path.dirname(matched_image_path)
 
2175
  if not matched_folder.startswith(backdrop_base_path):
2176
  continue
2177
 
2178
+ # skip if backdrop folder already processed
2179
+ if matched_folder in copied_backdrop_folders:
2180
+ continue
2181
+ copied_backdrop_folders.add(matched_folder)
2182
+
2183
  logger.info(f"Matched backdrop: {matched_image_path}")
2184
 
2185
  # 1) Copy the matched backdrop image itself
 
2239
  # then backdrop as the Stage
2240
  if backdrop_data:
2241
  all_costumes, sounds = [], []
2242
+ seen_costumes = set()
2243
  for i, bd in enumerate(backdrop_data):
2244
+ for costume in bd.get("costumes", []):
2245
+ # Create a unique key for the costume
2246
+ key = (costume.get("name"), costume.get("assetId"))
2247
+ if key not in seen_costumes:
2248
+ seen_costumes.add(key)
2249
+ all_costumes.append(costume)
2250
+
2251
  if i == 0:
2252
  sounds = bd.get("sounds", [])
2253
  stage_obj={
 
2330
  json.dump(final_project, f, indent=2)
2331
 
2332
  return project_json_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2333
 
2334
  def convert_pdf_stream_to_images(pdf_stream: io.BytesIO, dpi=300):
2335
  # Ensure we are at the start of the stream
 
2345
 
2346
  def delay_for_tpm_node(state: GameState):
2347
  logger.info("--- Running DelayForTPMNode ---")
2348
+ time.sleep(10) # Adjust the delay as needed
2349
  logger.info("Delay completed.")
2350
  return state
2351
 
 
2365
  workflow.add_node("opcode_counter", plan_opcode_counter_node)
2366
  workflow.add_node("block_builder", overall_block_builder_node_2)
2367
  workflow.add_node("variable_initializer", variable_adder_node)
2368
+ workflow.add_node("page_processed", processed_page_node)
2369
+
2370
+ workflow.set_entry_point("page_processed")
2371
+ # Conditional branching from the start
2372
+ def decide_next_step(state: GameState):
2373
+ if state.get("processing", False):
2374
+ return "pseudo_generator"
2375
+ else:
2376
+ return END
2377
+
2378
+ workflow.add_conditional_edges(
2379
+ "page_processed",
2380
+ decide_next_step,
2381
+ {
2382
+ "pseudo_generator": "pseudo_generator",
2383
+ str(END): END # str(END) is '__end__'
2384
+ }
2385
+ )
2386
+ # Main chain
2387
+ workflow.add_edge("pseudo_generator", "time_delay_1")
2388
+ workflow.add_edge("time_delay_1", "plan_generator")
2389
+ workflow.add_edge("plan_generator", "time_delay_2")
2390
+ workflow.add_edge("time_delay_2", "refined_planner")
2391
+ workflow.add_edge("refined_planner", "time_delay_3")
2392
+ workflow.add_edge("time_delay_3", "opcode_counter")
2393
+ workflow.add_edge("opcode_counter", "block_builder")
2394
+ workflow.add_edge("block_builder", "variable_initializer")
2395
+
2396
+ # After last node, check again
2397
+ workflow.add_edge("variable_initializer", "page_processed")
2398
 
 
 
 
 
 
 
 
 
 
 
 
2399
  app_graph = workflow.compile()
2400
 
2401
  # ============== Helper function to Upscale an Image ============== #
 
2573
  print(f"-----------------------------Execution Time save_pdf_to_generated_dir() : {total_time}-----------------------------\n")
2574
  # }
2575
 
2576
+ # Save uploaded file to disk
2577
+ # pdf_path = os.path.join("/tmp", secure_filename(file.filename))
2578
+ # file.save(pdf_path)
2579
+ # compressed_pages = pdf_to_images_with_size_check(pdf_path, "/tmp/compressed_pages", size_limit_mb=4)
2580
+
2581
  # {
2582
  # Extract & process
2583
  # output_path, result = extract_images_from_pdf(saved_pdf_path)
 
2623
  images = convert_pdf_stream_to_images(pdf_stream, dpi=300)
2624
  else:
2625
  images = convert_from_path(pdf_stream, dpi=300)
2626
+
2627
  #updating logic here [Dev Patel]
2628
+ initial_state_dict = {
2629
+ "project_json": project_skeleton,
2630
+ "description": "The pseudo code for the script",
2631
+ "project_id": project_id,
2632
+ # "project_image": img_b64,
2633
+ "project_image": images,
2634
+ "action_plan": {},
2635
+ "pseudo_code": {},
2636
+ "temporary_node": {},
2637
+ "processing":True,
2638
+ "page_count": 0,
2639
+ "temp_pseudo_code":[],
2640
+ }
2641
 
2642
+ #final_state_dict = app_graph.invoke(initial_state_dict) # Pass dictionary
2643
+ final_state_dict = app_graph.invoke(initial_state_dict,config={"recursion_limit": 200})
2644
+ final_project_json = final_state_dict['project_json'] # Access as dict
2645
+ # final_project_json = project_skeleton
2646
 
2647
  # Save the *final* filled project JSON, overwriting the skeleton
2648
  with open(project_output, "w") as f: