prthm11 commited on
Commit
7f63ea1
·
verified ·
1 Parent(s): 3170067

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +491 -492
app.py CHANGED
@@ -136,126 +136,126 @@ for d in (
136
  # pseudo_code: dict
137
  # action_plan: Optional[Dict]
138
  # temporary_node: Optional[Dict]
139
- class GameState(TypedDict):
140
- image: str
141
- pseudo_node: Optional[Dict]
142
 
143
- # Refined SYSTEM_PROMPT with more explicit Scratch JSON rules, especially for variables
144
- SYSTEM_PROMPT = """
145
- You are an expert AI assistant named GameScratchAgent, specialized in generating and modifying Scratch-VM 3.x game project JSON.
146
- Your core task is to process game descriptions and existing Scratch JSON structures, then produce or update JSON segments accurately.
147
- You possess deep knowledge of Scratch 3.0 project schema, informed by comprehensive reference materials. When generating or modifying the `blocks` section, pay extremely close attention to the following:
148
-
149
- **Scratch Project JSON Schema Rules:**
150
-
151
- 1. **Target Structure (`project.json`'s `targets` array):**
152
- * Each object in the `targets` array represents a Stage or a Sprite.
153
- * `isStage`: A boolean indicating if the target is the Stage (`true`) or a Sprite (`false`).
154
- * `name`: The name of the Stage (e.g., `"Stage"`) or the Sprite (e.g., `"Cat"`). This property replaces `objName` found in older Scratch versions.
155
- * `variables` dictionary: This dictionary maps unique variable IDs to arrays `[variable_name, initial_value, isCloudVariable?]`.
156
- * `variable_name`: The user-defined name of the variable.
157
- * `initial_value`: The variable's initial value, which can be a number or a string.
158
- * `isCloudVariable?`: (Optional) A boolean indicating if it's a cloud variable (`true`) or a local variable (`false` or absent for regular variables).
159
- * Example: `"myVarId123": ["score", 0]`, `"cloudVarId456": ["☁ High Score", "54", true]`
160
- * `lists` dictionary: This dictionary maps unique list IDs to arrays `[list_name, [item1, item2, ...]]`.
161
- * Example: `"myListId789": ["my list", ["apple", "banana"]]`
162
- * `broadcasts` dictionary: This dictionary maps unique broadcast IDs to their names.
163
- * Example: `"myBroadcastId": "Game Over"`
164
- * `blocks` dictionary: This dictionary contains all the blocks belonging to this target. Keys are block IDs, values are block objects.
165
-
166
- 2. **Block Structure (within a `target`'s `blocks` dictionary):**
167
- * Every block object must have the following core properties:
168
- * [cite_start]`opcode`: A unique internal identifier for the block's specific functionality (e.g., `"motion_movesteps"`, `"event_whenflagclicked"`)[cite: 31, 18, 439, 452].
169
- * `parent`: The ID of the block directly above it in the script stack (or `null` for a top-level block).
170
- * `next`: The ID of the block directly below it in the script stack (or `null` for the end of a stack).
171
- * `inputs`: An object defining values or blocks plugged into the block's input slots. Values are **arrays**.
172
- * `fields`: An object defining dropdown menu selections or direct internal values within the block. Values are **arrays**.
173
- * `shadow`: `true` if it's a shadow block (e.g., a default number input that can be replaced by another block), `false` otherwise.
174
- * `topLevel`: `true` if it's a hat block or a standalone block (not connected to a parent), `false` otherwise.
175
-
176
- 3. **`inputs` Property Details (for blocks plugged into input slots):**
177
- * **Direct Block Connection (Reporter/Boolean block plugged in):**
178
- * Format: `"<INPUT_NAME>": [1, "<blockId_of_plugged_block>"]`
179
- * Example: `"CONDITION": [1, "someBooleanBlockId"]` (e.g., for an `if` block).
180
- * **Literal Value Input (Shadow block with a literal):**
181
- * Format: `"<INPUT_NAME>": [1, [<type_code>, "<value_string>"]]`
182
- * `type_code`: A numeric code representing the data type. Common codes include: `4` for number, `7` for string/text, `10` for string/message.
183
- * `value_string`: The literal value as a string.
184
- * Examples:
185
- * Number: `"STEPS": [1, [4, "10"]]` (for `move 10 steps` block).
186
- * String/Text: `"MESSAGE": [1, [7, "Hello"]]` (for `say Hello` block).
187
- * String/Message (common for text inputs): `"MESSAGE": [1, [10, "Hello!"]]` (for `say Hello! for 2 secs`).
188
- * **C-Block Substack (blocks within a loop or conditional):**
189
- * Format: `"<SUBSTACK_NAME>": [2, "<blockId_of_first_block_in_substack>"]`
190
- * Common `SUBSTACK_NAME` values are `SUBSTACK` (for `if`, `forever`, `repeat`) and `SUBSTACK2` (for `else` in `if else`).
191
- * Example: `"SUBSTACK": [2, "firstBlockInLoopId"]`
192
-
193
- 4. **`fields` Property Details (for dropdowns or direct internal values):**
194
- * Used for dropdown menus, variable names, list names, or other static selections directly within the block.
195
- * Format: `"<FIELD_NAME>": ["<selected_value>", null]`
196
- * Examples:
197
- * Dropdown: `"KEY_OPTION": ["space", null]` (for `when space key pressed`).
198
- * Variable Name: `"VARIABLE": ["score", null]` (for `set score to 0`).
199
- * Direction (specific motion block): `"FORWARD_BACKWARD": ["forward", null]` (for `go forward layers`).
200
-
201
- 5. **Unique IDs:**
202
- * All block IDs, variable IDs, and list IDs must be unique strings (e.g., "myBlock123", "myVarId456", "myListId789"). Do NOT use placeholder strings like "block_id_here".
203
-
204
- 6. **No Nested `blocks` Dictionary:**
205
- * The `blocks` dictionary should only appear once per `target` (sprite/stage). Do NOT nest a `blocks` dictionary inside an individual block definition. Blocks that are part of a substack are linked via the `SUBSTACK` input.
206
-
207
- 7. **Asset Properties (for Costumes/Sounds):**
208
- * `assetId`, `md5ext`, `bitmapResolution`, `rotationCenterX`/`rotationCenterY` should be correctly associated with costume and sound objects within the `costumes` and `sounds` arrays.
209
-
210
- **General Principles and Important Considerations:**
211
- * **Backward Compatibility:** Adhere strictly to existing Scratch 3.0 opcodes and schema to ensure backward compatibility with older projects. [cite_start]Opcodes must remain consistent to prevent previously saved projects from failing to load or behaving unexpectedly[cite: 18, 19, 25, 65].
212
- * **Forgiving Inputs:** Recognize that Scratch is designed to be "forgiving in its interpretation of inputs." [cite_start]The Scratch VM handles potentially "invalid" inputs gracefully (e.g., converting a number to a string if expected, returning default values like zero or empty strings, or performing no action) rather than crashing[cite: 20, 21, 22, 38, 39, 41]. This implies that precise type matching for inputs might be handled internally by Scratch, allowing for some flexibility in how values are provided, but the agent should aim for the most common and logical type.
213
- """
214
-
215
- SYSTEM_PROMPT_JSON_CORRECTOR ="""
216
- You are an assistant that outputs JSON responses strictly following the given schema.
217
- If the JSON you produce has any formatting errors, missing required fields, or invalid structure, you must identify the problems and correct them.
218
- Always return only valid JSON that fully conforms to the schema below, enclosed in triple backticks (```), without any extra text or explanation.
219
-
220
- If you receive an invalid or incomplete JSON response, fix it by:
221
- - Adding any missing required fields with appropriate values.
222
- - Correcting syntax errors such as missing commas, brackets, or quotes.
223
- - Ensuring the JSON structure matches the schema exactly.
224
-
225
- Remember: Your output must be valid JSON only, ready to be parsed without errors.
226
- """
227
- # debugger and resolver agent for Scratch 3.0
228
- # Main agent of the system agent for Scratch 3.0
229
- agent = create_react_agent(
230
- model=llm,
231
- tools=[], # No specific tools are defined here, but could be added later
232
- prompt=SYSTEM_PROMPT
233
- )
234
 
235
- agent_json_resolver = create_react_agent(
236
- model=llm,
237
- tools=[], # No specific tools are defined here, but could be added later
238
- prompt=SYSTEM_PROMPT_JSON_CORRECTOR
239
- )
240
 
241
- # Helper function to load the block catalog from a JSON file
242
- def _load_block_catalog(file_path: str) -> Dict:
243
- """Loads the Scratch block catalog from a specified JSON file."""
244
- try:
245
- with open(file_path, 'r') as f:
246
- catalog = json.load(f)
247
- logger.info(f"Successfully loaded block catalog from {file_path}")
248
- return catalog
249
- except FileNotFoundError:
250
- logger.error(f"Error: Block catalog file not found at {file_path}")
251
- # Return an empty dict or raise an error, depending on desired behavior
252
- return {}
253
- except json.JSONDecodeError as e:
254
- logger.error(f"Error decoding JSON from {file_path}: {e}")
255
- return {}
256
- except Exception as e:
257
- logger.error(f"An unexpected error occurred while loading {file_path}: {e}")
258
- return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
  # Helper function to load the block catalog from a JSON file
261
  # def _load_block_catalog(block_type: str) -> Dict:
@@ -303,191 +303,190 @@ def _load_block_catalog(file_path: str) -> Dict:
303
 
304
 
305
  # --- Global variable for the block catalog ---
306
- ALL_SCRATCH_BLOCKS_CATALOG = {}
307
- # BLOCK_CATALOG_PATH = "blocks" # Define the path to your JSON file
308
- # HAT_BLOCKS_PATH = "hat_blocks" # Path to the hat blocks JSON file
309
- # STACK_BLOCKS_PATH = "stack_blocks" # Path to the stack blocks JSON file
310
- # REPORTER_BLOCKS_PATH = "reporter_blocks" # Path to the reporter blocks JSON file
311
- # BOOLEAN_BLOCKS_PATH = "boolean_blocks" # Path to the boolean blocks JSON file
312
- # C_BLOCKS_PATH = "c_blocks" # Path to the C blocks JSON file
313
- # CAP_BLOCKS_PATH = "cap_blocks" # Path to the cap blocks JSON file
314
-
315
- BLOCK_CATALOG_PATH = BLOCKS_DIR / "blocks.json"
316
- HAT_BLOCKS_PATH = BLOCKS_DIR / "hat_blocks.json"
317
- STACK_BLOCKS_PATH = BLOCKS_DIR / "stack_blocks.json"
318
- REPORTER_BLOCKS_PATH = BLOCKS_DIR / "reporter_blocks.json"
319
- BOOLEAN_BLOCKS_PATH = BLOCKS_DIR / "boolean_blocks.json"
320
- C_BLOCKS_PATH = BLOCKS_DIR / "c_blocks.json"
321
- CAP_BLOCKS_PATH = BLOCKS_DIR / "cap_blocks.json"
322
-
323
- # Load the block catalogs from their respective JSON files
324
- hat_block_data = _load_block_catalog(HAT_BLOCKS_PATH)
325
- hat_description = hat_block_data["description"]
326
- # hat_description = hat_block_data.get("description", "No description available")
327
- # hat_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in hat_block_data["blocks"]])
328
- # hat_opcodes_functionalities = "\n".join([
329
- # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
330
- # for block in hat_block_data.get("blocks", [])
331
- # ]) if isinstance(hat_block_data.get("blocks"), list) else " No blocks information available."
332
- hat_opcodes_functionalities = os.path.join(BLOCKS_DIR, "hat_blocks.txt")
333
- print("Hat blocks loaded successfully.", hat_description)
334
-
335
- boolean_block_data = _load_block_catalog(BOOLEAN_BLOCKS_PATH)
336
- boolean_description = boolean_block_data["description"]
337
- # boolean_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in boolean_block_data["blocks"]])
338
- # boolean_opcodes_functionalities = "\n".join([
339
- # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
340
- # for block in boolean_block_data.get("blocks", [])
341
- # ]) if isinstance(boolean_block_data.get("blocks"), list) else " No blocks information available."
342
- boolean_opcodes_functionalities = os.path.join(BLOCKS_DIR, "boolean_blocks.txt")
343
-
344
- c_block_data = _load_block_catalog(C_BLOCKS_PATH)
345
- c_description = c_block_data["description"]
346
- # c_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in c_block_data["blocks"]])
347
- # c_opcodes_functionalities = "\n".join([
348
- # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
349
- # for block in c_block_data.get("blocks", [])
350
- # ]) if isinstance(c_block_data.get("blocks"), list) else " No blocks information available."
351
- c_opcodes_functionalities = os.path.join(BLOCKS_DIR, "c_blocks.txt")
352
-
353
- cap_block_data = _load_block_catalog(CAP_BLOCKS_PATH)
354
- cap_description = cap_block_data["description"]
355
- # cap_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in cap_block_data["blocks"]])
356
- # cap_opcodes_functionalities = "\n".join([
357
- # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
358
- # for block in cap_block_data.get("blocks", [])
359
- # ]) if isinstance(cap_block_data.get("blocks"), list) else " No blocks information available."
360
- cap_opcodes_functionalities = os.path.join(BLOCKS_DIR, "cap_blocks.txt")
361
-
362
- reporter_block_data = _load_block_catalog(REPORTER_BLOCKS_PATH)
363
- reporter_description = reporter_block_data["description"]
364
- # reporter_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in reporter_block_data["blocks"]])
365
- # reporter_opcodes_functionalities = "\n".join([
366
- # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
367
- # for block in reporter_block_data.get("blocks", [])
368
- # ]) if isinstance(reporter_block_data.get("blocks"), list) else " No blocks information available."
369
- reporter_opcodes_functionalities = os.path.join(BLOCKS_DIR, "reporter_blocks.txt")
370
-
371
- stack_block_data = _load_block_catalog(STACK_BLOCKS_PATH)
372
- stack_description = stack_block_data["description"]
373
- # stack_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in stack_block_data["blocks"]])
374
- # stack_opcodes_functionalities = "\n".join([
375
- # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
376
- # for block in stack_block_data.get("blocks", [])
377
- # ]) if isinstance(stack_block_data.get("blocks"), list) else " No blocks information available."
378
- stack_opcodes_functionalities = os.path.join(BLOCKS_DIR, "stack_blocks.txt")
379
-
380
- # This makes ALL_SCRATCH_BLOCKS_CATALOG available globally
381
- # ALL_SCRATCH_BLOCKS_CATALOG = _load_block_catalog(BLOCK_CATALOG_PATH)
382
-
383
- # Helper function to extract JSON from LLM response
384
- def extract_json_from_llm_response(raw_response: str) -> dict:
385
- # --- 1) Pull out the JSON code‑block if present ---
386
- md = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", raw_response)
387
- json_string = md.group(1).strip() if md else raw_response
388
-
389
- # --- 2) Trim to the outermost { … } so we drop any prefix/suffix junk ---
390
- first, last = json_string.find('{'), json_string.rfind('}')
391
- if 0 <= first < last:
392
- json_string = json_string[first:last+1]
393
-
394
- # --- 3) PRE‑CLEANUP: remove stray assistant{…}, rogue assistant keys, fix boolean quotes ---
395
- json_string = re.sub(r'\b\w+\s*{', '{', json_string)
396
- json_string = re.sub(r'"assistant"\s*:', '', json_string)
397
- json_string = re.sub(r'\b(false|true)"', r'\1', json_string)
398
- logger.debug("Ran pre‑cleanup for stray tokens and boolean quotes.")
399
-
400
- # --- 3.1) Fix stray inner quotes at start of name/list values ---
401
- # e.g., { "name": " \"recent_scoress\"", ... } → "recent_scoress"
402
- json_string = re.sub(
403
- r'("name"\s*:\s*")\s*"',
404
- r'\1',
405
- json_string
406
- )
407
-
408
- # --- 4) Escape all embedded quotes in any `logic` value up to the next key ---
409
- def _esc(m):
410
- prefix, body = m.group(1), m.group(2)
411
- return prefix + body.replace('"', r'\"')
412
- json_string = re.sub(
413
- r'("logic"\s*:\s*")([\s\S]+?)(?=",\s*"[A-Za-z_]\w*"\s*:\s*)',
414
- _esc,
415
- json_string
416
- )
417
- logger.debug("Escaped embedded quotes in logic fields.")
418
-
419
- logger.debug("Quoted unquoted keys.")
420
-
421
- # --- 6) Remove trailing commas before } or ] ---
422
- json_string = re.sub(r',\s*(?=[}\],])', '', json_string)
423
- json_string = re.sub(r',\s*,', ',', json_string)
424
- logger.debug("Removed trailing commas.")
425
-
426
- # --- 7) Balance braces: drop extra } at end if needed ---
427
- ob, cb = json_string.count('{'), json_string.count('}')
428
- if cb > ob:
429
- excess = cb - ob
430
- json_string = json_string.rstrip()[:-excess]
431
- logger.debug(f"Stripped {excess} extra closing brace(s).")
432
-
433
- # --- 8) Escape literal newlines in *all* string values ---
434
- json_string = re.sub(
435
- r'"((?:[^"\\]|\\.)*?)"',
436
- lambda m: '"' + m.group(1).replace('\n', '\\n').replace('\r', '\\r') + '"',
437
- json_string,
438
- flags=re.DOTALL
439
- )
440
- logger.debug("Escaped newlines in strings.")
441
-
442
- # --- 9) Final parse attempt ---
443
- try:
444
- return json.loads(json_string)
445
- except json.JSONDecodeError:
446
- logger.error("Sanitized JSON still invalid:\n%s", json_string)
447
- raise
448
-
449
- def clean_base64_for_model(raw_b64):
450
- """
451
- Normalize input into a valid data:image/png;base64,<payload> string.
452
 
453
- Accepts:
454
- - a list of base64 strings → picks the first element
455
- - a PIL Image instance → encodes to PNG/base64
456
- - a raw base64 string → strips whitespace and data URI prefix
457
- """
458
- if not raw_b64:
459
- return ""
460
-
461
- # 1. If it’s a list, take its first element
462
- if isinstance(raw_b64, list):
463
- raw_b64 = raw_b64[0] if raw_b64 else ""
464
- if not raw_b64:
465
- return ""
466
-
467
- # 2. If it’s a PIL Image, convert to base64
468
- if isinstance(raw_b64, Image.Image):
469
- buf = io.BytesIO()
470
- raw_b64.save(buf, format="PNG")
471
- raw_b64 = base64.b64encode(buf.getvalue()).decode()
472
-
473
- # 3. At this point it must be a string
474
- if not isinstance(raw_b64, str):
475
- raise TypeError(f"Expected base64 string or PIL Image, got {type(raw_b64)}")
476
-
477
- # 4. Strip any existing data URI prefix, whitespace, or newlines
478
- clean_b64 = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", raw_b64)
479
- clean_b64 = clean_b64.replace("\n", "").replace("\r", "").strip()
480
-
481
- # 5. Validate it’s proper base64
482
- try:
483
- base64.b64decode(clean_b64)
484
- except Exception as e:
485
- logger.error(f"Invalid Base64 passed to model: {e}")
486
- raise
487
 
488
- # 6. Return with the correct data URI prefix
489
- return f"data:image/png;base64,{clean_b64}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
 
 
 
491
 
492
 
493
  # # Node 1: Logic updating if any issue here
@@ -1707,220 +1706,220 @@ def clean_base64_for_model(raw_b64):
1707
  # ]
1708
 
1709
  # Node 6: Logic updating if any issue here
1710
- def plan_logic_aligner_node(state: GameState):
1711
- logger.info("--- Running plan_logic_aligner_node ---")
1712
 
1713
- image = state.get("image", "")
1714
 
1715
- refinement_prompt = f"""
1716
- You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
1717
- "Analyze the Scratch code-block image and generate Pseudo-Code for what this logic appears to be doing."
1718
- From Image, you also have to detect a value of Key given in Text form "Script for: ". Below is the example
1719
- Example: "Script for: Bear", "Script for:" is a key and "Bear" is value.
1720
- --- Scratch 3.0 Block Reference ---
1721
- ### Hat Blocks
1722
- Description: {hat_description}
1723
- Blocks:
1724
- {hat_opcodes_functionalities}
1725
 
1726
- ### Boolean Blocks
1727
- Description: {boolean_description}
1728
- Blocks:
1729
- {boolean_opcodes_functionalities}
1730
 
1731
- ### C Blocks
1732
- Description: {c_description}
1733
- Blocks:
1734
- {c_opcodes_functionalities}
1735
 
1736
- ### Cap Blocks
1737
- Description: {cap_description}
1738
- Blocks:
1739
- {cap_opcodes_functionalities}
1740
 
1741
- ### Reporter Blocks
1742
- Description: {reporter_description}
1743
- Blocks:
1744
- {reporter_opcodes_functionalities}
1745
 
1746
- ### Stack Blocks
1747
- Description: {stack_description}
1748
- Blocks:
1749
- {stack_opcodes_functionalities}
1750
- -----------------------------------
1751
 
1752
- Your task is to:
1753
- If you don't find any "Code-Blocks" then,
1754
- **Don't generate Pseudo Code, and pass the message "No Code-blocks" find...
1755
- If you find any "Code-Blocks" then,
1756
- 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.
1757
 
1758
- 2. **Structural requirements**:
1759
- - **Numeric values** `(e.g., 0, 5, 0.2, -130)` **must** be in parentheses: `(0)`, `(5)`, `(0.2)`, `(-130)`.
1760
- - **AlphaNumeric values** `(e.g., hello, say 5, 4, hi!)` **must** be in parentheses: `(hello)`, `(say 5)`, `(4)`, `(hi!)`.
1761
- - **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])`.
1762
- - **Dropdown options** must be in the form `[option v]` (e.g., `[Game Start v]`, `[blue sky v]`). example use `when [space v] key pressed`.
1763
- - **Reporter blocks** used as inputs must be double‑wrapped: `((x position))`, `((y position))`. example use `if <((y position)) = (-130)> then` or `(((x position)) * (1))`.
1764
- - **Boolean blocks** in conditions must be inside `< >`, including nested ones: `<not <condition>>`, `<<cond1> and <cond2>>`,`<<cond1> or <cond2>>`.
1765
- - **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]?>`.
1766
- - **Operator expressions** must use explicit Scratch operator blocks, e.g.:
1767
- ```
1768
- (([ballSpeed v]) * (1.1))
1769
- ```
1770
- - **Every hat block script must end** with a final `end` on its own line.
1771
 
1772
- 3. **Pseudo‑code formatting**:
1773
- - Represent each block or nested block on its own line.
1774
- - Indent nested blocks by 4 spaces under their parent (`forever`, `if`, etc.).
1775
- - No comments or explanatory text—just the block sequence.
1776
- - 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.
1777
 
1778
- 4. **Logic content**:
1779
- - Build clear flow for mechanics (movement, jumping, flying, scoring, collisions).
1780
- - Match each action closely to a Scratch block or tight sequence.
1781
- - Do **NOT** include any justification or comments—only the raw logic.
1782
 
1783
- 5. **Examples for reference**:
1784
- **Correct** pattern for a simple start script:
1785
- ```
1786
- when green flag clicked
1787
- switch backdrop to [blue sky v]
1788
- set [score v] to (0)
1789
- show variable [score v]
1790
- broadcast [Game Start v]
1791
- end
1792
- ```
1793
- **Correct** pattern for updating the high score variable handling:
1794
- ```
1795
- when I receive [Game Over v]
1796
- if <((score)) > (([High Score v]))> then
1797
- set [High Score v] to ([score v])
1798
- end
1799
- switch backdrop to [Game Over v]
1800
- end
1801
- ```
1802
- **Correct** pattern for level up and increase difficulty use:
1803
- ```
1804
- when I receive [Level Up v]
1805
- change [level v] by (1)
1806
- set [ballSpeed v] to ((([ballSpeed v]) * (1.1)))
1807
- end
1808
- ```
1809
- **Correct** pattern for jumping mechanics use:
1810
- ```
1811
- when [space v] key pressed
1812
- if <((y position)) = (-100)> then
1813
- repeat (5)
1814
- change y by (100)
1815
- wait (0.1) seconds
1816
- change y by (-100)
1817
- wait (0.1) seconds
1818
- end
1819
- end
1820
- end
1821
- ```
1822
- **Correct** pattern for continuos moving objects use:
1823
- ```
1824
- when green flag clicked
1825
- go to x: (240) y: (-100)
1826
- set [speed v] to (-5)
1827
- show variable [speed v]
1828
- forever
1829
- change x by ([speed v])
1830
- if <((x position)) < (-240)> then
1831
- go to x: (240) y: (-100)
1832
- end
1833
- end
1834
- end
1835
- ```
1836
- **Correct** pattern for continuos moving objects use:
1837
- ```
1838
- when green flag clicked
1839
- go to x: (240) y: (-100)
1840
- set [speed v] to (-5)
1841
- show variable [speed v]
1842
- forever
1843
- change x by ([speed v])
1844
- if <((x position)) < (-240)> then
1845
- go to x: (240) y: (-100)
1846
- end
1847
- end
1848
- end
1849
- ```
1850
- 6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
1851
- 7. **Output**:
1852
- Return **only** a JSON object, using double quotes everywhere:
1853
- ```json
1854
- {{
1855
- "refined_logic":{{
1856
- "name_variable": 'Value of "Sript for: "',
1857
- "pseudocode":"…your fully‑formatted pseudo‑code here…",
1858
- }}
1859
- }}
1860
- ```
1861
- """
1862
- image_input = {
1863
- "type": "image_url",
1864
- "image_url": {
1865
- "url": f"data:image/png;base64,{image}"
1866
- }
1867
- }
1868
 
1869
- content = [
1870
- {"type": "text", "text": refinement_prompt},
1871
- image_input
1872
- ]
1873
 
1874
- try:
1875
- # Invoke the main agent for logic refinement and relationship identification
1876
- response = agent.invoke({"messages": [{"role": "user", "content": content}]})
1877
- llm_output_raw = response["messages"][-1].content.strip()
1878
 
1879
- parsed_llm_output = extract_json_from_llm_response(llm_output_raw)
1880
 
1881
- # result = parsed_llm_output
1882
- # Extract needed values directly
1883
- logic_data = parsed_llm_output.get("refined_logic", {})
1884
- name_variable = logic_data.get("name_variable", "Unknown")
1885
- pseudocode = logic_data.get("pseudocode", "No logic extracted")
1886
 
1887
- result = {"pseudo_node": {
1888
- "name_variable": name_variable,
1889
- "pseudocode": pseudocode
1890
- }}
1891
 
1892
- print(f"result:\n\n {result}")
1893
- return result
1894
- except Exception as e:
1895
- logger.error(f"❌ plan_logic_aligner_node failed: {str(e)}")
1896
- return {"error": str(e)}
1897
- except json.JSONDecodeError as error_json:
1898
- # If JSON parsing fails, use the json resolver agent
1899
- correction_prompt = (
1900
- "Your task is to correct the provided JSON string to ensure it is **syntactically perfect and adheres strictly to JSON rules**.\n"
1901
- "It must be a JSON object with `refined_logic` (string) and `block_relationships` (array of objects).\n"
1902
- f"- **Error Details**: {error_json}\n\n"
1903
- "**Strict Instructions for your response:**\n"
1904
- "1. **ONLY** output the corrected JSON. Do not include any other text or explanations.\n"
1905
- "2. Ensure all keys and string values are enclosed in **double quotes**. Escape internal quotes (`\\`).\n"
1906
- "3. No trailing commas. Correct nesting.\n\n"
1907
- "Here is the problematic JSON string to correct:\n"
1908
- f"```json\n{llm_output_raw}\n```\n"
1909
- "Corrected JSON:\n"
1910
- )
1911
- try:
1912
- correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
1913
- corrected_output = extract_json_from_llm_response(correction_response["messages"][-1].content)
1914
 
1915
- result = {
1916
- #"image_path": image_path,
1917
- "pseudo_code": corrected_output
1918
- }
1919
 
1920
- return result
1921
 
1922
- except Exception as e_corr:
1923
- logger.error(f"Failed to correct JSON output for even after retry: {e_corr}")
1924
 
1925
  #def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path, image_base_dir: Path):
1926
  #def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path):
 
136
  # pseudo_code: dict
137
  # action_plan: Optional[Dict]
138
  # temporary_node: Optional[Dict]
139
+ # class GameState(TypedDict):
140
+ # image: str
141
+ # pseudo_node: Optional[Dict]
142
 
143
+ # # Refined SYSTEM_PROMPT with more explicit Scratch JSON rules, especially for variables
144
+ # SYSTEM_PROMPT = """
145
+ # You are an expert AI assistant named GameScratchAgent, specialized in generating and modifying Scratch-VM 3.x game project JSON.
146
+ # Your core task is to process game descriptions and existing Scratch JSON structures, then produce or update JSON segments accurately.
147
+ # You possess deep knowledge of Scratch 3.0 project schema, informed by comprehensive reference materials. When generating or modifying the `blocks` section, pay extremely close attention to the following:
148
+
149
+ # **Scratch Project JSON Schema Rules:**
150
+
151
+ # 1. **Target Structure (`project.json`'s `targets` array):**
152
+ # * Each object in the `targets` array represents a Stage or a Sprite.
153
+ # * `isStage`: A boolean indicating if the target is the Stage (`true`) or a Sprite (`false`).
154
+ # * `name`: The name of the Stage (e.g., `"Stage"`) or the Sprite (e.g., `"Cat"`). This property replaces `objName` found in older Scratch versions.
155
+ # * `variables` dictionary: This dictionary maps unique variable IDs to arrays `[variable_name, initial_value, isCloudVariable?]`.
156
+ # * `variable_name`: The user-defined name of the variable.
157
+ # * `initial_value`: The variable's initial value, which can be a number or a string.
158
+ # * `isCloudVariable?`: (Optional) A boolean indicating if it's a cloud variable (`true`) or a local variable (`false` or absent for regular variables).
159
+ # * Example: `"myVarId123": ["score", 0]`, `"cloudVarId456": ["☁ High Score", "54", true]`
160
+ # * `lists` dictionary: This dictionary maps unique list IDs to arrays `[list_name, [item1, item2, ...]]`.
161
+ # * Example: `"myListId789": ["my list", ["apple", "banana"]]`
162
+ # * `broadcasts` dictionary: This dictionary maps unique broadcast IDs to their names.
163
+ # * Example: `"myBroadcastId": "Game Over"`
164
+ # * `blocks` dictionary: This dictionary contains all the blocks belonging to this target. Keys are block IDs, values are block objects.
165
+
166
+ # 2. **Block Structure (within a `target`'s `blocks` dictionary):**
167
+ # * Every block object must have the following core properties:
168
+ # * [cite_start]`opcode`: A unique internal identifier for the block's specific functionality (e.g., `"motion_movesteps"`, `"event_whenflagclicked"`)[cite: 31, 18, 439, 452].
169
+ # * `parent`: The ID of the block directly above it in the script stack (or `null` for a top-level block).
170
+ # * `next`: The ID of the block directly below it in the script stack (or `null` for the end of a stack).
171
+ # * `inputs`: An object defining values or blocks plugged into the block's input slots. Values are **arrays**.
172
+ # * `fields`: An object defining dropdown menu selections or direct internal values within the block. Values are **arrays**.
173
+ # * `shadow`: `true` if it's a shadow block (e.g., a default number input that can be replaced by another block), `false` otherwise.
174
+ # * `topLevel`: `true` if it's a hat block or a standalone block (not connected to a parent), `false` otherwise.
175
+
176
+ # 3. **`inputs` Property Details (for blocks plugged into input slots):**
177
+ # * **Direct Block Connection (Reporter/Boolean block plugged in):**
178
+ # * Format: `"<INPUT_NAME>": [1, "<blockId_of_plugged_block>"]`
179
+ # * Example: `"CONDITION": [1, "someBooleanBlockId"]` (e.g., for an `if` block).
180
+ # * **Literal Value Input (Shadow block with a literal):**
181
+ # * Format: `"<INPUT_NAME>": [1, [<type_code>, "<value_string>"]]`
182
+ # * `type_code`: A numeric code representing the data type. Common codes include: `4` for number, `7` for string/text, `10` for string/message.
183
+ # * `value_string`: The literal value as a string.
184
+ # * Examples:
185
+ # * Number: `"STEPS": [1, [4, "10"]]` (for `move 10 steps` block).
186
+ # * String/Text: `"MESSAGE": [1, [7, "Hello"]]` (for `say Hello` block).
187
+ # * String/Message (common for text inputs): `"MESSAGE": [1, [10, "Hello!"]]` (for `say Hello! for 2 secs`).
188
+ # * **C-Block Substack (blocks within a loop or conditional):**
189
+ # * Format: `"<SUBSTACK_NAME>": [2, "<blockId_of_first_block_in_substack>"]`
190
+ # * Common `SUBSTACK_NAME` values are `SUBSTACK` (for `if`, `forever`, `repeat`) and `SUBSTACK2` (for `else` in `if else`).
191
+ # * Example: `"SUBSTACK": [2, "firstBlockInLoopId"]`
192
+
193
+ # 4. **`fields` Property Details (for dropdowns or direct internal values):**
194
+ # * Used for dropdown menus, variable names, list names, or other static selections directly within the block.
195
+ # * Format: `"<FIELD_NAME>": ["<selected_value>", null]`
196
+ # * Examples:
197
+ # * Dropdown: `"KEY_OPTION": ["space", null]` (for `when space key pressed`).
198
+ # * Variable Name: `"VARIABLE": ["score", null]` (for `set score to 0`).
199
+ # * Direction (specific motion block): `"FORWARD_BACKWARD": ["forward", null]` (for `go forward layers`).
200
+
201
+ # 5. **Unique IDs:**
202
+ # * All block IDs, variable IDs, and list IDs must be unique strings (e.g., "myBlock123", "myVarId456", "myListId789"). Do NOT use placeholder strings like "block_id_here".
203
+
204
+ # 6. **No Nested `blocks` Dictionary:**
205
+ # * The `blocks` dictionary should only appear once per `target` (sprite/stage). Do NOT nest a `blocks` dictionary inside an individual block definition. Blocks that are part of a substack are linked via the `SUBSTACK` input.
206
+
207
+ # 7. **Asset Properties (for Costumes/Sounds):**
208
+ # * `assetId`, `md5ext`, `bitmapResolution`, `rotationCenterX`/`rotationCenterY` should be correctly associated with costume and sound objects within the `costumes` and `sounds` arrays.
209
+
210
+ # **General Principles and Important Considerations:**
211
+ # * **Backward Compatibility:** Adhere strictly to existing Scratch 3.0 opcodes and schema to ensure backward compatibility with older projects. [cite_start]Opcodes must remain consistent to prevent previously saved projects from failing to load or behaving unexpectedly[cite: 18, 19, 25, 65].
212
+ # * **Forgiving Inputs:** Recognize that Scratch is designed to be "forgiving in its interpretation of inputs." [cite_start]The Scratch VM handles potentially "invalid" inputs gracefully (e.g., converting a number to a string if expected, returning default values like zero or empty strings, or performing no action) rather than crashing[cite: 20, 21, 22, 38, 39, 41]. This implies that precise type matching for inputs might be handled internally by Scratch, allowing for some flexibility in how values are provided, but the agent should aim for the most common and logical type.
213
+ # """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
+ # SYSTEM_PROMPT_JSON_CORRECTOR ="""
216
+ # You are an assistant that outputs JSON responses strictly following the given schema.
217
+ # If the JSON you produce has any formatting errors, missing required fields, or invalid structure, you must identify the problems and correct them.
218
+ # Always return only valid JSON that fully conforms to the schema below, enclosed in triple backticks (```), without any extra text or explanation.
 
219
 
220
+ # If you receive an invalid or incomplete JSON response, fix it by:
221
+ # - Adding any missing required fields with appropriate values.
222
+ # - Correcting syntax errors such as missing commas, brackets, or quotes.
223
+ # - Ensuring the JSON structure matches the schema exactly.
224
+
225
+ # Remember: Your output must be valid JSON only, ready to be parsed without errors.
226
+ # """
227
+ # debugger and resolver agent for Scratch 3.0
228
+ # Main agent of the system agent for Scratch 3.0
229
+ # agent = create_react_agent(
230
+ # model=llm,
231
+ # tools=[], # No specific tools are defined here, but could be added later
232
+ # prompt=SYSTEM_PROMPT
233
+ # )
234
+
235
+ # agent_json_resolver = create_react_agent(
236
+ # model=llm,
237
+ # tools=[], # No specific tools are defined here, but could be added later
238
+ # prompt=SYSTEM_PROMPT_JSON_CORRECTOR
239
+ # )
240
+
241
+ # # Helper function to load the block catalog from a JSON file
242
+ # def _load_block_catalog(file_path: str) -> Dict:
243
+ # """Loads the Scratch block catalog from a specified JSON file."""
244
+ # try:
245
+ # with open(file_path, 'r') as f:
246
+ # catalog = json.load(f)
247
+ # logger.info(f"Successfully loaded block catalog from {file_path}")
248
+ # return catalog
249
+ # except FileNotFoundError:
250
+ # logger.error(f"Error: Block catalog file not found at {file_path}")
251
+ # # Return an empty dict or raise an error, depending on desired behavior
252
+ # return {}
253
+ # except json.JSONDecodeError as e:
254
+ # logger.error(f"Error decoding JSON from {file_path}: {e}")
255
+ # return {}
256
+ # except Exception as e:
257
+ # logger.error(f"An unexpected error occurred while loading {file_path}: {e}")
258
+ # return {}
259
 
260
  # Helper function to load the block catalog from a JSON file
261
  # def _load_block_catalog(block_type: str) -> Dict:
 
303
 
304
 
305
  # --- Global variable for the block catalog ---
306
+ # ALL_SCRATCH_BLOCKS_CATALOG = {}
307
+ # # BLOCK_CATALOG_PATH = "blocks" # Define the path to your JSON file
308
+ # # HAT_BLOCKS_PATH = "hat_blocks" # Path to the hat blocks JSON file
309
+ # # STACK_BLOCKS_PATH = "stack_blocks" # Path to the stack blocks JSON file
310
+ # # REPORTER_BLOCKS_PATH = "reporter_blocks" # Path to the reporter blocks JSON file
311
+ # # BOOLEAN_BLOCKS_PATH = "boolean_blocks" # Path to the boolean blocks JSON file
312
+ # # C_BLOCKS_PATH = "c_blocks" # Path to the C blocks JSON file
313
+ # # CAP_BLOCKS_PATH = "cap_blocks" # Path to the cap blocks JSON file
314
+
315
+ # BLOCK_CATALOG_PATH = BLOCKS_DIR / "blocks.json"
316
+ # HAT_BLOCKS_PATH = BLOCKS_DIR / "hat_blocks.json"
317
+ # STACK_BLOCKS_PATH = BLOCKS_DIR / "stack_blocks.json"
318
+ # REPORTER_BLOCKS_PATH = BLOCKS_DIR / "reporter_blocks.json"
319
+ # BOOLEAN_BLOCKS_PATH = BLOCKS_DIR / "boolean_blocks.json"
320
+ # C_BLOCKS_PATH = BLOCKS_DIR / "c_blocks.json"
321
+ # CAP_BLOCKS_PATH = BLOCKS_DIR / "cap_blocks.json"
322
+
323
+ # # Load the block catalogs from their respective JSON files
324
+ # hat_block_data = _load_block_catalog(HAT_BLOCKS_PATH)
325
+ # hat_description = hat_block_data["description"]
326
+ # # hat_description = hat_block_data.get("description", "No description available")
327
+ # # hat_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in hat_block_data["blocks"]])
328
+ # # hat_opcodes_functionalities = "\n".join([
329
+ # # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
330
+ # # for block in hat_block_data.get("blocks", [])
331
+ # # ]) if isinstance(hat_block_data.get("blocks"), list) else " No blocks information available."
332
+ # hat_opcodes_functionalities = os.path.join(BLOCKS_DIR, "hat_blocks.txt")
333
+ # print("Hat blocks loaded successfully.", hat_description)
334
+
335
+ # boolean_block_data = _load_block_catalog(BOOLEAN_BLOCKS_PATH)
336
+ # boolean_description = boolean_block_data["description"]
337
+ # # boolean_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in boolean_block_data["blocks"]])
338
+ # # boolean_opcodes_functionalities = "\n".join([
339
+ # # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
340
+ # # for block in boolean_block_data.get("blocks", [])
341
+ # # ]) if isinstance(boolean_block_data.get("blocks"), list) else " No blocks information available."
342
+ # boolean_opcodes_functionalities = os.path.join(BLOCKS_DIR, "boolean_blocks.txt")
343
+
344
+ # c_block_data = _load_block_catalog(C_BLOCKS_PATH)
345
+ # c_description = c_block_data["description"]
346
+ # # c_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in c_block_data["blocks"]])
347
+ # # c_opcodes_functionalities = "\n".join([
348
+ # # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
349
+ # # for block in c_block_data.get("blocks", [])
350
+ # # ]) if isinstance(c_block_data.get("blocks"), list) else " No blocks information available."
351
+ # c_opcodes_functionalities = os.path.join(BLOCKS_DIR, "c_blocks.txt")
352
+
353
+ # cap_block_data = _load_block_catalog(CAP_BLOCKS_PATH)
354
+ # cap_description = cap_block_data["description"]
355
+ # # cap_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in cap_block_data["blocks"]])
356
+ # # cap_opcodes_functionalities = "\n".join([
357
+ # # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
358
+ # # for block in cap_block_data.get("blocks", [])
359
+ # # ]) if isinstance(cap_block_data.get("blocks"), list) else " No blocks information available."
360
+ # cap_opcodes_functionalities = os.path.join(BLOCKS_DIR, "cap_blocks.txt")
361
+
362
+ # reporter_block_data = _load_block_catalog(REPORTER_BLOCKS_PATH)
363
+ # reporter_description = reporter_block_data["description"]
364
+ # # reporter_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in reporter_block_data["blocks"]])
365
+ # # reporter_opcodes_functionalities = "\n".join([
366
+ # # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
367
+ # # for block in reporter_block_data.get("blocks", [])
368
+ # # ]) if isinstance(reporter_block_data.get("blocks"), list) else " No blocks information available."
369
+ # reporter_opcodes_functionalities = os.path.join(BLOCKS_DIR, "reporter_blocks.txt")
370
+
371
+ # stack_block_data = _load_block_catalog(STACK_BLOCKS_PATH)
372
+ # stack_description = stack_block_data["description"]
373
+ # # stack_opcodes_functionalities = "\n".join([f" - Opcode: {block['op_code']}, functionality: {block['functionality']} example: standalone use: {block['example_standalone']}" for block in stack_block_data["blocks"]])
374
+ # # stack_opcodes_functionalities = "\n".join([
375
+ # # f" - Opcode: {block.get('op_code', 'N/A')}, functionality: {block.get('functionality', 'N/A')}, example: standalone use {block.get('example_standalone', 'N/A')}"
376
+ # # for block in stack_block_data.get("blocks", [])
377
+ # # ]) if isinstance(stack_block_data.get("blocks"), list) else " No blocks information available."
378
+ # stack_opcodes_functionalities = os.path.join(BLOCKS_DIR, "stack_blocks.txt")
379
+
380
+ # # This makes ALL_SCRATCH_BLOCKS_CATALOG available globally
381
+ # # ALL_SCRATCH_BLOCKS_CATALOG = _load_block_catalog(BLOCK_CATALOG_PATH)
382
+
383
+ # # Helper function to extract JSON from LLM response
384
+ # def extract_json_from_llm_response(raw_response: str) -> dict:
385
+ # # --- 1) Pull out the JSON code‑block if present ---
386
+ # md = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", raw_response)
387
+ # json_string = md.group(1).strip() if md else raw_response
388
+
389
+ # # --- 2) Trim to the outermost { … } so we drop any prefix/suffix junk ---
390
+ # first, last = json_string.find('{'), json_string.rfind('}')
391
+ # if 0 <= first < last:
392
+ # json_string = json_string[first:last+1]
393
+
394
+ # # --- 3) PRE‑CLEANUP: remove stray assistant{…}, rogue assistant keys, fix boolean quotes ---
395
+ # json_string = re.sub(r'\b\w+\s*{', '{', json_string)
396
+ # json_string = re.sub(r'"assistant"\s*:', '', json_string)
397
+ # json_string = re.sub(r'\b(false|true)"', r'\1', json_string)
398
+ # logger.debug("Ran pre‑cleanup for stray tokens and boolean quotes.")
399
+
400
+ # # --- 3.1) Fix stray inner quotes at start of name/list values ---
401
+ # # e.g., { "name": " \"recent_scoress\"", ... } → "recent_scoress"
402
+ # json_string = re.sub(
403
+ # r'("name"\s*:\s*")\s*"',
404
+ # r'\1',
405
+ # json_string
406
+ # )
407
+
408
+ # # --- 4) Escape all embedded quotes in any `logic` value up to the next key ---
409
+ # def _esc(m):
410
+ # prefix, body = m.group(1), m.group(2)
411
+ # return prefix + body.replace('"', r'\"')
412
+ # json_string = re.sub(
413
+ # r'("logic"\s*:\s*")([\s\S]+?)(?=",\s*"[A-Za-z_]\w*"\s*:\s*)',
414
+ # _esc,
415
+ # json_string
416
+ # )
417
+ # logger.debug("Escaped embedded quotes in logic fields.")
418
+
419
+ # logger.debug("Quoted unquoted keys.")
420
+
421
+ # # --- 6) Remove trailing commas before } or ] ---
422
+ # json_string = re.sub(r',\s*(?=[}\],])', '', json_string)
423
+ # json_string = re.sub(r',\s*,', ',', json_string)
424
+ # logger.debug("Removed trailing commas.")
425
+
426
+ # # --- 7) Balance braces: drop extra } at end if needed ---
427
+ # ob, cb = json_string.count('{'), json_string.count('}')
428
+ # if cb > ob:
429
+ # excess = cb - ob
430
+ # json_string = json_string.rstrip()[:-excess]
431
+ # logger.debug(f"Stripped {excess} extra closing brace(s).")
432
+
433
+ # # --- 8) Escape literal newlines in *all* string values ---
434
+ # json_string = re.sub(
435
+ # r'"((?:[^"\\]|\\.)*?)"',
436
+ # lambda m: '"' + m.group(1).replace('\n', '\\n').replace('\r', '\\r') + '"',
437
+ # json_string,
438
+ # flags=re.DOTALL
439
+ # )
440
+ # logger.debug("Escaped newlines in strings.")
441
+
442
+ # # --- 9) Final parse attempt ---
443
+ # try:
444
+ # return json.loads(json_string)
445
+ # except json.JSONDecodeError:
446
+ # logger.error("Sanitized JSON still invalid:\n%s", json_string)
447
+ # raise
 
 
 
 
448
 
449
+ # def clean_base64_for_model(raw_b64):
450
+ # """
451
+ # Normalize input into a valid data:image/png;base64,<payload> string.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
 
453
+ # Accepts:
454
+ # - a list of base64 strings → picks the first element
455
+ # - a PIL Image instance → encodes to PNG/base64
456
+ # - a raw base64 string → strips whitespace and data URI prefix
457
+ # """
458
+ # if not raw_b64:
459
+ # return ""
460
+
461
+ # # 1. If it’s a list, take its first element
462
+ # if isinstance(raw_b64, list):
463
+ # raw_b64 = raw_b64[0] if raw_b64 else ""
464
+ # if not raw_b64:
465
+ # return ""
466
+
467
+ # # 2. If it’s a PIL Image, convert to base64
468
+ # if isinstance(raw_b64, Image.Image):
469
+ # buf = io.BytesIO()
470
+ # raw_b64.save(buf, format="PNG")
471
+ # raw_b64 = base64.b64encode(buf.getvalue()).decode()
472
+
473
+ # # 3. At this point it must be a string
474
+ # if not isinstance(raw_b64, str):
475
+ # raise TypeError(f"Expected base64 string or PIL Image, got {type(raw_b64)}")
476
+
477
+ # # 4. Strip any existing data URI prefix, whitespace, or newlines
478
+ # clean_b64 = re.sub(r"^data:image\/[a-zA-Z]+;base64,", "", raw_b64)
479
+ # clean_b64 = clean_b64.replace("\n", "").replace("\r", "").strip()
480
+
481
+ # # 5. Validate it’s proper base64
482
+ # try:
483
+ # base64.b64decode(clean_b64)
484
+ # except Exception as e:
485
+ # logger.error(f"Invalid Base64 passed to model: {e}")
486
+ # raise
487
 
488
+ # # 6. Return with the correct data URI prefix
489
+ # return f"data:image/png;base64,{clean_b64}"
490
 
491
 
492
  # # Node 1: Logic updating if any issue here
 
1706
  # ]
1707
 
1708
  # Node 6: Logic updating if any issue here
1709
+ # def plan_logic_aligner_node(state: GameState):
1710
+ # logger.info("--- Running plan_logic_aligner_node ---")
1711
 
1712
+ # image = state.get("image", "")
1713
 
1714
+ # refinement_prompt = f"""
1715
+ # You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
1716
+ # "Analyze the Scratch code-block image and generate Pseudo-Code for what this logic appears to be doing."
1717
+ # From Image, you also have to detect a value of Key given in Text form "Script for: ". Below is the example
1718
+ # Example: "Script for: Bear", "Script for:" is a key and "Bear" is value.
1719
+ # --- Scratch 3.0 Block Reference ---
1720
+ # ### Hat Blocks
1721
+ # Description: {hat_description}
1722
+ # Blocks:
1723
+ # {hat_opcodes_functionalities}
1724
 
1725
+ # ### Boolean Blocks
1726
+ # Description: {boolean_description}
1727
+ # Blocks:
1728
+ # {boolean_opcodes_functionalities}
1729
 
1730
+ # ### C Blocks
1731
+ # Description: {c_description}
1732
+ # Blocks:
1733
+ # {c_opcodes_functionalities}
1734
 
1735
+ # ### Cap Blocks
1736
+ # Description: {cap_description}
1737
+ # Blocks:
1738
+ # {cap_opcodes_functionalities}
1739
 
1740
+ # ### Reporter Blocks
1741
+ # Description: {reporter_description}
1742
+ # Blocks:
1743
+ # {reporter_opcodes_functionalities}
1744
 
1745
+ # ### Stack Blocks
1746
+ # Description: {stack_description}
1747
+ # Blocks:
1748
+ # {stack_opcodes_functionalities}
1749
+ # -----------------------------------
1750
 
1751
+ # Your task is to:
1752
+ # If you don't find any "Code-Blocks" then,
1753
+ # **Don't generate Pseudo Code, and pass the message "No Code-blocks" find...
1754
+ # If you find any "Code-Blocks" then,
1755
+ # 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.
1756
 
1757
+ # 2. **Structural requirements**:
1758
+ # - **Numeric values** `(e.g., 0, 5, 0.2, -130)` **must** be in parentheses: `(0)`, `(5)`, `(0.2)`, `(-130)`.
1759
+ # - **AlphaNumeric values** `(e.g., hello, say 5, 4, hi!)` **must** be in parentheses: `(hello)`, `(say 5)`, `(4)`, `(hi!)`.
1760
+ # - **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])`.
1761
+ # - **Dropdown options** must be in the form `[option v]` (e.g., `[Game Start v]`, `[blue sky v]`). example use `when [space v] key pressed`.
1762
+ # - **Reporter blocks** used as inputs must be double‑wrapped: `((x position))`, `((y position))`. example use `if <((y position)) = (-130)> then` or `(((x position)) * (1))`.
1763
+ # - **Boolean blocks** in conditions must be inside `< >`, including nested ones: `<not <condition>>`, `<<cond1> and <cond2>>`,`<<cond1> or <cond2>>`.
1764
+ # - **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]?>`.
1765
+ # - **Operator expressions** must use explicit Scratch operator blocks, e.g.:
1766
+ # ```
1767
+ # (([ballSpeed v]) * (1.1))
1768
+ # ```
1769
+ # - **Every hat block script must end** with a final `end` on its own line.
1770
 
1771
+ # 3. **Pseudo‑code formatting**:
1772
+ # - Represent each block or nested block on its own line.
1773
+ # - Indent nested blocks by 4 spaces under their parent (`forever`, `if`, etc.).
1774
+ # - No comments or explanatory text—just the block sequence.
1775
+ # - 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.
1776
 
1777
+ # 4. **Logic content**:
1778
+ # - Build clear flow for mechanics (movement, jumping, flying, scoring, collisions).
1779
+ # - Match each action closely to a Scratch block or tight sequence.
1780
+ # - Do **NOT** include any justification or comments—only the raw logic.
1781
 
1782
+ # 5. **Examples for reference**:
1783
+ # **Correct** pattern for a simple start script:
1784
+ # ```
1785
+ # when green flag clicked
1786
+ # switch backdrop to [blue sky v]
1787
+ # set [score v] to (0)
1788
+ # show variable [score v]
1789
+ # broadcast [Game Start v]
1790
+ # end
1791
+ # ```
1792
+ # **Correct** pattern for updating the high score variable handling:
1793
+ # ```
1794
+ # when I receive [Game Over v]
1795
+ # if <((score)) > (([High Score v]))> then
1796
+ # set [High Score v] to ([score v])
1797
+ # end
1798
+ # switch backdrop to [Game Over v]
1799
+ # end
1800
+ # ```
1801
+ # **Correct** pattern for level up and increase difficulty use:
1802
+ # ```
1803
+ # when I receive [Level Up v]
1804
+ # change [level v] by (1)
1805
+ # set [ballSpeed v] to ((([ballSpeed v]) * (1.1)))
1806
+ # end
1807
+ # ```
1808
+ # **Correct** pattern for jumping mechanics use:
1809
+ # ```
1810
+ # when [space v] key pressed
1811
+ # if <((y position)) = (-100)> then
1812
+ # repeat (5)
1813
+ # change y by (100)
1814
+ # wait (0.1) seconds
1815
+ # change y by (-100)
1816
+ # wait (0.1) seconds
1817
+ # end
1818
+ # end
1819
+ # end
1820
+ # ```
1821
+ # **Correct** pattern for continuos moving objects use:
1822
+ # ```
1823
+ # when green flag clicked
1824
+ # go to x: (240) y: (-100)
1825
+ # set [speed v] to (-5)
1826
+ # show variable [speed v]
1827
+ # forever
1828
+ # change x by ([speed v])
1829
+ # if <((x position)) < (-240)> then
1830
+ # go to x: (240) y: (-100)
1831
+ # end
1832
+ # end
1833
+ # end
1834
+ # ```
1835
+ # **Correct** pattern for continuos moving objects use:
1836
+ # ```
1837
+ # when green flag clicked
1838
+ # go to x: (240) y: (-100)
1839
+ # set [speed v] to (-5)
1840
+ # show variable [speed v]
1841
+ # forever
1842
+ # change x by ([speed v])
1843
+ # if <((x position)) < (-240)> then
1844
+ # go to x: (240) y: (-100)
1845
+ # end
1846
+ # end
1847
+ # end
1848
+ # ```
1849
+ # 6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
1850
+ # 7. **Output**:
1851
+ # Return **only** a JSON object, using double quotes everywhere:
1852
+ # ```json
1853
+ # {{
1854
+ # "refined_logic":{{
1855
+ # "name_variable": 'Value of "Sript for: "',
1856
+ # "pseudocode":"…your fully‑formatted pseudo‑code here…",
1857
+ # }}
1858
+ # }}
1859
+ # ```
1860
+ # """
1861
+ # image_input = {
1862
+ # "type": "image_url",
1863
+ # "image_url": {
1864
+ # "url": f"data:image/png;base64,{image}"
1865
+ # }
1866
+ # }
1867
 
1868
+ # content = [
1869
+ # {"type": "text", "text": refinement_prompt},
1870
+ # image_input
1871
+ # ]
1872
 
1873
+ # try:
1874
+ # # Invoke the main agent for logic refinement and relationship identification
1875
+ # response = agent.invoke({"messages": [{"role": "user", "content": content}]})
1876
+ # llm_output_raw = response["messages"][-1].content.strip()
1877
 
1878
+ # parsed_llm_output = extract_json_from_llm_response(llm_output_raw)
1879
 
1880
+ # # result = parsed_llm_output
1881
+ # # Extract needed values directly
1882
+ # logic_data = parsed_llm_output.get("refined_logic", {})
1883
+ # name_variable = logic_data.get("name_variable", "Unknown")
1884
+ # pseudocode = logic_data.get("pseudocode", "No logic extracted")
1885
 
1886
+ # result = {"pseudo_node": {
1887
+ # "name_variable": name_variable,
1888
+ # "pseudocode": pseudocode
1889
+ # }}
1890
 
1891
+ # print(f"result:\n\n {result}")
1892
+ # return result
1893
+ # except Exception as e:
1894
+ # logger.error(f"❌ plan_logic_aligner_node failed: {str(e)}")
1895
+ # return {"error": str(e)}
1896
+ # except json.JSONDecodeError as error_json:
1897
+ # # If JSON parsing fails, use the json resolver agent
1898
+ # correction_prompt = (
1899
+ # "Your task is to correct the provided JSON string to ensure it is **syntactically perfect and adheres strictly to JSON rules**.\n"
1900
+ # "It must be a JSON object with `refined_logic` (string) and `block_relationships` (array of objects).\n"
1901
+ # f"- **Error Details**: {error_json}\n\n"
1902
+ # "**Strict Instructions for your response:**\n"
1903
+ # "1. **ONLY** output the corrected JSON. Do not include any other text or explanations.\n"
1904
+ # "2. Ensure all keys and string values are enclosed in **double quotes**. Escape internal quotes (`\\`).\n"
1905
+ # "3. No trailing commas. Correct nesting.\n\n"
1906
+ # "Here is the problematic JSON string to correct:\n"
1907
+ # f"```json\n{llm_output_raw}\n```\n"
1908
+ # "Corrected JSON:\n"
1909
+ # )
1910
+ # try:
1911
+ # correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
1912
+ # corrected_output = extract_json_from_llm_response(correction_response["messages"][-1].content)
1913
 
1914
+ # result = {
1915
+ # #"image_path": image_path,
1916
+ # "pseudo_code": corrected_output
1917
+ # }
1918
 
1919
+ # return result
1920
 
1921
+ # except Exception as e_corr:
1922
+ # logger.error(f"Failed to correct JSON output for even after retry: {e_corr}")
1923
 
1924
  #def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path, image_base_dir: Path):
1925
  #def extract_images_from_pdf(pdf_path: Path, json_base_dir: Path):