Update app.py
Browse files
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 |
-
|
| 175 |
-
|
| 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 |
"""
|
|
@@ -654,48 +623,124 @@ def extract_json_from_llm_response(raw_response: str) -> dict:
|
|
| 654 |
logger.error("Sanitized JSON still invalid:\n%s", json_string)
|
| 655 |
raise
|
| 656 |
|
| 657 |
-
def clean_base64_for_model(raw_b64):
|
| 658 |
-
|
| 659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
|
| 661 |
-
|
| 662 |
-
- a list of base64 strings → picks the first element
|
| 663 |
-
- a PIL Image instance → encodes to PNG/base64
|
| 664 |
-
- a raw base64 string → strips whitespace and data URI prefix
|
| 665 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
if not raw_b64:
|
| 667 |
-
return ""
|
| 668 |
|
| 669 |
-
# 1. If it’s a list, take its first element
|
| 670 |
if isinstance(raw_b64, list):
|
| 671 |
raw_b64 = raw_b64[0] if raw_b64 else ""
|
| 672 |
if not raw_b64:
|
| 673 |
-
return ""
|
| 674 |
|
| 675 |
-
# 2. If it’s a PIL Image, convert to base64
|
| 676 |
if isinstance(raw_b64, Image.Image):
|
| 677 |
buf = io.BytesIO()
|
| 678 |
raw_b64.save(buf, format="PNG")
|
| 679 |
raw_b64 = base64.b64encode(buf.getvalue()).decode()
|
| 680 |
|
| 681 |
-
# 3. At this point it must be a string
|
| 682 |
if not isinstance(raw_b64, str):
|
| 683 |
raise TypeError(f"Expected base64 string or PIL Image, got {type(raw_b64)}")
|
| 684 |
|
| 685 |
-
#
|
| 686 |
clean_b64 = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", raw_b64)
|
| 687 |
clean_b64 = clean_b64.replace("\n", "").replace("\r", "").strip()
|
| 688 |
|
| 689 |
-
#
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
def format_scratch_pseudo_code(code_string):
|
| 700 |
"""
|
| 701 |
Parses and formats Scratch pseudo-code with correct indentation,
|
|
@@ -746,13 +791,13 @@ def format_scratch_pseudo_code(code_string):
|
|
| 746 |
|
| 747 |
return '\n'.join(formatted_lines)
|
| 748 |
|
| 749 |
-
|
| 750 |
# Node 1: Logic updating if any issue here
|
| 751 |
def pseudo_generator_node(state: GameState):
|
| 752 |
logger.info("--- Running plan_logic_aligner_node ---")
|
| 753 |
image = state.get("project_image", "")
|
| 754 |
project_json = state["project_json"]
|
| 755 |
-
|
|
|
|
| 756 |
# MODIFICATION 1: Include 'Stage' in the list of names to plan for.
|
| 757 |
# It's crucial to ensure 'Stage' is always present for its global role.
|
| 758 |
target_names = [t["name"] for t in project_json["targets"]]
|
|
@@ -897,7 +942,7 @@ If you find any "Code-Blocks" then,
|
|
| 897 |
"type": "image_url",
|
| 898 |
"image_url": {
|
| 899 |
# "url": f"data:image/png;base64,{image}"
|
| 900 |
-
"url": clean_base64_for_model(image)
|
| 901 |
}
|
| 902 |
}
|
| 903 |
|
|
@@ -1852,6 +1897,7 @@ def overall_block_builder_node_2(state: GameState):
|
|
| 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)
|
|
@@ -1861,239 +1907,23 @@ def variable_adder_node(state: GameState):
|
|
| 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}")
|
| 1867 |
raise
|
| 1868 |
|
| 1869 |
-
|
| 1870 |
-
|
| 1871 |
-
|
| 1872 |
-
|
| 1873 |
-
|
| 1874 |
-
|
| 1875 |
-
|
| 1876 |
-
|
| 1877 |
-
|
| 1878 |
-
|
| 1879 |
-
|
| 1880 |
-
# def plan_logic_aligner_node(state: GameState):
|
| 1881 |
-
# logger.info("--- Running plan_logic_aligner_node ---")
|
| 1882 |
-
|
| 1883 |
-
# image = state.get("image", "")
|
| 1884 |
-
|
| 1885 |
-
# refinement_prompt = f"""
|
| 1886 |
-
# You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
|
| 1887 |
-
# "Analyze the Scratch code-block image and generate Pseudo-Code for what this logic appears to be doing."
|
| 1888 |
-
# From Image, you also have to detect a value of Key given in Text form "Script for: ". Below is the example
|
| 1889 |
-
# Example: "Script for: Bear", "Script for:" is a key and "Bear" is value.
|
| 1890 |
-
# --- Scratch 3.0 Block Reference ---
|
| 1891 |
-
# ### Hat Blocks
|
| 1892 |
-
# Description: {hat_description}
|
| 1893 |
-
# Blocks:
|
| 1894 |
-
# {hat_opcodes_functionalities}
|
| 1895 |
-
|
| 1896 |
-
# ### Boolean Blocks
|
| 1897 |
-
# Description: {boolean_description}
|
| 1898 |
-
# Blocks:
|
| 1899 |
-
# {boolean_opcodes_functionalities}
|
| 1900 |
-
|
| 1901 |
-
# ### C Blocks
|
| 1902 |
-
# Description: {c_description}
|
| 1903 |
-
# Blocks:
|
| 1904 |
-
# {c_opcodes_functionalities}
|
| 1905 |
-
|
| 1906 |
-
# ### Cap Blocks
|
| 1907 |
-
# Description: {cap_description}
|
| 1908 |
-
# Blocks:
|
| 1909 |
-
# {cap_opcodes_functionalities}
|
| 1910 |
-
|
| 1911 |
-
# ### Reporter Blocks
|
| 1912 |
-
# Description: {reporter_description}
|
| 1913 |
-
# Blocks:
|
| 1914 |
-
# {reporter_opcodes_functionalities}
|
| 1915 |
-
|
| 1916 |
-
# ### Stack Blocks
|
| 1917 |
-
# Description: {stack_description}
|
| 1918 |
-
# Blocks:
|
| 1919 |
-
# {stack_opcodes_functionalities}
|
| 1920 |
-
# -----------------------------------
|
| 1921 |
-
|
| 1922 |
-
# Your task is to:
|
| 1923 |
-
# If you don't find any "Code-Blocks" then,
|
| 1924 |
-
# **Don't generate Pseudo Code, and pass the message "No Code-blocks" find...
|
| 1925 |
-
# If you find any "Code-Blocks" then,
|
| 1926 |
-
# 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.
|
| 1927 |
-
|
| 1928 |
-
# 2. **Structural requirements**:
|
| 1929 |
-
# - **Numeric values** `(e.g., 0, 5, 0.2, -130)` **must** be in parentheses: `(0)`, `(5)`, `(0.2)`, `(-130)`.
|
| 1930 |
-
# - **AlphaNumeric values** `(e.g., hello, say 5, 4, hi!)` **must** be in parentheses: `(hello)`, `(say 5)`, `(4)`, `(hi!)`.
|
| 1931 |
-
# - **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])`.
|
| 1932 |
-
# - **Dropdown options** must be in the form `[option v]` (e.g., `[Game Start v]`, `[blue sky v]`). example use `when [space v] key pressed`.
|
| 1933 |
-
# - **Reporter blocks** used as inputs must be double‑wrapped: `((x position))`, `((y position))`. example use `if <((y position)) = (-130)> then` or `(((x position)) * (1))`.
|
| 1934 |
-
# - **Boolean blocks** in conditions must be inside `< >`, including nested ones: `<not <condition>>`, `<<cond1> and <cond2>>`,`<<cond1> or <cond2>>`.
|
| 1935 |
-
# - **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]?>`.
|
| 1936 |
-
# - **Operator expressions** must use explicit Scratch operator blocks, e.g.:
|
| 1937 |
-
# ```
|
| 1938 |
-
# (([ballSpeed v]) * (1.1))
|
| 1939 |
-
# ```
|
| 1940 |
-
# - **Every hat block script must end** with a final `end` on its own line.
|
| 1941 |
-
|
| 1942 |
-
# 3. **Pseudo‑code formatting**:
|
| 1943 |
-
# - Represent each block or nested block on its own line.
|
| 1944 |
-
# - Indent nested blocks by 4 spaces under their parent (`forever`, `if`, etc.).
|
| 1945 |
-
# - No comments or explanatory text—just the block sequence.
|
| 1946 |
-
# - 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.
|
| 1947 |
-
|
| 1948 |
-
# 4. **Logic content**:
|
| 1949 |
-
# - Build clear flow for mechanics (movement, jumping, flying, scoring, collisions).
|
| 1950 |
-
# - Match each action closely to a Scratch block or tight sequence.
|
| 1951 |
-
# - Do **NOT** include any justification or comments—only the raw logic.
|
| 1952 |
-
|
| 1953 |
-
# 5. **Examples for reference**:
|
| 1954 |
-
# **Correct** pattern for a simple start script:
|
| 1955 |
-
# ```
|
| 1956 |
-
# when green flag clicked
|
| 1957 |
-
# switch backdrop to [blue sky v]
|
| 1958 |
-
# set [score v] to (0)
|
| 1959 |
-
# show variable [score v]
|
| 1960 |
-
# broadcast [Game Start v]
|
| 1961 |
-
# end
|
| 1962 |
-
# ```
|
| 1963 |
-
# **Correct** pattern for updating the high score variable handling:
|
| 1964 |
-
# ```
|
| 1965 |
-
# when I receive [Game Over v]
|
| 1966 |
-
# if <((score)) > (([High Score v]))> then
|
| 1967 |
-
# set [High Score v] to ([score v])
|
| 1968 |
-
# end
|
| 1969 |
-
# switch backdrop to [Game Over v]
|
| 1970 |
-
# end
|
| 1971 |
-
# ```
|
| 1972 |
-
# **Correct** pattern for level up and increase difficulty use:
|
| 1973 |
-
# ```
|
| 1974 |
-
# when I receive [Level Up v]
|
| 1975 |
-
# change [level v] by (1)
|
| 1976 |
-
# set [ballSpeed v] to ((([ballSpeed v]) * (1.1)))
|
| 1977 |
-
# end
|
| 1978 |
-
# ```
|
| 1979 |
-
# **Correct** pattern for jumping mechanics use:
|
| 1980 |
-
# ```
|
| 1981 |
-
# when [space v] key pressed
|
| 1982 |
-
# if <((y position)) = (-100)> then
|
| 1983 |
-
# repeat (5)
|
| 1984 |
-
# change y by (100)
|
| 1985 |
-
# wait (0.1) seconds
|
| 1986 |
-
# change y by (-100)
|
| 1987 |
-
# wait (0.1) seconds
|
| 1988 |
-
# end
|
| 1989 |
-
# end
|
| 1990 |
-
# end
|
| 1991 |
-
# ```
|
| 1992 |
-
# **Correct** pattern for continuos moving objects use:
|
| 1993 |
-
# ```
|
| 1994 |
-
# when green flag clicked
|
| 1995 |
-
# go to x: (240) y: (-100)
|
| 1996 |
-
# set [speed v] to (-5)
|
| 1997 |
-
# show variable [speed v]
|
| 1998 |
-
# forever
|
| 1999 |
-
# change x by ([speed v])
|
| 2000 |
-
# if <((x position)) < (-240)> then
|
| 2001 |
-
# go to x: (240) y: (-100)
|
| 2002 |
-
# end
|
| 2003 |
-
# end
|
| 2004 |
-
# end
|
| 2005 |
-
# ```
|
| 2006 |
-
# **Correct** pattern for continuos moving objects use:
|
| 2007 |
-
# ```
|
| 2008 |
-
# when green flag clicked
|
| 2009 |
-
# go to x: (240) y: (-100)
|
| 2010 |
-
# set [speed v] to (-5)
|
| 2011 |
-
# show variable [speed v]
|
| 2012 |
-
# forever
|
| 2013 |
-
# change x by ([speed v])
|
| 2014 |
-
# if <((x position)) < (-240)> then
|
| 2015 |
-
# go to x: (240) y: (-100)
|
| 2016 |
-
# end
|
| 2017 |
-
# end
|
| 2018 |
-
# end
|
| 2019 |
-
# ```
|
| 2020 |
-
# 6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
|
| 2021 |
-
# 7. **Output**:
|
| 2022 |
-
# Return **only** a JSON object, using double quotes everywhere:
|
| 2023 |
-
# ```json
|
| 2024 |
-
# {{
|
| 2025 |
-
# "refined_logic":{{
|
| 2026 |
-
# "name_variable": 'Value of "Sript for: "',
|
| 2027 |
-
# "pseudocode":"…your fully‑formatted pseudo‑code here…",
|
| 2028 |
-
# }}
|
| 2029 |
-
# }}
|
| 2030 |
-
# ```
|
| 2031 |
-
# """
|
| 2032 |
-
# image_input = {
|
| 2033 |
-
# "type": "image_url",
|
| 2034 |
-
# "image_url": {
|
| 2035 |
-
# "url": f"data:image/png;base64,{image}"
|
| 2036 |
-
# }
|
| 2037 |
-
# }
|
| 2038 |
-
|
| 2039 |
-
# content = [
|
| 2040 |
-
# {"type": "text", "text": refinement_prompt},
|
| 2041 |
-
# image_input
|
| 2042 |
-
# ]
|
| 2043 |
-
|
| 2044 |
-
# try:
|
| 2045 |
-
# # Invoke the main agent for logic refinement and relationship identification
|
| 2046 |
-
# response = agent.invoke({"messages": [{"role": "user", "content": content}]})
|
| 2047 |
-
# llm_output_raw = response["messages"][-1].content.strip()
|
| 2048 |
-
|
| 2049 |
-
# parsed_llm_output = extract_json_from_llm_response(llm_output_raw)
|
| 2050 |
-
|
| 2051 |
-
# # result = parsed_llm_output
|
| 2052 |
-
# # Extract needed values directly
|
| 2053 |
-
# logic_data = parsed_llm_output.get("refined_logic", {})
|
| 2054 |
-
# name_variable = logic_data.get("name_variable", "Unknown")
|
| 2055 |
-
# pseudocode = logic_data.get("pseudocode", "No logic extracted")
|
| 2056 |
-
|
| 2057 |
-
# result = {"pseudo_node": {
|
| 2058 |
-
# "name_variable": name_variable,
|
| 2059 |
-
# "pseudocode": pseudocode
|
| 2060 |
-
# }}
|
| 2061 |
-
|
| 2062 |
-
# print(f"result:\n\n {result}")
|
| 2063 |
-
# return result
|
| 2064 |
-
# except Exception as e:
|
| 2065 |
-
# logger.error(f"❌ plan_logic_aligner_node failed: {str(e)}")
|
| 2066 |
-
# return {"error": str(e)}
|
| 2067 |
-
# except json.JSONDecodeError as error_json:
|
| 2068 |
-
# # If JSON parsing fails, use the json resolver agent
|
| 2069 |
-
# correction_prompt = (
|
| 2070 |
-
# "Your task is to correct the provided JSON string to ensure it is **syntactically perfect and adheres strictly to JSON rules**.\n"
|
| 2071 |
-
# "It must be a JSON object with `refined_logic` (string) and `block_relationships` (array of objects).\n"
|
| 2072 |
-
# f"- **Error Details**: {error_json}\n\n"
|
| 2073 |
-
# "**Strict Instructions for your response:**\n"
|
| 2074 |
-
# "1. **ONLY** output the corrected JSON. Do not include any other text or explanations.\n"
|
| 2075 |
-
# "2. Ensure all keys and string values are enclosed in **double quotes**. Escape internal quotes (`\\`).\n"
|
| 2076 |
-
# "3. No trailing commas. Correct nesting.\n\n"
|
| 2077 |
-
# "Here is the problematic JSON string to correct:\n"
|
| 2078 |
-
# f"```json\n{llm_output_raw}\n```\n"
|
| 2079 |
-
# "Corrected JSON:\n"
|
| 2080 |
-
# )
|
| 2081 |
-
# try:
|
| 2082 |
-
# correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
|
| 2083 |
-
# corrected_output = extract_json_from_llm_response(correction_response["messages"][-1].content)
|
| 2084 |
-
|
| 2085 |
-
# result = {
|
| 2086 |
-
# #"image_path": image_path,
|
| 2087 |
-
# "pseudo_code": corrected_output
|
| 2088 |
-
# }
|
| 2089 |
-
|
| 2090 |
-
# return result
|
| 2091 |
-
|
| 2092 |
-
# except Exception as e_corr:
|
| 2093 |
-
# logger.error(f"Failed to correct JSON output for even after retry: {e_corr}")
|
| 2094 |
-
|
| 2095 |
-
#def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path, image_base_dir: Path):
|
| 2096 |
-
#def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path):
|
| 2097 |
|
| 2098 |
# Prepare manipulated sprite JSON structure
|
| 2099 |
manipulated_json = {}
|
|
@@ -2471,145 +2301,6 @@ def similarity_matching(sprites_data: str, project_folder: str) -> str:
|
|
| 2471 |
json.dump(final_project, f, indent=2)
|
| 2472 |
|
| 2473 |
return project_json_path
|
| 2474 |
-
# for sprite_idx, matched_idx in enumerate(most_similar_indices):
|
| 2475 |
-
# matched_image_path = folder_image_paths[matched_idx]
|
| 2476 |
-
# matched_image_path = os.path.normpath(matched_image_path)
|
| 2477 |
-
# print(" --------------------------------------1- matched_image_path ---------------------------------------",matched_image_path)
|
| 2478 |
-
# matched_folder = os.path.dirname(matched_image_path)
|
| 2479 |
-
# #folder_name = os.path.basename(matched_folder)
|
| 2480 |
-
# print(" --------------------------------------1- matched_folder ---------------------------------------",matched_folder)
|
| 2481 |
-
# if matched_folder in copied_folders:
|
| 2482 |
-
# continue
|
| 2483 |
-
# copied_folders.add(matched_folder)
|
| 2484 |
-
# logger.info(f"Matched image path: {matched_image_path}")
|
| 2485 |
-
|
| 2486 |
-
# sprite_json_path = os.path.join(matched_folder, 'sprite.json')
|
| 2487 |
-
# print(" --------------------------------------- sprite_json_path ---------------------------------------",sprite_json_path)
|
| 2488 |
-
# if not os.path.exists(sprite_json_path):
|
| 2489 |
-
# logger.warning(f"sprite.json not found in: {matched_folder}")
|
| 2490 |
-
# continue
|
| 2491 |
-
|
| 2492 |
-
# with open(sprite_json_path, 'r') as f:
|
| 2493 |
-
# sprite_data = json.load(f)
|
| 2494 |
-
# # print(f"SPRITE DATA: \n{sprite_data}")
|
| 2495 |
-
# # # Copy only non-matched files
|
| 2496 |
-
# # for fname in os.listdir(matched_folder):
|
| 2497 |
-
# # fpath = os.path.join(matched_folder, fname)
|
| 2498 |
-
# # if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'sprite.json'}:
|
| 2499 |
-
# # shutil.copy2(fpath, os.path.join(project_folder, fname))
|
| 2500 |
-
# # # logger.info(f"Copied Sprite asset: {fname}")
|
| 2501 |
-
# project_data.append(sprite_data)
|
| 2502 |
-
# print(" --------------------------------------1- project_data ---------------------------------------",project_data)
|
| 2503 |
-
# for fname in os.listdir(matched_folder):
|
| 2504 |
-
# fpath = os.path.join(matched_folder, fname)
|
| 2505 |
-
# dest_path = os.path.join(project_folder, fname)
|
| 2506 |
-
# if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'sprite.json'}:
|
| 2507 |
-
# shutil.copy2(fpath, dest_path)
|
| 2508 |
-
# logger.info(f"🟢 Copied Sprite Asset: {fpath} → {dest_path}")
|
| 2509 |
-
|
| 2510 |
-
# # ================================================================== #
|
| 2511 |
-
# # Loop through most similar images from Backdrops folder #
|
| 2512 |
-
# # → Copy Backdrop assets (excluding matched image + project.json) #
|
| 2513 |
-
# # → Load project.json and append its data to project_data #
|
| 2514 |
-
# # ================================================================== #
|
| 2515 |
-
# backdrop_data = [] # for backdrop-related entries
|
| 2516 |
-
|
| 2517 |
-
# for backdrop_idx, matched_idx in enumerate(most_similar_indices):
|
| 2518 |
-
# matched_image_path = os.path.normpath(folder_image_paths[matched_idx])
|
| 2519 |
-
# print(" --------------------------------------2- matched_image_path ---------------------------------------",matched_image_path)
|
| 2520 |
-
# # Check if the match is from the Backdrops folder
|
| 2521 |
-
# if matched_image_path.startswith(os.path.normpath(backdrop_images_path)):
|
| 2522 |
-
# matched_folder = os.path.dirname(matched_image_path)
|
| 2523 |
-
# print(" --------------------------------------2- matched_folder ---------------------------------------",matched_folder)
|
| 2524 |
-
# folder_name = os.path.basename(matched_folder)
|
| 2525 |
-
|
| 2526 |
-
# logger.info(f"Backdrop matched image: {matched_image_path}")
|
| 2527 |
-
|
| 2528 |
-
# # Copy only non-matched files
|
| 2529 |
-
# # for fname in os.listdir(matched_folder):
|
| 2530 |
-
# # fpath = os.path.join(matched_folder, fname)
|
| 2531 |
-
# # if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'project.json'}:
|
| 2532 |
-
# # shutil.copy2(fpath, os.path.join(project_folder, fname))
|
| 2533 |
-
# # # logger.info(f"Copied Backdrop asset: {fname}")
|
| 2534 |
-
# for fname in os.listdir(matched_folder):
|
| 2535 |
-
# fpath = os.path.join(matched_folder, fname)
|
| 2536 |
-
# dest_path = os.path.join(project_folder, fname)
|
| 2537 |
-
# if os.path.isfile(fpath) and fname not in {os.path.basename(matched_image_path), 'project.json'}:
|
| 2538 |
-
# shutil.copy2(fpath, dest_path)
|
| 2539 |
-
# logger.info(f"🟡 Copied Backdrop Asset: {fpath} → {dest_path}")
|
| 2540 |
-
|
| 2541 |
-
|
| 2542 |
-
# # Append backdrop's project.json
|
| 2543 |
-
# backdrop_json_path = os.path.join(matched_folder, 'project.json')
|
| 2544 |
-
# print(" --------------------------------------2- backdrop_json_path ---------------------------------------",backdrop_json_path)
|
| 2545 |
-
# if os.path.exists(backdrop_json_path):
|
| 2546 |
-
# with open(backdrop_json_path, 'r') as f:
|
| 2547 |
-
# backdrop_json_data = json.load(f)
|
| 2548 |
-
# # print(f"SPRITE DATA: \n{backdrop_json_data}")
|
| 2549 |
-
# if "targets" in backdrop_json_data:
|
| 2550 |
-
# for target in backdrop_json_data["targets"]:
|
| 2551 |
-
# if target.get("isStage") == True:
|
| 2552 |
-
# backdrop_data.append(target)
|
| 2553 |
-
# else:
|
| 2554 |
-
# logger.warning(f"project.json not found in: {matched_folder}")
|
| 2555 |
-
|
| 2556 |
-
# # Merge JSON structure
|
| 2557 |
-
# final_project = {
|
| 2558 |
-
# "targets": [],
|
| 2559 |
-
# "monitors": [],
|
| 2560 |
-
# "extensions": [],
|
| 2561 |
-
# "meta": {
|
| 2562 |
-
# "semver": "3.0.0",
|
| 2563 |
-
# "vm": "11.3.0",
|
| 2564 |
-
# "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
| 2565 |
-
# }
|
| 2566 |
-
# }
|
| 2567 |
-
|
| 2568 |
-
# for sprite in project_data:
|
| 2569 |
-
# if not sprite.get("isStage", False):
|
| 2570 |
-
# final_project["targets"].append(sprite)
|
| 2571 |
-
|
| 2572 |
-
# if backdrop_data:
|
| 2573 |
-
# all_costumes, sounds = [], []
|
| 2574 |
-
# for idx, bd in enumerate(backdrop_data):
|
| 2575 |
-
# all_costumes.extend(bd.get("costumes", []))
|
| 2576 |
-
# if idx == 0 and "sounds" in bd:
|
| 2577 |
-
# sounds = bd["sounds"]
|
| 2578 |
-
# final_project["targets"].append({
|
| 2579 |
-
# "isStage": True,
|
| 2580 |
-
# "name": "Stage",
|
| 2581 |
-
# "variables": {},
|
| 2582 |
-
# "lists": {},
|
| 2583 |
-
# "broadcasts": {},
|
| 2584 |
-
# "blocks": {},
|
| 2585 |
-
# "comments": {},
|
| 2586 |
-
# "currentCostume": 1 if len(all_costumes) > 1 else 0,
|
| 2587 |
-
# "costumes": all_costumes,
|
| 2588 |
-
# "sounds": sounds,
|
| 2589 |
-
# "volume": 100,
|
| 2590 |
-
# "layerOrder": 0,
|
| 2591 |
-
# "tempo": 60,
|
| 2592 |
-
# "videoTransparency": 50,
|
| 2593 |
-
# "videoState": "on",
|
| 2594 |
-
# "textToSpeechLanguage": None
|
| 2595 |
-
# })
|
| 2596 |
-
|
| 2597 |
-
# with open(project_json_path, 'w') as f:
|
| 2598 |
-
# json.dump(final_project, f, indent=2)
|
| 2599 |
-
|
| 2600 |
-
# # logger.info(f"🎉 Final project saved: {project_json_path}")
|
| 2601 |
-
# return project_json_path
|
| 2602 |
-
|
| 2603 |
-
# def convert_bytes_to_image(pdf_bytes: bytes, dpi: int):
|
| 2604 |
-
# images = convert_from_bytes(pdf_bytes, dpi=dpi, poppler_path=poppler_path)
|
| 2605 |
-
# # Save each page to an in-memory BytesIO and return a list of BytesIOs
|
| 2606 |
-
# buffers = []
|
| 2607 |
-
# for img in images:
|
| 2608 |
-
# buf = BytesIO()
|
| 2609 |
-
# img.save(buf, format="PNG")
|
| 2610 |
-
# buf.seek(0)
|
| 2611 |
-
# buffers.append(buf)
|
| 2612 |
-
# return buffers
|
| 2613 |
|
| 2614 |
def convert_pdf_stream_to_images(pdf_stream: io.BytesIO, dpi=300):
|
| 2615 |
# Ensure we are at the start of the stream
|
|
@@ -2625,7 +2316,7 @@ def convert_pdf_stream_to_images(pdf_stream: io.BytesIO, dpi=300):
|
|
| 2625 |
|
| 2626 |
def delay_for_tpm_node(state: GameState):
|
| 2627 |
logger.info("--- Running DelayForTPMNode ---")
|
| 2628 |
-
time.sleep(
|
| 2629 |
logger.info("Delay completed.")
|
| 2630 |
return state
|
| 2631 |
|
|
@@ -2645,18 +2336,37 @@ workflow.add_node("refined_planner", refined_planner_node) # Refines the action
|
|
| 2645 |
workflow.add_node("opcode_counter", plan_opcode_counter_node)
|
| 2646 |
workflow.add_node("block_builder", overall_block_builder_node_2)
|
| 2647 |
workflow.add_node("variable_initializer", variable_adder_node)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2648 |
|
| 2649 |
-
workflow.set_entry_point("pseudo_generator")
|
| 2650 |
-
workflow.add_edge("pseudo_generator","time_delay_1")
|
| 2651 |
-
workflow.add_edge("time_delay_1","plan_generator")
|
| 2652 |
-
workflow.add_edge("plan_generator","time_delay_2")
|
| 2653 |
-
# workflow.add_edge("time_delay_2",END)
|
| 2654 |
-
workflow.add_edge("time_delay_2","refined_planner")
|
| 2655 |
-
workflow.add_edge("refined_planner","time_delay_3")
|
| 2656 |
-
workflow.add_edge("time_delay_3","opcode_counter")
|
| 2657 |
-
workflow.add_edge("opcode_counter","block_builder")
|
| 2658 |
-
workflow.add_edge("block_builder","variable_initializer")
|
| 2659 |
-
workflow.add_edge("variable_initializer", END)
|
| 2660 |
app_graph = workflow.compile()
|
| 2661 |
|
| 2662 |
# ============== Helper function to Upscale an Image ============== #
|
|
@@ -2758,56 +2468,6 @@ def save_pdf_to_generated_dir(pdf_stream: io.BytesIO, project_id: str) -> str:
|
|
| 2758 |
logger.error(f"Failed to save PDF to generated dir: {e}", exc_info=True)
|
| 2759 |
return None
|
| 2760 |
|
| 2761 |
-
# def pdf_to_images_with_size_check(pdf_path, output_dir, size_limit_mb=4):
|
| 2762 |
-
# os.makedirs(output_dir, exist_ok=True)
|
| 2763 |
-
|
| 2764 |
-
# # Convert PDF to images
|
| 2765 |
-
# images = convert_from_path(pdf_path, dpi=300) # 300 DPI keeps quality
|
| 2766 |
-
# saved_files = []
|
| 2767 |
-
|
| 2768 |
-
# for i, img in enumerate(images, start=1):
|
| 2769 |
-
# output_path = os.path.join(output_dir, f"page_{i}.jpg")
|
| 2770 |
-
|
| 2771 |
-
# # Save to memory first to check size
|
| 2772 |
-
# img_bytes = io.BytesIO()
|
| 2773 |
-
# img.save(img_bytes, format="JPEG", quality=95) # near-lossless
|
| 2774 |
-
# size_mb = len(img_bytes.getvalue()) / (1024 * 1024)
|
| 2775 |
-
|
| 2776 |
-
# if size_mb > size_limit_mb:
|
| 2777 |
-
# print(f"Page {i}: {size_mb:.2f} MB → compressing...")
|
| 2778 |
-
# # Compress until under size limit
|
| 2779 |
-
# quality = 95
|
| 2780 |
-
# while size_mb > size_limit_mb and quality > 70: # don't go below 70
|
| 2781 |
-
# img_bytes = io.BytesIO()
|
| 2782 |
-
# img.save(img_bytes, format="JPEG", quality=quality)
|
| 2783 |
-
# size_mb = len(img_bytes.getvalue()) / (1024 * 1024)
|
| 2784 |
-
# quality -= 5
|
| 2785 |
-
# else:
|
| 2786 |
-
# print(f"Page {i}: {size_mb:.2f} MB → no compression needed.")
|
| 2787 |
-
|
| 2788 |
-
# # Write final image to disk
|
| 2789 |
-
# with open(output_path, "wb") as f:
|
| 2790 |
-
# f.write(img_bytes.getvalue())
|
| 2791 |
-
|
| 2792 |
-
# saved_files.append(output_path)
|
| 2793 |
-
|
| 2794 |
-
# return saved_files
|
| 2795 |
-
def compress_image_if_needed(image, max_size_mb=4, quality=85):
|
| 2796 |
-
"""
|
| 2797 |
-
Compress the given PIL Image if its size is greater than max_size_mb.
|
| 2798 |
-
Returns the (possibly compressed) image object.
|
| 2799 |
-
"""
|
| 2800 |
-
temp_path = "/tmp/temp_compression_check.jpg"
|
| 2801 |
-
image.save(temp_path, format="JPEG", quality=95) # save original temporarily
|
| 2802 |
-
size_mb = os.path.getsize(temp_path) / (1024 * 1024)
|
| 2803 |
-
|
| 2804 |
-
if size_mb > max_size_mb:
|
| 2805 |
-
# Compress by reducing quality
|
| 2806 |
-
image.save(temp_path, format="JPEG", quality=quality, optimize=True)
|
| 2807 |
-
print(f"Image compressed from {size_mb:.2f} MB to {os.path.getsize(temp_path)/(1024*1024):.2f} MB")
|
| 2808 |
-
return Image.open(temp_path)
|
| 2809 |
-
return image
|
| 2810 |
-
|
| 2811 |
@app.route('/')
|
| 2812 |
def index():
|
| 2813 |
return render_template('app_index.html')
|
|
@@ -2934,12 +2594,6 @@ def process_pdf():
|
|
| 2934 |
images = convert_pdf_stream_to_images(pdf_stream, dpi=300)
|
| 2935 |
else:
|
| 2936 |
images = convert_from_path(pdf_stream, dpi=300)
|
| 2937 |
-
|
| 2938 |
-
# Compress images if needed
|
| 2939 |
-
compressed_images = []
|
| 2940 |
-
for img in images:
|
| 2941 |
-
compressed_images.append(compress_image_if_needed(img, max_size_mb=4, quality=85))
|
| 2942 |
-
images = compressed_images
|
| 2943 |
|
| 2944 |
#updating logic here [Dev Patel]
|
| 2945 |
initial_state_dict = {
|
|
@@ -2951,6 +2605,10 @@ def process_pdf():
|
|
| 2951 |
"action_plan": {},
|
| 2952 |
"pseudo_code": {},
|
| 2953 |
"temporary_node": {},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2954 |
}
|
| 2955 |
|
| 2956 |
final_state_dict = app_graph.invoke(initial_state_dict) # Pass dictionary
|
|
|
|
| 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 |
"""
|
|
|
|
| 623 |
logger.error("Sanitized JSON still invalid:\n%s", json_string)
|
| 624 |
raise
|
| 625 |
|
| 626 |
+
# def clean_base64_for_model(raw_b64):
|
| 627 |
+
# """
|
| 628 |
+
# Normalize input into a valid data:image/png;base64,<payload> string.
|
| 629 |
+
|
| 630 |
+
# Accepts:
|
| 631 |
+
# - a list of base64 strings → picks the first element
|
| 632 |
+
# - a PIL Image instance → encodes to PNG/base64
|
| 633 |
+
# - a raw base64 string → strips whitespace and data URI prefix
|
| 634 |
+
# """
|
| 635 |
+
# if not raw_b64:
|
| 636 |
+
# return ""
|
| 637 |
+
|
| 638 |
+
# # 1. If it’s a list, take its first element
|
| 639 |
+
# if isinstance(raw_b64, list):
|
| 640 |
+
# raw_b64 = raw_b64[0] if raw_b64 else ""
|
| 641 |
+
# if not raw_b64:
|
| 642 |
+
# return ""
|
| 643 |
+
|
| 644 |
+
# # 2. If it’s a PIL Image, convert to base64
|
| 645 |
+
# if isinstance(raw_b64, Image.Image):
|
| 646 |
+
# buf = io.BytesIO()
|
| 647 |
+
# raw_b64.save(buf, format="PNG")
|
| 648 |
+
# raw_b64 = base64.b64encode(buf.getvalue()).decode()
|
| 649 |
+
|
| 650 |
+
# # 3. At this point it must be a string
|
| 651 |
+
# if not isinstance(raw_b64, str):
|
| 652 |
+
# raise TypeError(f"Expected base64 string or PIL Image, got {type(raw_b64)}")
|
| 653 |
+
|
| 654 |
+
# # 4. Strip any existing data URI prefix, whitespace, or newlines
|
| 655 |
+
# clean_b64 = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", raw_b64)
|
| 656 |
+
# clean_b64 = clean_b64.replace("\n", "").replace("\r", "").strip()
|
| 657 |
+
|
| 658 |
+
# # 5. Validate it’s proper base64
|
| 659 |
+
# try:
|
| 660 |
+
# base64.b64decode(clean_b64)
|
| 661 |
+
# except Exception as e:
|
| 662 |
+
# logger.error(f"Invalid Base64 passed to model: {e}")
|
| 663 |
+
# raise
|
| 664 |
+
|
| 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.
|
| 671 |
+
Returns the final base64 string and its size in KB.
|
| 672 |
+
"""
|
| 673 |
+
import re, base64
|
| 674 |
+
from io import BytesIO
|
| 675 |
+
from PIL import Image
|
| 676 |
+
|
| 677 |
+
# Remove the data URI prefix
|
| 678 |
+
base64_data = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", clean_b64_str)
|
| 679 |
+
image_data = base64.b64decode(base64_data)
|
| 680 |
+
|
| 681 |
+
# Load into PIL
|
| 682 |
+
img = Image.open(BytesIO(image_data))
|
| 683 |
+
|
| 684 |
+
low, high = 20, 95 # reasonable JPEG quality range
|
| 685 |
+
best_b64 = None
|
| 686 |
+
best_size_kb = 0
|
| 687 |
+
|
| 688 |
+
while low <= high:
|
| 689 |
+
mid = (low + high) // 2
|
| 690 |
+
buffer = BytesIO()
|
| 691 |
+
img.save(buffer, format="JPEG", quality=mid)
|
| 692 |
+
size_kb = len(buffer.getvalue()) / 1024
|
| 693 |
+
|
| 694 |
+
if size_kb <= max_kb:
|
| 695 |
+
# This quality is valid, try higher
|
| 696 |
+
best_b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
|
| 697 |
+
best_size_kb = size_kb
|
| 698 |
+
low = mid + 1
|
| 699 |
+
else:
|
| 700 |
+
# Too big, try lower
|
| 701 |
+
high = mid - 1
|
| 702 |
+
|
| 703 |
+
return f"data:image/jpeg;base64,{best_b64}"
|
| 704 |
+
|
| 705 |
+
#clean the base64 model here
|
| 706 |
+
def clean_base64_for_model(raw_b64):
|
| 707 |
+
import io, base64, re
|
| 708 |
+
from PIL import Image
|
| 709 |
+
|
| 710 |
if not raw_b64:
|
| 711 |
+
return "", ""
|
| 712 |
|
|
|
|
| 713 |
if isinstance(raw_b64, list):
|
| 714 |
raw_b64 = raw_b64[0] if raw_b64 else ""
|
| 715 |
if not raw_b64:
|
| 716 |
+
return "", ""
|
| 717 |
|
|
|
|
| 718 |
if isinstance(raw_b64, Image.Image):
|
| 719 |
buf = io.BytesIO()
|
| 720 |
raw_b64.save(buf, format="PNG")
|
| 721 |
raw_b64 = base64.b64encode(buf.getvalue()).decode()
|
| 722 |
|
|
|
|
| 723 |
if not isinstance(raw_b64, str):
|
| 724 |
raise TypeError(f"Expected base64 string or PIL Image, got {type(raw_b64)}")
|
| 725 |
|
| 726 |
+
# Remove data URI prefix if present
|
| 727 |
clean_b64 = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", raw_b64)
|
| 728 |
clean_b64 = clean_b64.replace("\n", "").replace("\r", "").strip()
|
| 729 |
|
| 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)
|
| 737 |
+
clean_b64_2 = clean_b64_2.replace("\n", "").replace("\r", "").strip()
|
| 738 |
+
reduced_size = len(clean_b64_2.encode("utf-8"))
|
| 739 |
+
print(f"Reduced Base64 size (bytes): {reduced_size}")
|
| 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"]]
|
|
|
|
| 942 |
"type": "image_url",
|
| 943 |
"image_url": {
|
| 944 |
# "url": f"data:image/png;base64,{image}"
|
| 945 |
+
"url": clean_base64_for_model(image[cnt])
|
| 946 |
}
|
| 947 |
}
|
| 948 |
|
|
|
|
| 1897 |
|
| 1898 |
# Node 6: variable adder node
|
| 1899 |
def variable_adder_node(state: GameState):
|
| 1900 |
+
logger.info("--- Running Variable Adder Node ---")
|
| 1901 |
project_json = state["project_json"]
|
| 1902 |
try:
|
| 1903 |
updated_project_json = variable_adder_main(project_json)
|
|
|
|
| 1907 |
else:
|
| 1908 |
print("Variable adder unable to add any variable inside the project!")
|
| 1909 |
state["project_json"]=project_json
|
| 1910 |
+
state["page_count"] +=1
|
| 1911 |
return state
|
| 1912 |
except Exception as e:
|
| 1913 |
logger.error(f"Error in variable adder node while updating project_json': {e}")
|
| 1914 |
raise
|
| 1915 |
|
| 1916 |
+
# Node 7: variable adder node
|
| 1917 |
+
def processed_page_node(state: GameState):
|
| 1918 |
+
logger.info("--- Processing the Pages Node ---")
|
| 1919 |
+
image = state.get("project_image", "")
|
| 1920 |
+
cnt =state["page_count"]
|
| 1921 |
+
print(f"The page processed for page:--------------> {cnt}")
|
| 1922 |
+
if cnt<len(image):
|
| 1923 |
+
state["processing"]= True
|
| 1924 |
+
else:
|
| 1925 |
+
state["processing"]= False
|
| 1926 |
+
return state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1927 |
|
| 1928 |
# Prepare manipulated sprite JSON structure
|
| 1929 |
manipulated_json = {}
|
|
|
|
| 2301 |
json.dump(final_project, f, indent=2)
|
| 2302 |
|
| 2303 |
return project_json_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2304 |
|
| 2305 |
def convert_pdf_stream_to_images(pdf_stream: io.BytesIO, dpi=300):
|
| 2306 |
# Ensure we are at the start of the stream
|
|
|
|
| 2316 |
|
| 2317 |
def delay_for_tpm_node(state: GameState):
|
| 2318 |
logger.info("--- Running DelayForTPMNode ---")
|
| 2319 |
+
time.sleep(10) # Adjust the delay as needed
|
| 2320 |
logger.info("Delay completed.")
|
| 2321 |
return state
|
| 2322 |
|
|
|
|
| 2336 |
workflow.add_node("opcode_counter", plan_opcode_counter_node)
|
| 2337 |
workflow.add_node("block_builder", overall_block_builder_node_2)
|
| 2338 |
workflow.add_node("variable_initializer", variable_adder_node)
|
| 2339 |
+
workflow.add_node("page_processed", processed_page_node)
|
| 2340 |
+
|
| 2341 |
+
workflow.set_entry_point("page_processed")
|
| 2342 |
+
# Conditional branching from the start
|
| 2343 |
+
def decide_next_step(state: GameState):
|
| 2344 |
+
if state.get("processing", False):
|
| 2345 |
+
return "pseudo_generator"
|
| 2346 |
+
else:
|
| 2347 |
+
return END
|
| 2348 |
+
|
| 2349 |
+
workflow.add_conditional_edges(
|
| 2350 |
+
"page_processed",
|
| 2351 |
+
decide_next_step,
|
| 2352 |
+
{
|
| 2353 |
+
"pseudo_generator": "pseudo_generator",
|
| 2354 |
+
"END": END
|
| 2355 |
+
}
|
| 2356 |
+
)
|
| 2357 |
+
# Main chain
|
| 2358 |
+
workflow.add_edge("pseudo_generator", "time_delay_1")
|
| 2359 |
+
workflow.add_edge("time_delay_1", "plan_generator")
|
| 2360 |
+
workflow.add_edge("plan_generator", "time_delay_2")
|
| 2361 |
+
workflow.add_edge("time_delay_2", "refined_planner")
|
| 2362 |
+
workflow.add_edge("refined_planner", "time_delay_3")
|
| 2363 |
+
workflow.add_edge("time_delay_3", "opcode_counter")
|
| 2364 |
+
workflow.add_edge("opcode_counter", "block_builder")
|
| 2365 |
+
workflow.add_edge("block_builder", "variable_initializer")
|
| 2366 |
+
|
| 2367 |
+
# After last node, check again
|
| 2368 |
+
workflow.add_edge("variable_initializer", "page_processed")
|
| 2369 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2370 |
app_graph = workflow.compile()
|
| 2371 |
|
| 2372 |
# ============== Helper function to Upscale an Image ============== #
|
|
|
|
| 2468 |
logger.error(f"Failed to save PDF to generated dir: {e}", exc_info=True)
|
| 2469 |
return None
|
| 2470 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2471 |
@app.route('/')
|
| 2472 |
def index():
|
| 2473 |
return render_template('app_index.html')
|
|
|
|
| 2594 |
images = convert_pdf_stream_to_images(pdf_stream, dpi=300)
|
| 2595 |
else:
|
| 2596 |
images = convert_from_path(pdf_stream, dpi=300)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2597 |
|
| 2598 |
#updating logic here [Dev Patel]
|
| 2599 |
initial_state_dict = {
|
|
|
|
| 2605 |
"action_plan": {},
|
| 2606 |
"pseudo_code": {},
|
| 2607 |
"temporary_node": {},
|
| 2608 |
+
"temporary_node": {},
|
| 2609 |
+
"processing":True,
|
| 2610 |
+
"page_count": 0,
|
| 2611 |
+
"temp_pseudo_code":{},
|
| 2612 |
}
|
| 2613 |
|
| 2614 |
final_state_dict = app_graph.invoke(initial_state_dict) # Pass dictionary
|