WebashalarForML commited on
Commit
c7cb129
·
verified ·
1 Parent(s): ccf5f13

Upload 76 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. scratch_agent/.env +5 -0
  3. scratch_agent/README.md +89 -0
  4. scratch_agent/__pycache__/agent.cpython-312.pyc +0 -0
  5. scratch_agent/__pycache__/retry_groq.cpython-312.pyc +0 -0
  6. scratch_agent/action_node.py +452 -0
  7. scratch_agent/agent.py +1647 -0
  8. scratch_agent/agent2.py +259 -0
  9. scratch_agent/app.py +997 -0
  10. scratch_agent/blocks_content.json +1367 -0
  11. scratch_agent/blocks_defination.py +274 -0
  12. scratch_agent/chats/user_chat_no_113a02f93587a6622aa86c130e31f025.json +18 -0
  13. scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  14. scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  15. scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  16. scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/project.json +104 -0
  17. scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  18. scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  19. scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  20. scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/project.json +104 -0
  21. scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  22. scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  23. scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  24. scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/project.json +104 -0
  25. scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  26. scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  27. scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  28. scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/project.json +104 -0
  29. scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  30. scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  31. scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  32. scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/project.json +104 -0
  33. scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  34. scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  35. scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  36. scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/project.json +104 -0
  37. scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  38. scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  39. scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  40. scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/project.json +104 -0
  41. scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
  42. scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
  43. scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
  44. scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/project.json +104 -0
  45. scratch_agent/helper_function.py +0 -0
  46. scratch_agent/last_updated.txt +2 -0
  47. scratch_agent/logs.txt +51 -0
  48. scratch_agent/multi_Agent_scratch.py +298 -0
  49. scratch_agent/project.json +573 -0
  50. scratch_agent/promptS.txt +560 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ scratch_agent/uploads/documents/LLM_based_QA_chatbot_builder.pdf filter=lfs diff=lfs merge=lfs -text
scratch_agent/.env ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ MISTRAL_API_KEY= "4ObQF29a1JbbYQxDafYXxxoTF02tC4DB"
2
+ GROQ_API_KEY = "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"
3
+ allow_unsafe_werkzeug=True
4
+ TAVILY_API_KEY= "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"
5
+ PINECONE_API = "pcsk_3GWkuM_S6YsnKKUUYgkjwDf2J44rtUq9z2KSkPko8eqnQ6WFpM5UQhHihWmp7vHw712MZm"
scratch_agent/README.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # General Agent with Audio, Image, and File Processing
2
+
3
+ This is a general-purpose agent built with LangChain/LangGraph that includes advanced tools for processing various types of data.
4
+
5
+ ## Tool Categories
6
+
7
+ The agent includes tools for working with:
8
+
9
+ - **Web Content**: Search web pages, news articles, Wikipedia, ArXiv papers
10
+ - **Files**: Read PDFs, DOCXs, Excel files
11
+ - **Media**: Process images, transcribe audio, extract text from YouTube videos
12
+ - **Code**: Analyze code structure, read code files, analyze functions
13
+ - **Math**: Basic math operations
14
+
15
+ ## Testing Your Installation
16
+
17
+ Before using the agent, you should check if all dependencies are installed and tools are working correctly:
18
+
19
+ 1. **Check and install dependencies**:
20
+ ```
21
+ python fix_dependencies.py
22
+ ```
23
+ This script will check for missing Python packages and system dependencies.
24
+
25
+ 2. **Test all tools**:
26
+ ```
27
+ python test_all_tools.py
28
+ ```
29
+ This will test all tools and report any issues.
30
+
31
+ 3. **Test image and audio processing specifically**:
32
+ ```
33
+ python test_image_audio.py
34
+ ```
35
+ This focuses on testing media processing tools and provides detailed troubleshooting steps.
36
+
37
+ ## System Requirements
38
+
39
+ For full functionality, you'll need:
40
+
41
+ - **Python 3.8+**
42
+ - **Tesseract OCR** (for image text extraction)
43
+ - **FFmpeg** (for audio processing)
44
+ - **Internet connection** (for web search, YouTube, etc.)
45
+ - **API Keys**: GROQ_API_KEY must be set in .env file or environment variables
46
+
47
+ ## Agent Structure
48
+
49
+ This agent uses a streamlined 3-node graph structure:
50
+
51
+ 1. **PerceptionAgent**: Handles web searches, looking up information
52
+ 2. **ActionAgent**: Performs calculations, file operations, code analysis
53
+ 3. **EvaluationAgent**: Ensures answers are properly formatted
54
+
55
+ ## Common Issues
56
+
57
+ If you encounter issues:
58
+
59
+ 1. **Web Scraping Errors**: The agent has robust error handling for 403 Forbidden errors
60
+ 2. **Audio Processing Errors**: Make sure FFmpeg is installed and in your PATH
61
+ 3. **Image Processing Errors**: Make sure Tesseract OCR is installed and in your PATH
62
+ 4. **GROQ API Rate Limits**: The agent includes automatic rate limiting and retry mechanisms
63
+
64
+ ## Running GAIA Tests
65
+
66
+ To test if the agent can properly handle factual questions with GAIA format:
67
+
68
+ ```
69
+ python test_factual_questions.py
70
+ ```
71
+
72
+ ## Testing Individual Tools
73
+
74
+ ```python
75
+ from agent import multiply, add, subtract, divide, modulus # Math tools
76
+ from agent import web_search, wiki_search # Web tools
77
+ from agent import read_text_from_pdf, read_text_from_docx # Document tools
78
+ from agent import image_processing # Image tools
79
+ from agent import transcribe_audio # Audio tools
80
+ from agent import analyze_code, read_code_file # Code tools
81
+
82
+ # Test a tool directly
83
+ result = multiply(5, 7)
84
+ print(result) # 35
85
+
86
+ # Process an image
87
+ image_description = image_processing("Describe this image", "path/to/image.jpg")
88
+ print(image_description)
89
+ ```
scratch_agent/__pycache__/agent.cpython-312.pyc ADDED
Binary file (65.8 kB). View file
 
scratch_agent/__pycache__/retry_groq.cpython-312.pyc ADDED
Binary file (9.73 kB). View file
 
scratch_agent/action_node.py ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import uuid
3
+ import logging
4
+ from typing import Dict, Any, List
5
+
6
+ # Assume GameState is a TypedDict or similar for clarity
7
+ # from typing import TypedDict
8
+ # class GameState(TypedDict):
9
+ # description: str
10
+ # project_json: Dict[str, Any]
11
+ # action_plan: Dict[str, Any]
12
+ # sprite_initial_positions: Dict[str, Any]
13
+
14
+ # Placeholder for actual GameState in your application
15
+ GameState = Dict[str, Any]
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # --- Mock Agent for demonstration ---
20
+ class MockAgent:
21
+ def invoke(self, payload: Dict[str, Any]) -> Dict[str, Any]:
22
+ """
23
+ Mocks an LLM agent invocation. In a real scenario, this would call your
24
+ actual LLM API (e.g., through LangChain, LlamaIndex, etc.).
25
+ """
26
+ user_message = payload["messages"][-1]["content"]
27
+ print(f"\n--- Mock Agent Received Prompt (partial) ---\n{user_message[:500]}...\n------------------------------------------")
28
+
29
+ # Simplified mock responses for demonstration purposes
30
+ # In a real scenario, the LLM would generate actual Scratch block JSON
31
+ if "Propose a high-level action flow" in user_message:
32
+ return {
33
+ "messages": [{
34
+ "content": json.dumps({
35
+ "action_overall_flow": {
36
+ "Sprite1": {
37
+ "description": "Basic movement and interaction",
38
+ "plans": [
39
+ {
40
+ "event": "when flag clicked",
41
+ "logic": "forever loop: move 10 steps, if touching Edge then turn 15 degrees"
42
+ },
43
+ {
44
+ "event": "when space key pressed",
45
+ "logic": "say Hello! for 2 seconds"
46
+ }
47
+ ]
48
+ },
49
+ "Ball": {
50
+ "description": "Simple bouncing behavior",
51
+ "plans": [
52
+ {
53
+ "event": "when flag clicked",
54
+ "logic": "move 5 steps, if on edge bounce"
55
+ }
56
+ ]
57
+ }
58
+ }
59
+ })
60
+ }]
61
+ }
62
+ elif "You are an AI assistant generating Scratch 3.0 block JSON" in user_message:
63
+ # This mock response is highly simplified. A real LLM would generate
64
+ # valid Scratch blocks based on the provided relevant catalog and plan.
65
+ # We're just demonstrating the *mechanism* of filtering the catalog.
66
+ if "Sprite1" in user_message:
67
+ return {
68
+ "messages": [{
69
+ "content": json.dumps({
70
+ f"block_id_{generate_block_id()}": {
71
+ "opcode": "event_whenflagclicked",
72
+ "next": f"block_id_{generate_block_id()}_forever",
73
+ "parent": None,
74
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 100, "y": 100
75
+ },
76
+ f"block_id_{generate_block_id()}_forever": {
77
+ "opcode": "control_forever",
78
+ "next": None,
79
+ "parent": f"block_id_{generate_block_id()}",
80
+ "inputs": {
81
+ "SUBSTACK": [2, f"block_id_{generate_block_id()}_move"]
82
+ }, "fields": {}, "shadow": False, "topLevel": False
83
+ },
84
+ f"block_id_{generate_block_id()}_move": {
85
+ "opcode": "motion_movesteps",
86
+ "next": f"block_id_{generate_block_id()}_if",
87
+ "parent": f"block_id_{generate_block_id()}_forever",
88
+ "inputs": {
89
+ "STEPS": [1, [4, "10"]]
90
+ }, "fields": {}, "shadow": False, "topLevel": False
91
+ },
92
+ f"block_id_{generate_block_id()}_if": {
93
+ "opcode": "control_if",
94
+ "next": None,
95
+ "parent": f"block_id_{generate_block_id()}_forever",
96
+ "inputs": {
97
+ "CONDITION": [2, f"block_id_{generate_block_id()}_touching"],
98
+ "SUBSTACK": [2, f"block_id_{generate_block_id()}_turn"]
99
+ }, "fields": {}, "shadow": False, "topLevel": False
100
+ },
101
+ f"block_id_{generate_block_id()}_touching": {
102
+ "opcode": "sensing_touchingobject",
103
+ "next": None,
104
+ "parent": f"block_id_{generate_block_id()}_if",
105
+ "inputs": {
106
+ "TOUCHINGOBJECTMENU": [1, f"block_id_{generate_block_id()}_touching_menu"]
107
+ }, "fields": {}, "shadow": False, "topLevel": False
108
+ },
109
+ f"block_id_{generate_block_id()}_touching_menu": {
110
+ "opcode": "sensing_touchingobjectmenu",
111
+ "next": None,
112
+ "parent": f"block_id_{generate_block_id()}_touching",
113
+ "inputs": {},
114
+ "fields": {"TOUCHINGOBJECTMENU": ["_edge_", None]},
115
+ "shadow": True, "topLevel": False
116
+ },
117
+ f"block_id_{generate_block_id()}_turn": {
118
+ "opcode": "motion_turnright",
119
+ "next": None,
120
+ "parent": f"block_id_{generate_block_id()}_if",
121
+ "inputs": {
122
+ "DEGREES": [1, [4, "15"]]
123
+ }, "fields": {}, "shadow": False, "topLevel": False
124
+ },
125
+ f"block_id_{generate_block_id()}_say": {
126
+ "opcode": "looks_sayforsecs",
127
+ "next": None,
128
+ "parent": None, # This block would typically be part of a separate script
129
+ "inputs": {
130
+ "MESSAGE": [1, [10, "Hello!"]],
131
+ "SECS": [1, [4, "2"]]
132
+ }, "fields": {}, "shadow": False, "topLevel": True, "x": 300, "y": 100
133
+ }
134
+ })
135
+ }]
136
+ }
137
+ elif "Ball" in user_message:
138
+ return {
139
+ "messages": [{
140
+ "content": json.dumps({
141
+ f"block_id_{generate_block_id()}": {
142
+ "opcode": "event_whenflagclicked",
143
+ "next": f"block_id_{generate_block_id()}_moveball",
144
+ "parent": None,
145
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 100, "y": 100
146
+ },
147
+ f"block_id_{generate_block_id()}_moveball": {
148
+ "opcode": "motion_movesteps",
149
+ "next": f"block_id_{generate_block_id()}_edgebounce",
150
+ "parent": f"block_id_{generate_block_id()}",
151
+ "inputs": {
152
+ "STEPS": [1, [4, "5"]]
153
+ }, "fields": {}, "shadow": False, "topLevel": False
154
+ },
155
+ f"block_id_{generate_block_id()}_edgebounce": {
156
+ "opcode": "motion_ifonedgebounce",
157
+ "next": None,
158
+ "parent": f"block_id_{generate_block_id()}_moveball",
159
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": False
160
+ }
161
+ })
162
+ }]
163
+ }
164
+ return {"messages": [{"content": "[]"}]} # Default empty response
165
+
166
+ agent = MockAgent()
167
+
168
+ # Helper function to generate a unique block ID
169
+ def generate_block_id():
170
+ return str(uuid.uuid4())[:10].replace('-', '') # Shorten for readability, ensure uniqueness
171
+
172
+ # Placeholder for your extract_json_from_llm_response function
173
+ def extract_json_from_llm_response(response_string):
174
+ try:
175
+ # Assuming the LLM response is ONLY the JSON string within triple backticks
176
+ json_match = response_string.strip().replace("```json", "").replace("```", "").strip()
177
+ return json.loads(json_match)
178
+ except json.JSONDecodeError as e:
179
+ logger.error(f"Failed to decode JSON from LLM response: {e}")
180
+ logger.error(f"Raw response: {response_string}")
181
+ raise ValueError("Invalid JSON response from LLM")
182
+
183
+ # --- GLOBAL CATALOG OF ALL SCRATCH BLOCKS ---
184
+ # This is where you would load your block_content.json
185
+ # For demonstration, I'm using your provided snippets and adding some common ones.
186
+ # In a real application, you'd load this once at startup.
187
+ ALL_SCRATCH_BLOCKS_CATALOG = {
188
+ "motion_movesteps": {
189
+ "opcode": "motion_movesteps", "next": None, "parent": None,
190
+ "inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 464, "y": -416
191
+ },
192
+ "motion_turnright": {
193
+ "opcode": "motion_turnright", "next": None, "parent": None,
194
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 467, "y": -316
195
+ },
196
+ "motion_ifonedgebounce": {
197
+ "opcode": "motion_ifonedgebounce", "next": None, "parent": None,
198
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 467, "y": -316
199
+ },
200
+ "event_whenflagclicked": {
201
+ "opcode": "event_whenflagclicked", "next": None, "parent": None,
202
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
203
+ },
204
+ "event_whenkeypressed": {
205
+ "opcode": "event_whenkeypressed", "next": None, "parent": None,
206
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": False, "topLevel": True, "x": 10, "y": 10
207
+ },
208
+ "control_forever": {
209
+ "opcode": "control_forever", "next": None, "parent": None,
210
+ "inputs": {"SUBSTACK": [2, "some_id"]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
211
+ },
212
+ "control_if": {
213
+ "opcode": "control_if", "next": None, "parent": None,
214
+ "inputs": {"CONDITION": [2, "some_id"], "SUBSTACK": [2, "some_id_2"]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
215
+ },
216
+ "looks_sayforsecs": {
217
+ "opcode": "looks_sayforsecs", "next": None, "parent": None,
218
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
219
+ },
220
+ "looks_say": {
221
+ "opcode": "looks_say", "next": None, "parent": None,
222
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
223
+ },
224
+ "sensing_touchingobject": {
225
+ "opcode": "sensing_touchingobject", "next": None, "parent": None,
226
+ "inputs": {"TOUCHINGOBJECTMENU": [1, "some_id"]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
227
+ },
228
+ "sensing_touchingobjectmenu": {
229
+ "opcode": "sensing_touchingobjectmenu", "next": None, "parent": None,
230
+ "inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": True, "x": 10, "y": 10
231
+ },
232
+ # Add more blocks from your block_content.json here...
233
+ }
234
+
235
+ # --- Heuristic-based block selection ---
236
+ def get_relevant_blocks_for_plan(action_plan: Dict[str, Any], all_blocks_catalog: Dict[str, Any]) -> Dict[str, Any]:
237
+ """
238
+ Analyzes the natural language action plan and selects relevant Scratch blocks
239
+ from the comprehensive catalog. This is a heuristic approach and might need
240
+ to be refined based on your specific use cases and LLM capabilities.
241
+ """
242
+ relevant_opcodes = set()
243
+
244
+ # Always include common event blocks
245
+ relevant_opcodes.add("event_whenflagclicked")
246
+ relevant_opcodes.add("event_whenkeypressed") # Could be more specific if key is mentioned
247
+
248
+ # Keyword to opcode mapping (can be expanded)
249
+ keyword_map = {
250
+ "move": "motion_movesteps",
251
+ "steps": "motion_movesteps",
252
+ "turn": "motion_turnright",
253
+ "rotate": "motion_turnright",
254
+ "bounce": "motion_ifonedgebounce",
255
+ "edge": "motion_ifonedgebounce",
256
+ "forever": "control_forever",
257
+ "loop": "control_forever",
258
+ "if": "control_if",
259
+ "condition": "control_if",
260
+ "say": "looks_say",
261
+ "hello": "looks_say", # Simple example, might need more context
262
+ "touching": "sensing_touchingobject",
263
+ "mouse pointer": "sensing_touchingobjectmenu",
264
+ "edge": "sensing_touchingobjectmenu", # For touching edge
265
+ }
266
+
267
+ # Iterate through the action plan to find keywords
268
+ for sprite_name, sprite_actions in action_plan.get("action_overall_flow", {}).items():
269
+ for plan in sprite_actions.get("plans", []):
270
+ event_logic = plan.get("event", "").lower() + " " + plan.get("logic", "").lower()
271
+
272
+ # Check for direct opcode matches (if the LLM somehow outputs opcodes in its plan)
273
+ for opcode in all_blocks_catalog.keys():
274
+ if opcode in event_logic:
275
+ relevant_opcodes.add(opcode)
276
+
277
+ # Check for keywords
278
+ for keyword, opcode in keyword_map.items():
279
+ if keyword in event_logic:
280
+ relevant_opcodes.add(opcode)
281
+ # Add associated shadow blocks if known
282
+ if opcode == "sensing_touchingobject":
283
+ relevant_opcodes.add("sensing_touchingobjectmenu")
284
+ if opcode == "event_whenkeypressed":
285
+ relevant_opcodes.add("event_whenkeypressed") # It's already there but good to be explicit
286
+
287
+ # Construct the filtered catalog
288
+ relevant_blocks_catalog = {
289
+ opcode: all_blocks_catalog[opcode]
290
+ for opcode in relevant_opcodes if opcode in all_blocks_catalog
291
+ }
292
+ return relevant_blocks_catalog
293
+
294
+ # --- New Action Planning Node ---
295
+ def plan_sprite_actions(state: GameState):
296
+ logger.info("--- Running PlanSpriteActionsNode ---")
297
+
298
+ planning_prompt = (
299
+ f"You are an AI assistant tasked with planning Scratch 3.0 block code for a game. "
300
+ f"The game description is: '{state['description']}'.\n\n"
301
+ f"Here are the sprites currently in the project: {', '.join(target['name'] for target in state['project_json']['targets'] if not target['isStage']) if len(state['project_json']['targets']) > 1 else 'None'}.\n"
302
+ f"Initial positions: {json.dumps(state.get('sprite_initial_positions', {}), indent=2)}\n\n"
303
+ f"Consider the main actions and interactions required for each sprite. "
304
+ f"Think step-by-step about what each sprite needs to *do*, *when* it needs to do it (events), "
305
+ f"and if any actions need to *repeat* or depend on *conditions*.\n\n"
306
+ f"Propose a high-level action flow for each sprite in the following JSON format. "
307
+ f"Do NOT generate Scratch block JSON yet. Only describe the logic using natural language or simplified pseudo-code.\n\n"
308
+ f"Example format:\n"
309
+ f"```json\n"
310
+ f"{{\n"
311
+ f" \"action_overall_flow\": {{\n"
312
+ f" \"Sprite1\": {{\n"
313
+ f" \"description\": \"Main character actions\",\n"
314
+ f" \"plans\": [\n"
315
+ f" {{\n"
316
+ f" \"event\": \"when flag clicked\",\n"
317
+ f" \"logic\": \"forever loop: move 10 steps, if on edge bounce\"\n"
318
+ f" }},\n"
319
+ f" {{\n"
320
+ f" \"event\": \"when space key pressed\",\n"
321
+ f" \"logic\": \"change y by 10, wait 0.1 seconds, change y by -10\"\n"
322
+ f" }}\n"
323
+ f" ]\n"
324
+ f" }},\n"
325
+ f" \"Ball\": {{\n"
326
+ f" \"description\": \"Projectile movement\",\n"
327
+ f" \"plans\": [\n"
328
+ f" {{\n"
329
+ f" \"event\": \"when I start as a clone\",\n"
330
+ f" \"logic\": \"glide 1 sec to random position, if touching Sprite1 then stop this script\"\n"
331
+ f" }}\n"
332
+ f" ]\n"
333
+ f" }}\n"
334
+ f" }}\n"
335
+ f"}}\n"
336
+ f"```\n\n"
337
+ f"Return ONLY the JSON object for the action overall flow."
338
+ )
339
+
340
+ try:
341
+ response = agent.invoke({"messages": [{"role": "user", "content": planning_prompt}]})
342
+ raw_response = response["messages"][-1].content
343
+ print("Raw response from LLM [PlanSpriteActionsNode]:", raw_response)
344
+ action_plan = extract_json_from_llm_response(raw_response)
345
+ logger.info("Sprite action plan generated by PlanSpriteActionsNode.")
346
+ return {"action_plan": action_plan}
347
+ except Exception as e:
348
+ logger.error(f"Error in PlanSpriteActionsNode: {e}")
349
+ raise
350
+
351
+ # --- Updated Action Node Builder (to consume the plan and build blocks) ---
352
+ def build_action_nodes(state: GameState):
353
+ logger.info("--- Running ActionNodeBuilder ---")
354
+
355
+ action_plan = state.get("action_plan", {})
356
+ if not action_plan:
357
+ raise ValueError("No action plan found in state. Run PlanSpriteActionsNode first.")
358
+
359
+ # Convert the Scratch project JSON to a mutable Python object
360
+ project_json = state["project_json"]
361
+ targets = project_json["targets"]
362
+
363
+ # We need a way to map sprite names to their actual target objects in project_json
364
+ sprite_map = {target["name"]: target for target in targets if not target["isStage"]}
365
+
366
+ # --- NEW: Get only the relevant blocks for the entire action plan ---
367
+ relevant_scratch_blocks_catalog = get_relevant_blocks_for_plan(action_plan, ALL_SCRATCH_BLOCKS_CATALOG)
368
+ logger.info(f"Filtered {len(relevant_scratch_blocks_catalog)} relevant blocks out of {len(ALL_SCRATCH_BLOCKS_CATALOG)} total.")
369
+
370
+
371
+ # Iterate through the planned actions for each sprite
372
+ for sprite_name, sprite_actions in action_plan.get("action_overall_flow", {}).items():
373
+ if sprite_name in sprite_map:
374
+ current_sprite_target = sprite_map[sprite_name]
375
+ # Ensure 'blocks' field exists for the sprite
376
+ if "blocks" not in current_sprite_target:
377
+ current_sprite_target["blocks"] = {}
378
+
379
+ # Generate block JSON based on the detailed action plan for this sprite
380
+ # This is where the LLM's role becomes crucial: translating logic to blocks
381
+ llm_block_generation_prompt = (
382
+ f"You are an AI assistant generating Scratch 3.0 block JSON based on a provided plan. "
383
+ f"The current sprite is '{sprite_name}'.\n"
384
+ f"Its planned actions are:\n"
385
+ f"```json\n{json.dumps(sprite_actions, indent=2)}\n```\n\n"
386
+ f"Here is a **curated catalog of only the most relevant Scratch 3.0 blocks** for this plan:\n"
387
+ f"```json\n{json.dumps(relevant_scratch_blocks_catalog, indent=2)}\n```\n\n"
388
+ f"Current Scratch project JSON (for context, specifically this sprite's existing blocks if any):\n"
389
+ f"```json\n{json.dumps(current_sprite_target, indent=2)}\n```\n\n"
390
+ f"**Instructions:**\n"
391
+ f"1. For each planned event and its associated logic, generate the corresponding Scratch 3.0 block JSON.\n"
392
+ f"2. **Generate unique block IDs** for every new block. Use a format like 'block_id_abcdef12'.\n" # Updated ID format hint
393
+ f"3. Properly link blocks using `next` and `parent` fields to form execution stacks. Hat blocks (`topLevel: true`, `parent: null`).\n"
394
+ f"4. Correctly fill `inputs` and `fields` based on the catalog and the plan's logic (e.g., specific values for motion, keys for events, conditions for controls).\n"
395
+ f"5. For C-blocks (like `control_repeat`, `control_forever`, `control_if`), use the `SUBSTACK` input to link to the first block inside its loop/conditional.\n"
396
+ f"6. If the plan involves operators (e.g., 'if touching Sprite1'), use the appropriate operator blocks from the catalog and link them correctly as `CONDITION` inputs.\n"
397
+ f"7. Ensure that any shadow blocks (e.g., for dropdowns like `motion_goto_menu`, `sensing_touchingobjectmenu`) are generated with `shadow: true` and linked correctly as inputs to their parent block.\n"
398
+ f"8. Return ONLY the **updated 'blocks' dictionary** for this specific sprite. Do NOT return the full project JSON. ONLY the `blocks` dictionary."
399
+ )
400
+
401
+ try:
402
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_block_generation_prompt}]})
403
+ raw_response = response["messages"][-1].content
404
+ print(f"Raw response from LLM [ActionNodeBuilder - {sprite_name}]:", raw_response)
405
+ generated_blocks = extract_json_from_llm_response(raw_response)
406
+ current_sprite_target["blocks"].update(generated_blocks) # Merge new blocks
407
+ logger.info(f"Action blocks added for sprite '{sprite_name}' by ActionNodeBuilder.")
408
+ except Exception as e:
409
+ logger.error(f"Error generating blocks for sprite '{sprite_name}': {e}")
410
+ # Depending on robustness needed, you might continue or re-raise
411
+ raise
412
+
413
+ return {"project_json": project_json}
414
+
415
+ # --- Example Usage (to demonstrate the flow) ---
416
+ if __name__ == "__main__":
417
+ # Initialize a mock game state
418
+ initial_game_state = {
419
+ "description": "A simple game where a sprite moves and says hello.",
420
+ "project_json": {
421
+ "targets": [
422
+ {"isStage": True, "name": "Stage", "blocks": {}},
423
+ {"isStage": False, "name": "Sprite1", "blocks": {}},
424
+ {"isStage": False, "name": "Ball", "blocks": {}}
425
+ ]
426
+ },
427
+ "sprite_initial_positions": {}
428
+ }
429
+
430
+ # Step 1: Plan Sprite Actions
431
+ try:
432
+ state_after_planning = plan_sprite_actions(initial_game_state)
433
+ initial_game_state.update(state_after_planning)
434
+ print("\n--- Game State After Planning ---")
435
+ print(json.dumps(initial_game_state, indent=2))
436
+ except Exception as e:
437
+ print(f"Planning failed: {e}")
438
+ exit()
439
+
440
+ # Step 2: Build Action Nodes (Generate Blocks)
441
+ try:
442
+ state_after_building = build_action_nodes(initial_game_state)
443
+ initial_game_state.update(state_after_building)
444
+ print("\n--- Game State After Building Blocks ---")
445
+ # Print only the blocks for a specific sprite to keep output manageable
446
+ for target in initial_game_state["project_json"]["targets"]:
447
+ if not target["isStage"]:
448
+ print(f"\nBlocks for {target['name']}:")
449
+ print(json.dumps(target.get('blocks', {}), indent=2))
450
+
451
+ except Exception as e:
452
+ print(f"Building blocks failed: {e}")
scratch_agent/agent.py ADDED
@@ -0,0 +1,1647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #─── Basic imports ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2
+ import os
3
+ import math
4
+ import sqlite3
5
+ import fitz # PyMuPDF for PDF parsing
6
+ import re
7
+
8
+ from dotenv import load_dotenv
9
+ # Load environment variables from .env file
10
+ load_dotenv() # This line ensures .env variables are loaded
11
+
12
+ from langgraph.graph import START, StateGraph, MessagesState, END
13
+ from langgraph.prebuilt import tools_condition
14
+ from langgraph.prebuilt import ToolNode
15
+ from langgraph.constants import START
16
+ from langchain_core.tools import tool
17
+ from langchain.schema import SystemMessage
18
+ #from langchain.chat_models import init_chat_model
19
+ #from langgraph.prebuilt import create_react_agent
20
+
21
+ from langchain.embeddings import HuggingFaceEmbeddings
22
+ #from langchain.vectorstores import Pinecone
23
+ from langchain.tools.retriever import create_retriever_tool
24
+ #import pinecone
25
+ #from pinecone import Pinecone as PineconeClient, ServerlessSpec
26
+ #from pinecone import Index # the blocking‐call client constructor
27
+ #from pinecone import Pinecone as PineconeClient, ServerlessSpec
28
+ from langchain.embeddings import HuggingFaceEmbeddings
29
+ from langchain_community.vectorstores.pinecone import Pinecone as LC_Pinecone
30
+
31
+ # ─── Langchain Frameworks ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
32
+ #from langchain.tools import Tool
33
+ from langchain.chat_models import ChatOpenAI
34
+ from langchain_groq import ChatGroq
35
+ from langchain_mistralai import ChatMistralAI
36
+ from langchain.agents import initialize_agent, AgentType
37
+ from langchain.schema import Document
38
+ from langchain.chains import RetrievalQA
39
+ from langchain.embeddings import OpenAIEmbeddings
40
+ from langchain_community.embeddings import HuggingFaceEmbeddings
41
+ from langchain.vectorstores import FAISS
42
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
43
+ from langchain.prompts import PromptTemplate
44
+ from langchain_community.document_loaders import TextLoader, PyMuPDFLoader
45
+ from langchain_community.document_loaders.wikipedia import WikipediaLoader
46
+ from langchain_community.document_loaders.arxiv import ArxivLoader
47
+ from langchain_experimental.tools.python.tool import PythonREPLTool
48
+
49
+
50
+ # ─── Memory ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
51
+ from langchain.agents import initialize_agent, AgentType
52
+ from langchain.tools import Tool
53
+ from typing import List, Callable
54
+ from langchain.schema import BaseMemory, AIMessage, HumanMessage, SystemMessage
55
+ from langchain.schema import HumanMessage, SystemMessage
56
+ from langchain.llms.base import LLM
57
+ from langchain.memory.chat_memory import BaseChatMemory
58
+ from pydantic import PrivateAttr
59
+ from langchain_core.messages import get_buffer_string
60
+
61
+ # ─── Image Processing ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
62
+
63
+ from PIL import Image
64
+ import pytesseract
65
+ from transformers import pipeline
66
+ from groq import Groq
67
+ import requests
68
+ from io import BytesIO
69
+ from transformers import pipeline, TrOCRProcessor, VisionEncoderDecoderModel
70
+ import requests
71
+ import base64
72
+ from PIL import UnidentifiedImageError
73
+
74
+ # ─── Browser var ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
75
+ from typing import List, Dict
76
+ import json
77
+ from io import BytesIO
78
+ #from langchain.tools import tool # or langchain_core.tools
79
+ from playwright.sync_api import sync_playwright
80
+ from duckduckgo_search import DDGS
81
+ import time
82
+ import random
83
+ import logging
84
+ from functools import lru_cache, wraps
85
+ import requests
86
+ from playwright.sync_api import sync_playwright
87
+ from bs4 import BeautifulSoup
88
+ import tenacity
89
+ from tenacity import retry, stop_after_attempt, wait_exponential
90
+
91
+ # Initialize logger
92
+ logger = logging.getLogger(__name__)
93
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
94
+
95
+ # Additional imports for new functionality
96
+ import pandas as pd
97
+ from PyPDF2 import PdfReader
98
+ import docx
99
+ import pytesseract
100
+ import speech_recognition as sr
101
+ from pydub import AudioSegment
102
+ from pytube import YouTube
103
+ from newspaper import Article
104
+ from langchain.document_loaders import ArxivLoader
105
+ from langchain_community.document_loaders.youtube import YoutubeLoader, TranscriptFormat
106
+
107
+ from playwright.sync_api import sync_playwright
108
+ # Attempt to import Playwright for dynamic page rendering
109
+ try:
110
+ from playwright.sync_api import sync_playwright
111
+ _playwright_available = True
112
+ except ImportError:
113
+ _playwright_available = False
114
+
115
+ # Define forbidden keywords for basic NSFW filtering
116
+ _forbidden = ["porn", "sex", "xxx", "nude", "erotic"]
117
+
118
+ # ─── LLM Setup ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
119
+ # Load OpenAI API key from environment (required for LLM and embeddings)
120
+
121
+ # API Keys from .env file
122
+ os.environ.setdefault("OPENAI_API_KEY", "<YOUR_OPENAI_KEY>") # Set your own key or env var
123
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY", "default_key_or_placeholder")
124
+ os.environ["MISTRAL_API_KEY"] = os.getenv("MISTRAL_API_KEY", "default_key_or_placeholder")
125
+
126
+ # Tavily API Key
127
+ TAVILY_API_KEY = os.getenv("TAVILY_API_KEY", "default_key_or_placeholder")
128
+ _forbidden = ["nsfw", "porn", "sex", "explicit"]
129
+ _playwright_available = True # set False to disable Playwright
130
+
131
+ # Globals for RAG system
132
+ vector_store = None
133
+ rag_chain = None
134
+ DB_PATH = None # will be set when a .db is uploaded
135
+ DOC_PATH = None # will be set when a document is uploaded
136
+ IMG_PATH = None # will be set when an image is uploaded
137
+ OTH_PATH = None # will be set when an other file is uploaded
138
+
139
+
140
+ # ─── LLMS ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
141
+ #llm = ChatOpenAI(model_name="gpt-3.5-turbo", streaming=True, temperature=0)
142
+ from tenacity import retry, stop_after_attempt, wait_exponential
143
+
144
+ # Import the RetryingChatGroq client
145
+ from retry_groq import RetryingChatGroq
146
+
147
+ # Use the retrying version instead
148
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", streaming=False, temperature=0)
149
+ #llm = ChatMistralAI(model="mistral-large-latest", streaming=True, temperature=0)
150
+
151
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
152
+ # ─────────────────────────────────────────────── Tool for multiply ──────────────────────────────────────────────────────────────────────
153
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
154
+ @tool(parse_docstring=True)
155
+ def multiply(a: int, b: int) -> int:
156
+ """
157
+ Multiply two numbers.
158
+
159
+ Args:
160
+ a (int): The first factor.
161
+ b (int): The second factor.
162
+
163
+ Returns:
164
+ int: The product of a and b.
165
+ """
166
+ try:
167
+ # Direct calculation without relying on LangChain handling
168
+ result = a * b
169
+ return result
170
+ except Exception as e:
171
+ return f"Error in multiplication: {str(e)}"
172
+
173
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
174
+ # ─────────────────────────────────────────────── Tool for add ──────────────────────────────────────────────────────────────────────────
175
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
176
+ @tool(parse_docstring=True)
177
+ def add(a: int, b: int) -> int:
178
+ """
179
+ Add two numbers.
180
+
181
+ Args:
182
+ a (int): The first factor.
183
+ b (int): The second factor.
184
+
185
+ Returns:
186
+ int: The addition of a and b.
187
+ """
188
+ try:
189
+ # Direct calculation without relying on LangChain handling
190
+ result = a + b
191
+ return result
192
+ except Exception as e:
193
+ return f"Error in addition: {str(e)}"
194
+
195
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
196
+ # ─────────────────────────────────────────────── Tool for subtract ──────────────────────────────────────────────────────────────────────
197
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
198
+ @tool(parse_docstring=True)
199
+ def subtract(a: int, b: int) -> int:
200
+ """
201
+ Subtract two numbers.
202
+
203
+ Args:
204
+ a (int): The first factor.
205
+ b (int): The second factor.
206
+
207
+ Returns:
208
+ int: The subtraction of a and b.
209
+ """
210
+ try:
211
+ # Direct calculation without relying on LangChain handling
212
+ result = a - b
213
+ return result
214
+ except Exception as e:
215
+ return f"Error in subtraction: {str(e)}"
216
+
217
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
218
+ # ─────────────────────────────────────────────── Tool for divide ──────────────────────────────────────────────────────────────────────
219
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
220
+ @tool(parse_docstring=True)
221
+ def divide(a: int, b: int) -> int:
222
+ """
223
+ Divide two numbers.
224
+
225
+ Args:
226
+ a (int): The numerator.
227
+ b (int): The denominator.
228
+
229
+ Returns:
230
+ float: The result of a divided by b.
231
+
232
+ Raises:
233
+ ValueError: If b is zero.
234
+ """
235
+ try:
236
+ if b == 0:
237
+ return "Error: Cannot divide by zero."
238
+ # Direct calculation without relying on LangChain handling
239
+ result = a / b
240
+ return result
241
+ except Exception as e:
242
+ return f"Error in division: {str(e)}"
243
+
244
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────���───────────────────────────────────────
245
+ # ─────────────────────────────────────────────── Tool for modulus ──────────────────────────────────────────────────────────────────────
246
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
247
+ @tool(parse_docstring=True)
248
+ def modulus(a: int, b: int) -> int:
249
+ """
250
+ Get the modulus (remainder) of two numbers.
251
+
252
+ Args:
253
+ a (int): The dividend.
254
+ b (int): The divisor.
255
+
256
+ Returns:
257
+ int: The remainder when a is divided by b.
258
+ """
259
+ try:
260
+ if b == 0:
261
+ return "Error: Cannot calculate modulus with zero divisor."
262
+ # Direct calculation without relying on LangChain handling
263
+ result = a % b
264
+ return result
265
+ except Exception as e:
266
+ return f"Error in modulus calculation: {str(e)}"
267
+
268
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
269
+ # ─────────────────────────────────────────────── Tool for browsing ──────────────────────────────────────────────────────────────────────
270
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
271
+ def with_retry(max_attempts: int = 3, backoff_base: int = 2):
272
+ """
273
+ Decorator for retrying a function with exponential backoff on exception.
274
+ """
275
+ def decorator(fn):
276
+ @wraps(fn)
277
+ def wrapper(*args, **kwargs):
278
+ for attempt in range(max_attempts):
279
+ try:
280
+ return fn(*args, **kwargs)
281
+ except Exception as e:
282
+ wait = backoff_base ** attempt + random.uniform(0, 1)
283
+ logger.warning(f"{fn.__name__} failed (attempt {attempt+1}/{max_attempts}): {e}")
284
+ if attempt < max_attempts - 1:
285
+ time.sleep(wait)
286
+ logger.error(f"{fn.__name__} failed after {max_attempts} attempts.")
287
+ return []
288
+ return wrapper
289
+ return decorator
290
+
291
+ @with_retry()
292
+ @lru_cache(maxsize=128)
293
+ def tavily_search(query: str, top_k: int = 3) -> List[Dict]:
294
+ """Call Tavily API and return a list of result dicts."""
295
+ if not TAVILY_API_KEY:
296
+ logger.info("[Tavily] No API key set. Skipping Tavily search.")
297
+ return []
298
+ url = "https://api.tavily.com/search"
299
+ headers = {
300
+ "Authorization": f"Bearer {TAVILY_API_KEY}",
301
+ "Content-Type": "application/json",
302
+ }
303
+ payload = {"query": query, "num_results": top_k}
304
+ resp = requests.post(url, headers=headers, json=payload, timeout=10)
305
+ resp.raise_for_status()
306
+ data = resp.json()
307
+ results = []
308
+ for item in data.get("results", []):
309
+ results.append({
310
+ "title": item.get("title", ""),
311
+ "url": item.get("url", ""),
312
+ "content": item.get("content", "")[:200],
313
+ "source": "Tavily"
314
+ })
315
+ return results
316
+
317
+ @with_retry()
318
+ @lru_cache(maxsize=128)
319
+ def duckduckgo_search(query: str, top_k: int = 3) -> List[Dict]:
320
+ """Query DuckDuckGo and return up to top_k raw SERP hits."""
321
+ results = []
322
+ try:
323
+ with DDGS(timeout=15) as ddgs: # Increase timeout from default
324
+ for hit in ddgs.text(query, safesearch="On", max_results=top_k, timeout=15):
325
+ results.append({
326
+ "title": hit.get("title", ""),
327
+ "url": hit.get("href") or hit.get("url", ""),
328
+ "content": hit.get("body", ""),
329
+ "source": "DuckDuckGo"
330
+ })
331
+ if len(results) >= top_k:
332
+ break
333
+ except Exception as e:
334
+ logger.warning(f"DuckDuckGo search failed: {e}")
335
+ # Don't re-raise - just return empty results to allow fallbacks to work
336
+
337
+ return results
338
+
339
+ # Additional fallback search alternative
340
+ def simple_google_search(query: str, top_k: int = 3) -> List[Dict]:
341
+ """Simplified Google search as a fallback when other methods fail."""
342
+ try:
343
+ # Encode the query
344
+ import urllib.parse
345
+ import bs4
346
+
347
+ encoded_query = urllib.parse.quote(query)
348
+ url = f"https://www.google.com/search?q={encoded_query}"
349
+
350
+ headers = {
351
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
352
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
353
+ "Accept-Language": "en-US,en;q=0.5",
354
+ "Referer": "https://www.google.com/",
355
+ "Connection": "keep-alive",
356
+ }
357
+
358
+ response = requests.get(url, headers=headers, timeout=20)
359
+ response.raise_for_status()
360
+
361
+ soup = bs4.BeautifulSoup(response.text, "html.parser")
362
+ results = []
363
+
364
+ # Extract search results
365
+ for result in soup.select("div.g")[:top_k]:
366
+ title_elem = result.select_one("h3")
367
+ link_elem = result.select_one("a")
368
+ snippet_elem = result.select_one("div.VwiC3b")
369
+
370
+ if title_elem and link_elem and snippet_elem and "href" in link_elem.attrs:
371
+ href = link_elem["href"]
372
+ if href.startswith("/url?q="):
373
+ href = href.split("/url?q=")[1].split("&")[0]
374
+
375
+ if href.startswith("http"):
376
+ results.append({
377
+ "title": title_elem.get_text(),
378
+ "url": href,
379
+ "content": snippet_elem.get_text(),
380
+ "source": "Google"
381
+ })
382
+
383
+ return results
384
+
385
+ except Exception as e:
386
+ logger.warning(f"Simple Google search failed: {e}")
387
+ return []
388
+
389
+ def hybrid_search(query: str, top_k: int = 3) -> List[Dict]:
390
+ """Combine multiple search sources with fallbacks."""
391
+ # Try primary search methods first
392
+ results = []
393
+
394
+ # Start with Tavily if API key is available
395
+ if TAVILY_API_KEY and TAVILY_API_KEY != "default_key_or_placeholder":
396
+ try:
397
+ tavily_results = tavily_search(query, top_k)
398
+ results.extend(tavily_results)
399
+ logger.info(f"Retrieved {len(tavily_results)} results from Tavily")
400
+ except Exception as e:
401
+ logger.warning(f"Tavily search failed: {e}")
402
+
403
+ # If we don't have enough results, try DuckDuckGo
404
+ if len(results) < top_k:
405
+ try:
406
+ ddg_results = duckduckgo_search(query, top_k - len(results))
407
+ results.extend(ddg_results)
408
+ logger.info(f"Retrieved {len(ddg_results)} results from DuckDuckGo")
409
+ except Exception as e:
410
+ logger.warning(f"DuckDuckGo search failed: {e}")
411
+
412
+ # If we still don't have enough results, try Google
413
+ if len(results) < top_k:
414
+ try:
415
+ google_results = simple_google_search(query, top_k - len(results))
416
+ results.extend(google_results)
417
+ logger.info(f"Retrieved {len(google_results)} results from Google")
418
+ except Exception as e:
419
+ logger.warning(f"Google search failed: {e}")
420
+
421
+ # If all search methods failed, return a dummy result
422
+ if not results:
423
+ results.append({
424
+ "title": "Search Failed",
425
+ "url": "",
426
+ "content": f"Sorry, I couldn't find results for '{query}'. Please try refining your search terms or check your internet connection.",
427
+ "source": "No results"
428
+ })
429
+
430
+ return results[:top_k] # Ensure we only return top_k results
431
+
432
+ def format_search_docs(search_docs: List[Dict]) -> Dict[str, str]:
433
+ """
434
+ Turn a list of {source, page, content} dicts into one big
435
+ string with <Document ...>…</Document> entries separated by `---`.
436
+ """
437
+ formatted_search_docs = "\n\n---\n\n".join(
438
+ [
439
+ f'<Document source="{doc["source"]}" page="{doc.get("page", "")}"/>\n'
440
+ f'{doc.get("content", "")}\n'
441
+ f'</Document>'
442
+ for doc in search_docs
443
+ ]
444
+ )
445
+ return {"web_results": formatted_search_docs}
446
+
447
+
448
+ @tool(parse_docstring=True)
449
+ def web_search(query: str, top_k: int = 3) -> Dict[str, str]:
450
+ """
451
+ Perform a hybrid web search combining multiple search engines with robust fallbacks.
452
+
453
+ Args:
454
+ query: The search query string to look up.
455
+ top_k: The maximum number of search results to return (default is 3).
456
+
457
+ Returns:
458
+ A dictionary mapping result indices to XML-like <Document> blocks, each containing:
459
+ - source: The URL of the webpage.
460
+ - page: Placeholder for page identifier (empty string by default).
461
+ - content: The first 200 words of the page text, cleaned of HTML tags.
462
+ """
463
+ try:
464
+ # Use our robust hybrid search to get initial results
465
+ search_results = hybrid_search(query, top_k)
466
+ results = []
467
+
468
+ # Process each search result to get better content
469
+ for hit in search_results:
470
+ url = hit.get("url")
471
+ if not url:
472
+ continue
473
+
474
+ # Start with the snippet from search
475
+ content = hit.get("content", "")
476
+ title = hit.get("title", "")
477
+
478
+ # Try to scrape additional content if possible
479
+ try:
480
+ # Use a random user agent to avoid blocking
481
+ headers = {
482
+ "User-Agent": random.choice([
483
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
484
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15",
485
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",
486
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.62"
487
+ ]),
488
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
489
+ "Accept-Language": "en-US,en;q=0.5",
490
+ "Referer": "https://www.google.com/",
491
+ "DNT": "1",
492
+ "Connection": "keep-alive"
493
+ }
494
+
495
+ # Higher timeout for better reliability
496
+ resp = requests.get(url, timeout=15, headers=headers)
497
+
498
+ # Only process if successful
499
+ if resp.status_code == 200:
500
+ soup = BeautifulSoup(resp.text, "html.parser")
501
+
502
+ # Try to find main content
503
+ main_content = soup.find('main') or soup.find('article') or soup.find('div', class_='content')
504
+
505
+ # If we found main content, use it
506
+ if main_content:
507
+ extracted_text = main_content.get_text(separator=" ", strip=True)
508
+ # Take first 200 words
509
+ content = " ".join(extracted_text.split()[:200])
510
+ else:
511
+ # Otherwise use all text
512
+ all_text = soup.get_text(separator=" ", strip=True)
513
+ content = " ".join(all_text.split()[:200])
514
+
515
+ # Use content from page only if it's substantial
516
+ if len(content) < 50:
517
+ content = hit.get("content", "")[:200]
518
+
519
+ # Random delay between 0.5-1.5 seconds to avoid rate limits
520
+ time.sleep(0.5 + random.random())
521
+
522
+ except requests.exceptions.HTTPError as e:
523
+ logger.warning(f"HTTP error when scraping {url}: {e}")
524
+ # Keep the search snippet as a fallback
525
+ except requests.exceptions.RequestException as e:
526
+ logger.warning(f"Request error when scraping {url}: {e}")
527
+ # Keep the search snippet as a fallback
528
+ except Exception as e:
529
+ logger.warning(f"Unexpected error when scraping {url}: {e}")
530
+ # Keep the search snippet as a fallback
531
+
532
+ # Filter out inappropriate content
533
+ if any(f in content.lower() for f in _forbidden):
534
+ continue
535
+
536
+ # Add to results
537
+ results.append({
538
+ "source": url,
539
+ "page": "",
540
+ "content": content
541
+ })
542
+
543
+ # Return formatted search docs
544
+ return format_search_docs(results[:top_k])
545
+ except Exception as e:
546
+ logger.error(f"Web search failed: {e}")
547
+ # Return a helpful error message
548
+ return format_search_docs([{
549
+ "source": "Error",
550
+ "page": "",
551
+ "content": f"Search failed with error: {e}. Please try again with different search terms."
552
+ }])
553
+
554
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
555
+ # ─────────────────────────────────────────────── Tool for File System ───────────────────────────────────────────────────────────────────
556
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
557
+ @tool(parse_docstring=True)
558
+ def download_file(url: str, dest_path: str) -> str:
559
+ """
560
+ Download a file from a given URL and save it locally.
561
+
562
+ Args:
563
+ url: The direct URL of the file to download.
564
+ dest_path: The local path to save the downloaded file.
565
+
566
+ Returns:
567
+ The destination path where the file was saved.
568
+ """
569
+ r = requests.get(url, stream=True)
570
+ r.raise_for_status()
571
+ with open(dest_path, 'wb') as f:
572
+ for chunk in r.iter_content(8192):
573
+ f.write(chunk)
574
+ return dest_path
575
+
576
+ @tool(parse_docstring=True)
577
+ def process_excel_to_text(file_path: str) -> str:
578
+ """
579
+ Convert an Excel file into CSV-formatted text.
580
+
581
+ Args:
582
+ file_path: Path to the Excel (.xlsx) file.
583
+
584
+ Returns:
585
+ A string of CSV-formatted content extracted from the Excel file.
586
+ """
587
+ try:
588
+ # Check if file exists
589
+ import os
590
+ if not os.path.exists(file_path):
591
+ return f"Error: Excel file '{file_path}' does not exist."
592
+
593
+ # Try different engines
594
+ engines = ['openpyxl', 'xlrd', None]
595
+
596
+ for engine in engines:
597
+ try:
598
+ # For engine=None, pandas will try to auto-detect
599
+ if engine:
600
+ df = pd.read_excel(file_path, engine=engine)
601
+ else:
602
+ df = pd.read_excel(file_path)
603
+ return df.to_csv(index=False)
604
+ except Exception as e:
605
+ print(f"Excel engine {engine} failed: {e}")
606
+ last_error = e
607
+ continue
608
+
609
+ # If we got here, all engines failed
610
+ return f"Error processing Excel file: {str(last_error)}"
611
+ except Exception as e:
612
+ return f"Error with Excel file: {str(e)}"
613
+
614
+ @tool(parse_docstring=True)
615
+ def read_text_from_pdf(file_path: str, question: str = None) -> str:
616
+ """
617
+ Extract text from a PDF file, chunking large documents if needed.
618
+
619
+ Args:
620
+ file_path: Path to the PDF file.
621
+ question: Optional question to help retrieve relevant parts of long documents.
622
+
623
+ Returns:
624
+ The extracted text content, potentially chunked if the document is large.
625
+ """
626
+ try:
627
+ # Check if file exists
628
+ import os
629
+ if not os.path.exists(file_path):
630
+ return f"Error: PDF file '{file_path}' does not exist."
631
+
632
+ reader = PdfReader(file_path)
633
+ full_text = "\n".join([page.extract_text() or "" for page in reader.pages])
634
+
635
+ # If a question is provided, use retrieval to get relevant parts
636
+ if question and len(full_text) > 5000: # Only chunk if text is large
637
+ return process_large_document(full_text, question)
638
+
639
+ return full_text
640
+ except Exception as e:
641
+ return f"Error reading PDF: {str(e)}"
642
+
643
+ @tool(parse_docstring=True)
644
+ def read_text_from_docx(file_path: str, question: str = None) -> str:
645
+ """
646
+ Extract text from a DOCX (Word) document, chunking large documents if needed.
647
+
648
+ Args:
649
+ file_path: Path to the DOCX file.
650
+ question: Optional question to help retrieve relevant parts of long documents.
651
+
652
+ Returns:
653
+ The extracted text, potentially chunked if the document is large.
654
+ """
655
+ try:
656
+ # Check if file exists
657
+ import os
658
+ if not os.path.exists(file_path):
659
+ return f"Error: File '{file_path}' does not exist."
660
+
661
+ try:
662
+ doc = docx.Document(file_path)
663
+ full_text = "\n".join([para.text for para in doc.paragraphs])
664
+ except Exception as docx_err:
665
+ # Handle "Package not found" error specifically
666
+ if "Package not found" in str(docx_err):
667
+ # Try to read raw text if possible
668
+ try:
669
+ import zipfile
670
+ from xml.etree.ElementTree import XML
671
+
672
+ WORD_NAMESPACE = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
673
+ PARA = WORD_NAMESPACE + 'p'
674
+ TEXT = WORD_NAMESPACE + 't'
675
+
676
+ with zipfile.ZipFile(file_path) as docx_file:
677
+ with docx_file.open('word/document.xml') as document:
678
+ tree = XML(document.read())
679
+ paragraphs = []
680
+ for paragraph in tree.iter(PARA):
681
+ texts = [node.text for node in paragraph.iter(TEXT) if node.text]
682
+ if texts:
683
+ paragraphs.append(''.join(texts))
684
+ full_text = '\n'.join(paragraphs)
685
+ except Exception as e:
686
+ return f"Error reading DOCX file: {str(e)}"
687
+ else:
688
+ return f"Error reading DOCX file: {str(docx_err)}"
689
+
690
+ # If a question is provided, use retrieval to get relevant parts
691
+ if question and len(full_text) > 5000: # Only chunk if text is large
692
+ return process_large_document(full_text, question)
693
+
694
+ return full_text
695
+ except Exception as e:
696
+ return f"Error reading DOCX file: {str(e)}"
697
+
698
+
699
+ @tool(parse_docstring=True)
700
+ def transcribe_audio(file_path: str) -> str:
701
+ """
702
+ Transcribe speech from a local audio file to text.
703
+
704
+ Args:
705
+ file_path: Path to the audio file.
706
+
707
+ Returns:
708
+ Transcribed text using Google Web Speech API.
709
+ """
710
+ try:
711
+ # Check if file exists
712
+ import os
713
+ if not os.path.exists(file_path):
714
+ return f"Error: Audio file '{file_path}' does not exist."
715
+
716
+ # For non-WAV files, convert to WAV first
717
+ if not file_path.lower().endswith('.wav'):
718
+ try:
719
+ from pydub import AudioSegment
720
+ temp_wav = os.path.splitext(file_path)[0] + "_temp.wav"
721
+ audio = AudioSegment.from_file(file_path)
722
+ audio.export(temp_wav, format="wav")
723
+ file_path = temp_wav
724
+ except Exception as e:
725
+ return f"Failed to convert audio to WAV format: {str(e)}"
726
+
727
+ recognizer = sr.Recognizer()
728
+ with sr.AudioFile(file_path) as src:
729
+ audio = recognizer.record(src)
730
+ return recognizer.recognize_google(audio)
731
+ except Exception as e:
732
+ if "Audio file could not be read" in str(e):
733
+ return f"Error: Audio format not supported. Try converting to WAV, MP3, OGG, or FLAC."
734
+ return f"Error transcribing audio: {str(e)}"
735
+
736
+ @tool(parse_docstring=True)
737
+ def youtube_audio_processing(youtube_url: str) -> str:
738
+ """
739
+ Download and transcribe audio from a YouTube video.
740
+
741
+ Args:
742
+ youtube_url: URL of the YouTube video.
743
+
744
+ Returns:
745
+ Transcription text extracted from the video's audio.
746
+ """
747
+ yt = YouTube(youtube_url)
748
+ audio_stream = yt.streams.filter(only_audio=True).first()
749
+ out_file = audio_stream.download(output_path='.', filename='yt_audio')
750
+ wav_path = 'yt_audio.wav'
751
+ AudioSegment.from_file(out_file).export(wav_path, format='wav')
752
+ return transcribe_audio(wav_path)
753
+
754
+ @tool(parse_docstring=True)
755
+ def extract_article_text(url: str, question: str = None) -> str:
756
+ """
757
+ Download and extract the main article content from a webpage, chunking large articles if needed.
758
+
759
+ Args:
760
+ url: The URL of the article to extract.
761
+ question: Optional question to help retrieve relevant parts of long articles.
762
+
763
+ Returns:
764
+ The article's textual content, potentially chunked if large.
765
+ """
766
+ try:
767
+ art = Article(url)
768
+ art.download()
769
+ art.parse()
770
+ full_text = art.text
771
+
772
+ # If a question is provided, use retrieval to get relevant parts
773
+ if question and len(full_text) > 5000: # Only chunk if text is large
774
+ return process_large_document(full_text, question)
775
+
776
+ return full_text
777
+ except Exception as e:
778
+ return f"Error extracting article: {str(e)}"
779
+
780
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
781
+ # ───────────────────────────────────────────────────────────── Tool for ArXiv ────────────────────────────────────────────────────────────
782
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
783
+
784
+ @tool(parse_docstring=True)
785
+ def arvix_search(query: str) -> Dict[str, str]:
786
+ """
787
+ Search for academic papers on ArXiv.
788
+
789
+ Args:
790
+ query: The search term to look for in ArXiv.
791
+
792
+ Returns:
793
+ A dictionary of up to 3 relevant paper entries in JSON format.
794
+ """
795
+ papers = ArxivLoader(query=query, load_max_docs=3).load()
796
+ results = []
797
+ for doc in papers:
798
+ try:
799
+ # Handle different metadata formats that might be returned
800
+ source = doc.metadata.get("source", "ArXiv")
801
+ doc_id = doc.metadata.get("id", doc.metadata.get("entry_id", ""))
802
+ result = {
803
+ "source": source,
804
+ "id": doc_id,
805
+ "summary": doc.page_content[:1000] if hasattr(doc, "page_content") else str(doc)[:1000],
806
+ }
807
+ results.append(result)
808
+ except Exception as e:
809
+ # Add error information as a fallback
810
+ results.append({
811
+ "source": "ArXiv Error",
812
+ "id": "error",
813
+ "summary": f"Error processing paper: {str(e)}"
814
+ })
815
+
816
+ return {"arvix_results": json.dumps(results)}
817
+
818
+ @tool(parse_docstring=True)
819
+ def answer_youtube_video_question(
820
+ youtube_url: str,
821
+ question: str,
822
+ chunk_size_seconds: int = 30
823
+ ) -> str:
824
+ """
825
+ Answer a question based on a YouTube video's transcript.
826
+
827
+ Args:
828
+ youtube_url: URL of the YouTube video.
829
+ question: The question to be answered using video content.
830
+ chunk_size_seconds: Duration of each transcript chunk.
831
+
832
+ Returns:
833
+ The answer to the question generated from the video transcript.
834
+ """
835
+ loader = YoutubeLoader.from_youtube_url(
836
+ youtube_url,
837
+ add_video_info=True,
838
+ transcript_format=TranscriptFormat.CHUNKS,
839
+ chunk_size_seconds=chunk_size_seconds,
840
+ )
841
+ documents = loader.load()
842
+ embeddings = HuggingFaceEmbeddings(model_name='sentence-transformers/all-mpnet-base-v2')
843
+ vectorstore = FAISS.from_documents(documents, embeddings)
844
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", streaming=False)
845
+ qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=vectorstore.as_retriever())
846
+ return qa_chain.run(question)
847
+
848
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
849
+ # ───────────────────────────────────────────────────────────── Tool for Python REPL tool ────────────────────────────────────────────────
850
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
851
+
852
+ python_repl = PythonREPLTool()
853
+
854
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
855
+ # ───────────────────────────────────────────────────────────── Tool for Wiki ────────────────────────────────────────────────────────────
856
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
857
+
858
+ @tool(parse_docstring=True)
859
+ def wiki_search(query: str) -> str:
860
+ """
861
+ Search Wikipedia for information on a given topic.
862
+
863
+ Args:
864
+ query: The search term for Wikipedia.
865
+
866
+ Returns:
867
+ A JSON string with up to 3 summary results.
868
+ """
869
+ # load up to top_k pages
870
+ pages = WikipediaLoader(query=query, load_max_docs=3).load()
871
+ results: List[Dict] = []
872
+ for doc in pages:
873
+ results.append({
874
+ "source": doc.metadata["source"],
875
+ "page": doc.metadata.get("page", ""),
876
+ "content": doc.page_content[:1000], # truncate if you like
877
+ })
878
+ return {"wiki_results": format_search_docs(results)}
879
+
880
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
881
+ # ───────────────────────────────────── Tool for Image (understading, captioning & classification) ─────────────────────────────────────────
882
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
883
+
884
+ def _load_image(img_path: str, resize_to=(512, 512)) -> Image.Image:
885
+ """
886
+ Load, verify, convert, and resize an image.
887
+ Raises ValueError on failure.
888
+ """
889
+ if not img_path:
890
+ raise ValueError("No image path provided.")
891
+ try:
892
+ with Image.open(img_path) as img:
893
+ img.verify()
894
+ img = Image.open(img_path).convert("RGB")
895
+ img = img.resize(resize_to)
896
+ return img
897
+ except UnidentifiedImageError:
898
+ raise ValueError(f"File at {img_path} is not a valid image.")
899
+ except Exception as e:
900
+ raise ValueError(f"Failed to load image at {img_path}: {e}")
901
+
902
+ def _encode_image_to_base64(img_path: str) -> str:
903
+ """
904
+ Load an image, save optimized PNG into memory, and base64‑encode it.
905
+ """
906
+ img = _load_image(img_path)
907
+ buffer = BytesIO()
908
+ img.save(buffer, format="PNG", optimize=True)
909
+ return base64.b64encode(buffer.getvalue()).decode("utf-8")
910
+
911
+ @tool
912
+ def image_processing(prompt: str, img_path: str) -> str:
913
+ """Process an image using a vision LLM, with OCR fallback.
914
+
915
+ Args:
916
+ prompt: Instruction or question related to the image.
917
+ img_path: Path to the image file.
918
+
919
+ Returns:
920
+ The model's response or fallback OCR result.
921
+ """
922
+ try:
923
+ import os
924
+ # Check if file exists
925
+ if not os.path.exists(img_path):
926
+ return f"Error: Image file '{img_path}' does not exist."
927
+
928
+ try:
929
+ b64 = _encode_image_to_base64(img_path)
930
+ # Build a single markdown string with inline base64 image
931
+ md = f"{prompt}\n\n![](data:image/png;base64,{b64})"
932
+ message = HumanMessage(content=md)
933
+ # Use RetryingChatGroq with Llama 4 Maverick for vision
934
+ llm = RetryingChatGroq(model="meta-llama/llama-4-maverick-17b-128e-instruct", streaming=False, temperature=0)
935
+ try:
936
+ resp = llm.invoke([message])
937
+ if hasattr(resp, 'content'):
938
+ return resp.content.strip()
939
+ elif isinstance(resp, str):
940
+ return resp.strip()
941
+ else:
942
+ # Handle dictionary or other response types
943
+ return str(resp)
944
+ except Exception as invoke_err:
945
+ print(f"[LLM invoke error] {invoke_err}")
946
+ # Fall back to OCR
947
+ raise ValueError("LLM invocation failed")
948
+ except Exception as llama_err:
949
+ print(f"[LLM vision failed] {llama_err}")
950
+ try:
951
+ img = _load_image(img_path)
952
+ return pytesseract.image_to_string(img).strip()
953
+ except Exception as ocr_err:
954
+ print(f"[OCR fallback failed] {ocr_err}")
955
+ return "Unable to process the image. Please check the file and try again."
956
+ except Exception as e:
957
+ # Catch any other errors
958
+ print(f"[image_processing error] {e}")
959
+ return f"Error processing image: {str(e)}"
960
+
961
+ python_repl_tool = PythonREPLTool()
962
+
963
+ @tool
964
+ def echo(text: str) -> str:
965
+ """Echo back the input text.
966
+
967
+ Args:
968
+ text: The string to be echoed.
969
+
970
+ Returns:
971
+ The same text that was provided as input.
972
+ """
973
+ return text
974
+
975
+ # ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────��───────────────────
976
+ # ─────────────────────────────────────────────── Langgraph Agent ───────────────────────────────────────────────────────────────────────
977
+ # ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
978
+
979
+
980
+ # Build graph function
981
+ from langchain_core.tools import tool
982
+ from langchain.chat_models import ChatOpenAI
983
+ from langgraph.prebuilt.chat_agent_executor import create_react_agent, AgentState
984
+ from langchain.chat_models import init_chat_model
985
+
986
+
987
+
988
+ def build_graph(provider: str = "groq"):
989
+ """Construct and compile the multi‑agent GAIA workflow StateGraph.
990
+
991
+ This graph wires together three React‑style agents into a streamlined pipeline:
992
+ PerceptionAgent → ActionAgent → EvaluationAgent (with appropriate entry/exit points)
993
+
994
+ The agents have the following responsibilities:
995
+ - PerceptionAgent: Handles web searches, Wikipedia, ArXiv, and image processing
996
+ - ActionAgent: Performs calculations, file operations, and code analysis
997
+ - EvaluationAgent: Reviews results and ensures the final answer is properly formatted
998
+
999
+ Args:
1000
+ provider: The name of the LLM provider. Must be "groq".
1001
+
1002
+ Returns:
1003
+ CompiledGraph: A compiled LangGraph state machine ready for invocation.
1004
+
1005
+ Raises:
1006
+ ValueError: If `provider` is anything other than "groq".
1007
+ """
1008
+ try:
1009
+ if provider != "groq":
1010
+ raise ValueError("Invalid provider. Expected 'groq'.")
1011
+
1012
+ # Initialize LLM
1013
+ try:
1014
+ logger.info("Initializing LLM with model: deepseek-r1-distill-llama-70b")
1015
+ api_key = os.getenv("GROQ_API_KEY")
1016
+ if not api_key or api_key == "default_key_or_placeholder":
1017
+ logger.error("GROQ_API_KEY is not set or is using placeholder value")
1018
+ raise ValueError("GROQ_API_KEY environment variable is not set properly. Please set a valid API key.")
1019
+
1020
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", temperature=0)
1021
+ logger.info("LLM initialized successfully")
1022
+ except Exception as e:
1023
+ logger.error(f"Error initializing LLM: {str(e)}")
1024
+ raise
1025
+
1026
+ # General system message for agents
1027
+ sys_msg = SystemMessage(content="""
1028
+ You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template:
1029
+
1030
+ FINAL ANSWER: [YOUR FINAL ANSWER]
1031
+
1032
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma-separated list of numbers and/or strings.
1033
+
1034
+ If you are asked for a number, don't use commas or units (e.g., $, %, kg) unless specified otherwise.
1035
+
1036
+ If you are asked for a string, don't use articles (a, an, the), and don't use abbreviations (e.g., for states).
1037
+
1038
+ If you are asked for a comma-separated list, apply the above rules to each element in the list.
1039
+ """.strip())
1040
+
1041
+ # Special system message for the evaluation agent with stricter formatting requirements
1042
+ eval_sys_msg = SystemMessage(content="""
1043
+ You are a specialized evaluation agent. Your job is to review the work done by other agents
1044
+ and provide a final, properly formatted answer.
1045
+
1046
+ IMPORTANT: You MUST ALWAYS format your answer using this exact template:
1047
+
1048
+ FINAL ANSWER: [concise answer]
1049
+
1050
+ Rules for formatting the answer:
1051
+ 1. The answer must be extremely concise - use as few words as possible
1052
+ 2. For numeric answers, provide only the number without units unless units are specifically requested
1053
+ 3. For text answers, avoid articles (a, an, the) and unnecessary words
1054
+ 4. For list answers, use a comma-separated format
1055
+ 5. NEVER explain your reasoning in the FINAL ANSWER section
1056
+ 6. NEVER skip the "FINAL ANSWER:" prefix
1057
+
1058
+ Example good answers:
1059
+ FINAL ANSWER: 42
1060
+ FINAL ANSWER: Paris
1061
+ FINAL ANSWER: 1912, 1945, 1989
1062
+
1063
+ Example bad answers (don't do these):
1064
+ - Based on my analysis, the answer is 42.
1065
+ - I think it's Paris because that's the capital of France.
1066
+ - The years were 1912, 1945, and 1989.
1067
+
1068
+ Remember: ALWAYS include "FINAL ANSWER:" followed by the most concise answer possible.
1069
+ """.strip())
1070
+
1071
+ # Define tools for each agent
1072
+ logger.info("Setting up agent tools")
1073
+ perception_tools = [web_search, wiki_search, news_article_search, arvix_search, image_processing, echo]
1074
+ execution_tools = [
1075
+ multiply, add, subtract, divide, modulus,
1076
+ download_file, process_excel_to_text,
1077
+ read_text_from_pdf, read_text_from_docx,
1078
+ transcribe_audio, youtube_audio_processing,
1079
+ extract_article_text, answer_youtube_video_question,
1080
+ python_repl_tool, analyze_code, read_code_file, analyze_python_function
1081
+ ]
1082
+
1083
+ # ─────────────── Agent Creation ───────────────
1084
+ logger.info("Creating agents")
1085
+ try:
1086
+ # Create agents with proper error handling
1087
+ PerceptionAgent = create_react_agent(
1088
+ model=llm,
1089
+ tools=perception_tools,
1090
+ prompt=sys_msg,
1091
+ state_schema=AgentState,
1092
+ name="PerceptionAgent"
1093
+ )
1094
+ logger.info("Created PerceptionAgent successfully")
1095
+
1096
+ # Combined Planning and Execution agent for better efficiency
1097
+ ActionAgent = create_react_agent(
1098
+ model=llm,
1099
+ tools=execution_tools, # Has access to all execution tools
1100
+ prompt=sys_msg,
1101
+ state_schema=AgentState,
1102
+ name="ActionAgent"
1103
+ )
1104
+ logger.info("Created ActionAgent successfully")
1105
+
1106
+ # Evaluation agent with stricter prompt
1107
+ EvaluationAgent = create_react_agent(
1108
+ model=llm,
1109
+ tools=[], # No tools needed for evaluation
1110
+ prompt=eval_sys_msg, # Use the specialized evaluation prompt
1111
+ state_schema=AgentState,
1112
+ name="EvaluationAgent"
1113
+ )
1114
+ logger.info("Created EvaluationAgent successfully")
1115
+ except Exception as e:
1116
+ logger.error(f"Error creating agent: {str(e)}")
1117
+ import traceback
1118
+ logger.error(f"Traceback: {traceback.format_exc()}")
1119
+ raise
1120
+
1121
+ # Build the StateGraph
1122
+ logger.info("Building StateGraph")
1123
+ try:
1124
+ builder = StateGraph(AgentState)
1125
+
1126
+ # Add agent nodes first
1127
+ builder.add_node("PerceptionAgent", PerceptionAgent)
1128
+ builder.add_node("ActionAgent", ActionAgent)
1129
+ builder.add_node("EvaluationAgent", EvaluationAgent)
1130
+
1131
+ # Define the flow with a starting edge
1132
+ builder.set_entry_point("PerceptionAgent")
1133
+
1134
+ # Add the edges for the simpler linear flow
1135
+ builder.add_edge("PerceptionAgent", "ActionAgent")
1136
+ builder.add_edge("ActionAgent", "EvaluationAgent")
1137
+
1138
+ # Set EvaluationAgent as the end node
1139
+ builder.set_finish_point("EvaluationAgent")
1140
+
1141
+ logger.info("Compiling StateGraph")
1142
+ return builder.compile()
1143
+ except Exception as e:
1144
+ logger.error(f"Error building graph: {str(e)}")
1145
+ import traceback
1146
+ logger.error(f"Traceback: {traceback.format_exc()}")
1147
+ raise
1148
+ except Exception as e:
1149
+ logger.error(f"Overall error in build_graph: {str(e)}")
1150
+ import traceback
1151
+ logger.error(f"Traceback: {traceback.format_exc()}")
1152
+ raise
1153
+
1154
+ def get_final_answer(text):
1155
+ """Extract just the FINAL ANSWER from the model's response.
1156
+
1157
+ Args:
1158
+ text: The full text response from the LLM
1159
+
1160
+ Returns:
1161
+ str: The extracted answer without the "FINAL ANSWER:" prefix
1162
+ """
1163
+ # Log the raw text for debugging if needed
1164
+ logger.debug(f"Extracting answer from: {text[:200]}...")
1165
+
1166
+ if not text:
1167
+ logger.warning("Empty response received")
1168
+ return "No answer provided."
1169
+
1170
+ # Method 1: Look for "FINAL ANSWER:" with most comprehensive pattern matching
1171
+ pattern = r'(?:^|\n)FINAL ANSWER:\s*(.*?)(?:\n\s*$|$)'
1172
+ match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
1173
+ if match:
1174
+ # Return just the answer part, cleaned up
1175
+ logger.debug("Found answer using pattern 1")
1176
+ return match.group(1).strip()
1177
+
1178
+ # Method 2: Try looking for variations on the final answer format
1179
+ for variant in ["FINAL ANSWER:", "FINAL_ANSWER:", "Final Answer:", "Answer:"]:
1180
+ lines = text.split('\n')
1181
+ for i, line in enumerate(reversed(lines)):
1182
+ if variant in line:
1183
+ # Extract everything after the variant text
1184
+ logger.debug(f"Found answer using variant: {variant}")
1185
+ answer = line[line.find(variant) + len(variant):].strip()
1186
+ if answer:
1187
+ return answer
1188
+ # If the answer is on the next line, return that
1189
+ if i > 0:
1190
+ next_line = lines[len(lines) - i]
1191
+ if next_line.strip():
1192
+ return next_line.strip()
1193
+
1194
+ # Method 3: Look for phrases that suggest an answer
1195
+ for phrase in ["The answer is", "The result is", "We get", "Therefore,", "In conclusion,"]:
1196
+ phrase_pos = text.find(phrase)
1197
+ if phrase_pos != -1:
1198
+ # Try to extract everything after the phrase until the end of the sentence
1199
+ sentence_end = text.find(".", phrase_pos)
1200
+ if sentence_end != -1:
1201
+ logger.debug(f"Found answer using phrase: {phrase}")
1202
+ return text[phrase_pos + len(phrase):sentence_end].strip()
1203
+
1204
+ # Method 4: Fall back to taking the last paragraph with actual content
1205
+ paragraphs = text.strip().split('\n\n')
1206
+ for para in reversed(paragraphs):
1207
+ para = para.strip()
1208
+ if para and not para.startswith("I ") and not para.lower().startswith("to "):
1209
+ logger.debug("Using last meaningful paragraph")
1210
+ # If paragraph is very long, try to extract a concise answer
1211
+ if len(para) > 100:
1212
+ sentences = re.split(r'[.!?]', para)
1213
+ for sentence in reversed(sentences):
1214
+ sent = sentence.strip()
1215
+ if sent and len(sent) > 5 and not sent.startswith("I "):
1216
+ return sent
1217
+ return para
1218
+
1219
+ # Method 5: Last resort - just return the last line with content
1220
+ lines = text.strip().split('\n')
1221
+ for line in reversed(lines):
1222
+ line = line.strip()
1223
+ if line and len(line) > 3:
1224
+ logger.debug("Using last line with content")
1225
+ return line
1226
+
1227
+ # If everything fails, warn and return the truncated response
1228
+ logger.warning("Could not find a properly formatted answer")
1229
+ return text[:100] + "..." if len(text) > 100 else text
1230
+
1231
+ # test
1232
+ if __name__ == "__main__":
1233
+ question = "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect?"
1234
+ # Build the graph
1235
+ graph = build_graph(provider="groq")
1236
+ # Run the graph
1237
+ messages = [HumanMessage(content=question)]
1238
+ messages = graph.invoke({"messages": messages})
1239
+ for m in messages["messages"]:
1240
+ m.pretty_print()
1241
+
1242
+ # ─────────────────────────────────────────────── Tool for Code Analysis ───────────────────────────────────────────────────────────────
1243
+ @tool
1244
+ def analyze_code(code_string: str) -> str:
1245
+ """Analyze a string of code to understand its structure, functionality, and potential issues.
1246
+
1247
+ Args:
1248
+ code_string: The code to analyze as a string.
1249
+
1250
+ Returns:
1251
+ A structured analysis of the code including functions, classes, and key operations.
1252
+ """
1253
+ try:
1254
+ import ast
1255
+
1256
+ # Try to parse with Python's AST module
1257
+ try:
1258
+ parsed = ast.parse(code_string)
1259
+
1260
+ # Extract functions and classes
1261
+ functions = [node.name for node in ast.walk(parsed) if isinstance(node, ast.FunctionDef)]
1262
+ classes = [node.name for node in ast.walk(parsed) if isinstance(node, ast.ClassDef)]
1263
+ imports = [node.names[0].name for node in ast.walk(parsed) if isinstance(node, ast.Import)]
1264
+ imports.extend([f"{node.module}.{name.name}" if node.module else name.name
1265
+ for node in ast.walk(parsed) if isinstance(node, ast.ImportFrom)
1266
+ for name in node.names])
1267
+
1268
+ # Count various node types for complexity assessment
1269
+ num_loops = len([node for node in ast.walk(parsed)
1270
+ if isinstance(node, (ast.For, ast.While))])
1271
+ num_conditionals = len([node for node in ast.walk(parsed)
1272
+ if isinstance(node, (ast.If, ast.IfExp))])
1273
+
1274
+ analysis = {
1275
+ "language": "Python",
1276
+ "functions": functions,
1277
+ "classes": classes,
1278
+ "imports": imports,
1279
+ "complexity": {
1280
+ "functions": len(functions),
1281
+ "classes": len(classes),
1282
+ "loops": num_loops,
1283
+ "conditionals": num_conditionals
1284
+ }
1285
+ }
1286
+ return str(analysis)
1287
+ except SyntaxError:
1288
+ # If not valid Python, try some simple pattern matching
1289
+ if "{" in code_string and "}" in code_string:
1290
+ if "function" in code_string or "=>" in code_string:
1291
+ language = "JavaScript/TypeScript"
1292
+ elif "func" in code_string or "struct" in code_string:
1293
+ language = "Go or Rust"
1294
+ elif "public" in code_string or "private" in code_string or "class" in code_string:
1295
+ language = "Java/C#/C++"
1296
+ else:
1297
+ language = "Unknown C-like language"
1298
+ elif "<" in code_string and ">" in code_string and ("/>" in code_string or "</"):
1299
+ language = "HTML/XML/JSX"
1300
+ else:
1301
+ language = "Unknown"
1302
+
1303
+ return f"Non-Python code detected ({language}). Basic code structure analysis not available."
1304
+ except Exception as e:
1305
+ return f"Error analyzing code: {str(e)}"
1306
+
1307
+ @tool
1308
+ def read_code_file(file_path: str) -> str:
1309
+ """Read a code file and return its contents with proper syntax detection.
1310
+
1311
+ Args:
1312
+ file_path: Path to the code file.
1313
+
1314
+ Returns:
1315
+ The file contents and detected language.
1316
+ """
1317
+ try:
1318
+ # Check if file exists
1319
+ import os
1320
+ if not os.path.exists(file_path):
1321
+ return f"Error: File '{file_path}' does not exist."
1322
+
1323
+ with open(file_path, 'r', encoding='utf-8') as f:
1324
+ content = f.read()
1325
+
1326
+ # Try to detect language from extension
1327
+ ext = os.path.splitext(file_path)[1].lower()
1328
+
1329
+ language_map = {
1330
+ '.py': 'Python',
1331
+ '.js': 'JavaScript',
1332
+ '.ts': 'TypeScript',
1333
+ '.html': 'HTML',
1334
+ '.css': 'CSS',
1335
+ '.java': 'Java',
1336
+ '.c': 'C',
1337
+ '.cpp': 'C++',
1338
+ '.cs': 'C#',
1339
+ '.go': 'Go',
1340
+ '.rs': 'Rust',
1341
+ '.php': 'PHP',
1342
+ '.rb': 'Ruby',
1343
+ '.sh': 'Shell',
1344
+ '.bat': 'Batch',
1345
+ '.ps1': 'PowerShell',
1346
+ '.sql': 'SQL',
1347
+ '.json': 'JSON',
1348
+ '.xml': 'XML',
1349
+ '.yaml': 'YAML',
1350
+ '.yml': 'YAML',
1351
+ }
1352
+
1353
+ language = language_map.get(ext, 'Unknown')
1354
+
1355
+ return f"File content ({language}):\n\n{content}"
1356
+ except Exception as e:
1357
+ return f"Error reading file: {str(e)}"
1358
+
1359
+ @tool
1360
+ def analyze_python_function(function_name: str, code_string: str) -> str:
1361
+ """Extract and analyze a specific function from Python code.
1362
+
1363
+ Args:
1364
+ function_name: The name of the function to analyze.
1365
+ code_string: The complete code containing the function.
1366
+
1367
+ Returns:
1368
+ Analysis of the function including parameters, return type, and docstring.
1369
+ """
1370
+ try:
1371
+ import ast
1372
+ import inspect
1373
+ from types import CodeType, FunctionType
1374
+
1375
+ # Parse the code string
1376
+ parsed = ast.parse(code_string)
1377
+
1378
+ # Find the function definition
1379
+ function_def = None
1380
+ for node in ast.walk(parsed):
1381
+ if isinstance(node, ast.FunctionDef) and node.name == function_name:
1382
+ function_def = node
1383
+ break
1384
+
1385
+ if not function_def:
1386
+ return f"Function '{function_name}' not found in the provided code."
1387
+
1388
+ # Extract parameters
1389
+ params = []
1390
+ for arg in function_def.args.args:
1391
+ param_name = arg.arg
1392
+ # Get annotation if it exists
1393
+ if arg.annotation:
1394
+ if isinstance(arg.annotation, ast.Name):
1395
+ param_type = arg.annotation.id
1396
+ elif isinstance(arg.annotation, ast.Attribute):
1397
+ param_type = f"{arg.annotation.value.id}.{arg.annotation.attr}"
1398
+ else:
1399
+ param_type = "complex_type"
1400
+ params.append(f"{param_name}: {param_type}")
1401
+ else:
1402
+ params.append(param_name)
1403
+
1404
+ # Extract return type if it exists
1405
+ return_type = None
1406
+ if function_def.returns:
1407
+ if isinstance(function_def.returns, ast.Name):
1408
+ return_type = function_def.returns.id
1409
+ elif isinstance(function_def.returns, ast.Attribute):
1410
+ return_type = f"{function_def.returns.value.id}.{function_def.returns.attr}"
1411
+ else:
1412
+ return_type = "complex_return_type"
1413
+
1414
+ # Extract docstring
1415
+ docstring = ast.get_docstring(function_def)
1416
+
1417
+ # Create a summary
1418
+ summary = {
1419
+ "function_name": function_name,
1420
+ "parameters": params,
1421
+ "return_type": return_type,
1422
+ "docstring": docstring,
1423
+ "decorators": [d.id if isinstance(d, ast.Name) else "complex_decorator" for d in function_def.decorator_list],
1424
+ "line_count": len(function_def.body)
1425
+ }
1426
+
1427
+ # Create a more explicit string representation that ensures key terms are included
1428
+ result = f"Function '{function_name}' analysis:\n"
1429
+ result += f"- Parameters: {', '.join(params)}\n"
1430
+ result += f"- Return type: {return_type or 'None specified'}\n"
1431
+ result += f"- Docstring: {docstring or 'None'}\n"
1432
+ result += f"- Line count: {len(function_def.body)}"
1433
+
1434
+ return result
1435
+ except Exception as e:
1436
+ return f"Error analyzing function: {str(e)}"
1437
+
1438
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1439
+ # ─────────────────────────────────────────────── Tool for News Article Retrieval ──────────────────────────────────────────────────────────────────────
1440
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1441
+
1442
+ @tool
1443
+ def news_article_search(query: str, top_k: int = 3) -> Dict[str, str]:
1444
+ """Search for and retrieve news articles with robust error handling for news sites.
1445
+
1446
+ Args:
1447
+ query: The news topic or keywords to search for.
1448
+ top_k: Maximum number of articles to retrieve.
1449
+
1450
+ Returns:
1451
+ A dictionary with search results formatted as XML-like document entries.
1452
+ """
1453
+ # First, get URLs from DuckDuckGo with "news" focus
1454
+ results = []
1455
+ news_sources = [
1456
+ "bbc.com", "reuters.com", "apnews.com", "nasa.gov",
1457
+ "space.com", "universetoday.com", "nature.com", "science.org",
1458
+ "scientificamerican.com", "nytimes.com", "theguardian.com"
1459
+ ]
1460
+
1461
+ # Find news from reliable sources
1462
+ try:
1463
+ with DDGS() as ddgs:
1464
+ search_query = f"{query} site:{' OR site:'.join(news_sources)}"
1465
+ for hit in ddgs.text(search_query, safesearch="On", max_results=top_k*2):
1466
+ url = hit.get("href") or hit.get("url", "")
1467
+ if not url:
1468
+ continue
1469
+
1470
+ # Add the search snippet first as a fallback
1471
+ result = {
1472
+ "source": url,
1473
+ "page": "",
1474
+ "content": hit.get("body", "")[:250],
1475
+ "title": hit.get("title", "")
1476
+ }
1477
+
1478
+ # Try to get better content via a more robust method
1479
+ try:
1480
+ headers = {
1481
+ "User-Agent": random.choice([
1482
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
1483
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15",
1484
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
1485
+ ]),
1486
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
1487
+ "Accept-Language": "en-US,en;q=0.5",
1488
+ "Referer": "https://www.google.com/",
1489
+ "DNT": "1",
1490
+ "Connection": "keep-alive",
1491
+ "Upgrade-Insecure-Requests": "1"
1492
+ }
1493
+
1494
+ # Add a short delay between requests
1495
+ time.sleep(1 + random.random())
1496
+
1497
+ # Try to use newspaper3k for more reliable article extraction
1498
+ from newspaper import Article
1499
+ article = Article(url)
1500
+ article.download()
1501
+ article.parse()
1502
+
1503
+ # If we got meaningful content, update the result
1504
+ if article.text and len(article.text) > 100:
1505
+ # Get a summary - first paragraph + some highlights
1506
+ paragraphs = article.text.split('\n\n')
1507
+ first_para = paragraphs[0] if paragraphs else ""
1508
+ summary = first_para[:300]
1509
+ if len(paragraphs) > 1:
1510
+ summary += "... " + paragraphs[1][:200]
1511
+
1512
+ result["content"] = summary
1513
+ if article.title:
1514
+ result["title"] = article.title
1515
+
1516
+ except Exception as article_err:
1517
+ logger.warning(f"Article extraction failed for {url}: {article_err}")
1518
+ # Fallback to simple requests-based extraction
1519
+ try:
1520
+ resp = requests.get(url, timeout=12, headers=headers)
1521
+ resp.raise_for_status()
1522
+ soup = BeautifulSoup(resp.text, "html.parser")
1523
+
1524
+ # Try to get main content
1525
+ main_content = soup.find('main') or soup.find('article') or soup.find('div', class_='content')
1526
+
1527
+ if main_content:
1528
+ content = " ".join(main_content.get_text(separator=" ", strip=True).split()[:250])
1529
+ result["content"] = content
1530
+ except Exception as req_err:
1531
+ logger.warning(f"Fallback extraction failed for {url}: {req_err}")
1532
+ # Keep the original snippet as fallback
1533
+
1534
+ results.append(result)
1535
+ if len(results) >= top_k:
1536
+ break
1537
+
1538
+ except Exception as e:
1539
+ logger.error(f"News search failed: {e}")
1540
+ return format_search_docs([{
1541
+ "source": "Error",
1542
+ "page": "",
1543
+ "content": f"Failed to retrieve news articles for '{query}': {str(e)}"
1544
+ }])
1545
+
1546
+ if not results:
1547
+ # Fallback to regular web search
1548
+ logger.info(f"No news results found, falling back to web_search for {query}")
1549
+ return web_search(query, top_k)
1550
+
1551
+ return format_search_docs(results[:top_k])
1552
+
1553
+ # ───────────────────────────────────────────────────────────── Document Chunking Utilities ──────────────────────────────────────────────────────────
1554
+ def chunk_document(text: str, chunk_size: int = 1000, overlap: int = 100) -> List[str]:
1555
+ """
1556
+ Split a large document into smaller chunks with overlap to maintain context across chunks.
1557
+
1558
+ Args:
1559
+ text: The document text to split into chunks
1560
+ chunk_size: Maximum size of each chunk in characters
1561
+ overlap: Number of characters to overlap between chunks
1562
+
1563
+ Returns:
1564
+ List of text chunks
1565
+ """
1566
+ # If text is smaller than chunk_size, return it as is
1567
+ if len(text) <= chunk_size:
1568
+ return [text]
1569
+
1570
+ chunks = []
1571
+ start = 0
1572
+
1573
+ while start < len(text):
1574
+ # Get chunk with overlap
1575
+ end = min(start + chunk_size, len(text))
1576
+
1577
+ # Try to find sentence boundary for cleaner breaks
1578
+ if end < len(text):
1579
+ # Look for sentence endings: period, question mark, or exclamation followed by space
1580
+ for sentence_end in ['. ', '? ', '! ']:
1581
+ last_period = text[start:end].rfind(sentence_end)
1582
+ if last_period != -1:
1583
+ end = start + last_period + 2 # +2 to include the period and space
1584
+ break
1585
+
1586
+ # Add chunk to list
1587
+ chunks.append(text[start:end])
1588
+
1589
+ # Move start position, accounting for overlap
1590
+ start = end - overlap if end < len(text) else len(text)
1591
+
1592
+ return chunks
1593
+
1594
+ # Document processing utility that uses chunking
1595
+ def process_large_document(text: str, question: str, llm=None) -> str:
1596
+ """
1597
+ Process a large document by chunking it and using retrieval to find relevant parts.
1598
+
1599
+ Args:
1600
+ text: The document text to process
1601
+ question: The question being asked about the document
1602
+ llm: Optional language model to use (defaults to agent's LLM)
1603
+
1604
+ Returns:
1605
+ Summarized answer based on relevant chunks
1606
+ """
1607
+ if not llm:
1608
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", streaming=False, temperature=0)
1609
+
1610
+ # Split document into chunks
1611
+ chunks = chunk_document(text)
1612
+
1613
+ # If document is small enough, don't bother with retrieval
1614
+ if len(chunks) <= 1:
1615
+ return text
1616
+
1617
+ # For larger documents, create embeddings to find relevant chunks
1618
+ try:
1619
+ from langchain_community.embeddings import HuggingFaceEmbeddings
1620
+ from langchain.vectorstores import FAISS
1621
+ from langchain.schema import Document
1622
+
1623
+ # Create documents with chunk content
1624
+ documents = [Document(page_content=chunk, metadata={"chunk_id": i}) for i, chunk in enumerate(chunks)]
1625
+
1626
+ # Create embeddings and vector store
1627
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
1628
+ vectorstore = FAISS.from_documents(documents, embeddings)
1629
+
1630
+ # Get most relevant chunks
1631
+ relevant_chunks = vectorstore.similarity_search(question, k=2) # Get top 2 most relevant chunks
1632
+
1633
+ # Join the relevant chunks
1634
+ relevant_text = "\n\n".join([doc.page_content for doc in relevant_chunks])
1635
+
1636
+ # Option 1: Return relevant chunks directly
1637
+ return relevant_text
1638
+
1639
+ # Option 2: Summarize with LLM (commented out for now)
1640
+ # prompt = f"Using only the following information, answer the question: '{question}'\n\nInformation:\n{relevant_text}"
1641
+ # response = llm.invoke([HumanMessage(content=prompt)])
1642
+ # return response.content
1643
+
1644
+ except Exception as e:
1645
+ # Fall back to first chunk if retrieval fails
1646
+ logger.warning(f"Retrieval failed: {e}. Falling back to first chunk.")
1647
+ return chunks[0]
scratch_agent/agent2.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ from langchain_core.tools import tool
5
+ from langgraph.prebuilt import tools_condition, ToolNode
6
+ from langgraph.graph import START, StateGraph, MessagesState
7
+
8
+ from langchain_community.tools.tavily_search import TavilySearchResults
9
+ from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
10
+ from langchain_core.messages import SystemMessage, HumanMessage
11
+ from langchain_community.vectorstores import FAISS
12
+ from langchain_huggingface import HuggingFaceEmbeddings
13
+ from langchain_groq import ChatGroq
14
+ from langchain.tools.retriever import create_retriever_tool
15
+
16
+ """LangGraph Agent"""
17
+ import os
18
+ from dotenv import load_dotenv
19
+ from langgraph.graph import START, StateGraph, MessagesState
20
+ from langgraph.prebuilt import tools_condition
21
+ from langgraph.prebuilt import ToolNode
22
+ from langchain_google_genai import ChatGoogleGenerativeAI
23
+ from langchain_groq import ChatGroq
24
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
25
+ from langchain_community.tools.tavily_search import TavilySearchResults
26
+ from langchain_community.document_loaders import WikipediaLoader
27
+ from langchain_community.document_loaders import ArxivLoader
28
+ from langchain_community.vectorstores import SupabaseVectorStore,FAISS
29
+ from langchain_core.messages import SystemMessage, HumanMessage
30
+ from langchain_core.tools import tool
31
+ from langchain.tools.retriever import create_retriever_tool
32
+ #from supabase.client import Client, create_client
33
+
34
+ # ──────────────────────────────────────────────────────────
35
+ # ENV
36
+ # ──────────────────────────────────────────────────────────
37
+ load_dotenv()
38
+ # API Keys from .env file
39
+ os.environ.setdefault("OPENAI_API_KEY", "<YOUR_OPENAI_KEY>") # Set your own key or env var
40
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY", "default_key_or_placeholder")
41
+ os.environ["MISTRAL_API_KEY"] = os.getenv("MISTRAL_API_KEY", "default_key_or_placeholder")
42
+
43
+ # Tavily API Key
44
+ TAVILY_API_KEY = os.getenv("TAVILY_API_KEY", "default_key_or_placeholder")
45
+ _forbidden = ["nsfw", "porn", "sex", "explicit"]
46
+ _playwright_available = True # set False to disable Playwright
47
+
48
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim = 768
49
+
50
+ @tool
51
+ def multiply(a: int, b: int) -> int:
52
+ """Multiply two numbers.
53
+
54
+ Args:
55
+ a: first int
56
+ b: second int
57
+ """
58
+ return a * b
59
+
60
+ @tool
61
+ def add(a: int, b: int) -> int:
62
+ """Add two numbers.
63
+
64
+ Args:
65
+ a: first int
66
+ b: second int
67
+ """
68
+ return a + b
69
+
70
+ @tool
71
+ def subtract(a: int, b: int) -> int:
72
+ """Subtract two numbers.
73
+
74
+ Args:
75
+ a: first int
76
+ b: second int
77
+ """
78
+ return a - b
79
+
80
+ @tool
81
+ def divide(a: int, b: int) -> int:
82
+ """Divide two numbers.
83
+
84
+ Args:
85
+ a: first int
86
+ b: second int
87
+ """
88
+ if b == 0:
89
+ raise ValueError("Cannot divide by zero.")
90
+ return a / b
91
+
92
+ @tool
93
+ def modulus(a: int, b: int) -> int:
94
+ """Get the modulus of two numbers.
95
+
96
+ Args:
97
+ a: first int
98
+ b: second int
99
+ """
100
+ return a % b
101
+
102
+ @tool
103
+ def wiki_search(query: str) -> str:
104
+ """Search Wikipedia for a query and return maximum 2 results.
105
+
106
+ Args:
107
+ query: The search query."""
108
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
109
+ formatted_search_docs = "\n\n---\n\n".join(
110
+ [
111
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
112
+ for doc in search_docs
113
+ ])
114
+ return {"wiki_results": formatted_search_docs}
115
+
116
+ @tool
117
+ def web_search(query: str) -> str:
118
+ """Search Tavily for a query and return maximum 3 results.
119
+
120
+ Args:
121
+ query: The search query."""
122
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
123
+ formatted_search_docs = "\n\n---\n\n".join(
124
+ [
125
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
126
+ for doc in search_docs
127
+ ])
128
+ return {"web_results": formatted_search_docs}
129
+
130
+ @tool
131
+ def arvix_search(query: str) -> str:
132
+ """Search Arxiv for a query and return maximum 3 result.
133
+
134
+ Args:
135
+ query: The search query."""
136
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
137
+ formatted_search_docs = "\n\n---\n\n".join(
138
+ [
139
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
140
+ for doc in search_docs
141
+ ])
142
+ return {"arvix_results": formatted_search_docs}
143
+
144
+
145
+
146
+ # load the system prompt from the file
147
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
148
+ system_prompt = f.read()
149
+
150
+ # System message
151
+ sys_msg = SystemMessage(content=system_prompt)
152
+
153
+ # build a retriever
154
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim=768
155
+
156
+ INDEX_PATH = "faiss_index"
157
+ if os.path.exists(INDEX_PATH):
158
+ vector_store = FAISS.load_local(INDEX_PATH, embeddings, allow_dangerous_deserialization=True)
159
+ else:
160
+ vector_store = FAISS.from_texts(["__init__"], embeddings)
161
+ vector_store.save_local(INDEX_PATH)
162
+
163
+ create_retriever_tool = create_retriever_tool(
164
+ retriever=vector_store.as_retriever(),
165
+ name="Question Search",
166
+ description="A tool to retrieve similar questions from a local FAISS vector store."
167
+ )
168
+
169
+
170
+
171
+ tools = [
172
+ multiply,
173
+ add,
174
+ subtract,
175
+ divide,
176
+ modulus,
177
+ wiki_search,
178
+ web_search,
179
+ arvix_search,
180
+ ]
181
+
182
+ # Build graph function
183
+ def build_graph(provider: str = "groq"):
184
+ """Build the graph"""
185
+ # Load environment variables from .env file
186
+ if provider == "google":
187
+ # Google Gemini
188
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
189
+ elif provider == "groq":
190
+ # Groq https://console.groq.com/docs/models
191
+ try:
192
+ llm = ChatGroq(model="deepseek-r1-distill-llama-70b", temperature=0) # optional : qwen-qwq-32b gemma2-9b-it
193
+ except Exception as e:
194
+ print(f"Error initializing Groq: {str(e)}")
195
+ raise
196
+ elif provider == "huggingface":
197
+ # TODO: Add huggingface endpoint
198
+ llm = ChatHuggingFace(
199
+ llm=HuggingFaceEndpoint(
200
+ url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
201
+ temperature=0,
202
+ ),
203
+ )
204
+ else:
205
+ raise ValueError("Invalid provider. Choose 'google', 'groq' or 'huggingface'.")
206
+
207
+ # Bind tools to LLM
208
+ llm_with_tools = llm.bind_tools(tools)
209
+
210
+ # Node
211
+ def assistant(state: MessagesState):
212
+ """Assistant node"""
213
+ try:
214
+ if not state["messages"] or not state["messages"][-1].content:
215
+ raise ValueError("Empty message content")
216
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
217
+ except Exception as e:
218
+ print(f"Error in assistant node: {str(e)}")
219
+ raise
220
+
221
+ def retriever(state: MessagesState):
222
+ """Retriever node"""
223
+ try:
224
+ if not state["messages"] or not state["messages"][0].content:
225
+ raise ValueError("Empty message content")
226
+ similar_question = vector_store.similarity_search(state["messages"][0].content)
227
+ example_msg = HumanMessage(
228
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
229
+ )
230
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
231
+ except Exception as e:
232
+ print(f"Error in retriever node: {str(e)}")
233
+ raise
234
+
235
+ builder = StateGraph(MessagesState)
236
+ builder.add_node("retriever", retriever)
237
+ builder.add_node("assistant", assistant)
238
+ builder.add_node("tools", ToolNode(tools))
239
+ builder.add_edge(START, "retriever")
240
+ builder.add_edge("retriever", "assistant")
241
+ builder.add_conditional_edges(
242
+ "assistant",
243
+ tools_condition,
244
+ )
245
+ builder.add_edge("tools", "assistant")
246
+
247
+ # Compile graph
248
+ return builder.compile()
249
+
250
+ # test
251
+ if __name__ == "__main__":
252
+ question = "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect?"
253
+ # Build the graph
254
+ graph = build_graph(provider="groq")
255
+ # Run the graph
256
+ messages = [HumanMessage(content=question)]
257
+ messages = graph.invoke({"messages": messages})
258
+ for m in messages["messages"]:
259
+ m.pretty_print()
scratch_agent/app.py ADDED
@@ -0,0 +1,997 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template, send_from_directory
2
+ from langgraph.prebuilt import create_react_agent
3
+ from langchain_groq import ChatGroq
4
+ from PIL import Image
5
+ import os, json, re
6
+ import shutil
7
+ import uuid
8
+ from langgraph.graph import StateGraph, END
9
+ import logging
10
+ from typing import Dict, TypedDict, Optional
11
+
12
+ # --- Configure logging ---
13
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
14
+ logger = logging.getLogger(__name__)
15
+
16
+ app = Flask(__name__)
17
+
18
+ # --- LLM / Vision model setup ---
19
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY", "default_key_or_placeholder")
20
+
21
+ groq_key = os.environ["GROQ_API_KEY"]
22
+ if not groq_key or groq_key == "default_key_or_placeholder":
23
+ logger.critical("GROQ_API_KEY environment variable is not set or invalid. Please set it to proceed.")
24
+ raise ValueError("GROQ_API_KEY environment variable is not set or invalid.")
25
+
26
+ llm = ChatGroq(
27
+ model="deepseek-r1-distill-llama-70b",
28
+ temperature=0.0,
29
+ )
30
+
31
+ # Refined SYSTEM_PROMPT with more explicit Scratch JSON rules, especially for variables
32
+ SYSTEM_PROMPT = """
33
+ You are an expert AI assistant named GameScratchAgent, specialized in generating and modifying Scratch-VM 3.x game project JSON.
34
+ Your core task is to process game descriptions and existing Scratch JSON structures, then produce or update JSON segments accurately.
35
+ You possess deep knowledge of Scratch 3.0 project schema. When generating or modifying the `blocks` section, pay extremely close attention to the following:
36
+
37
+ **Scratch Target and Block Schema Rules:**
38
+ 1. **Target Structure:**
39
+ * Every target (Stage or Sprite) must have an `objName` property. For the Stage, it's typically `"objName": "Stage"`. For sprites, it's their name (e.g., `"objName": "Cat"`).
40
+ * `variables` dictionary: This dictionary maps unique variable IDs to arrays `[variable_name, initial_value]`.
41
+ Example: `"variables": { "myVarId123": ["score", 0] }`
42
+
43
+ 2. **Block Structure:** Every block must have `opcode`, `parent`, `next`, `inputs`, `fields`, `shadow`, `topLevel`.
44
+ * `parent`: ID of the block it's stacked on (or `null` for top-level).
45
+ * `next`: ID of the block directly below it (or `null` for end of stack).
46
+ * `topLevel`: `true` if it's a hat block or a standalone block, `false` otherwise.
47
+ * `shadow`: `true` if it's a shadow block (e.g., a default number input), `false` otherwise.
48
+
49
+ 3. **`inputs` vs. `fields`:** This is critical.
50
+ * **`inputs`**: Used for blocks or values *plugged into* a block (e.g., condition for an `if` block, target for `go to`). Values are **arrays**.
51
+ * `[1, <blockId>]`: Represents a Reporter or Boolean block *plugged in* (e.g., `operator_equals`).
52
+ * `[1, [<type>, "<value>"]]`: For a shadow block with a literal value (e.g., `[1, ["num", "10"]]` for a number input). The `type` can be "num", "str", "bool", "colour", etc.
53
+ * `[2, <blockId>]`: For a C-block's substack (e.g., `SUBSTACK` in `control_if`).
54
+ * **`fields`**: Used for dropdown menus or direct internal values *within* a block (e.g., key selected in `when key pressed`, variable name in `set variable`). Values are always **arrays** `["<value>", null]`.
55
+ * Example for `event_whenkeypressed`: `"KEY": ["space", null]`
56
+ * Example for `data_setvariableto`: `"VARIABLE": ["score", null]`
57
+
58
+ 4. **Unique IDs:** All block IDs, variable IDs, and list IDs must be unique strings (e.g., "myBlock123", "myVarId456"). Do NOT use placeholder strings like "block_id_here".
59
+
60
+ 5. **No Nested `blocks` Dictionary:** 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 (like inside an `if` or `forever` loop) are linked via the `SUBSTACK` input, where the input value is `[2, "ID_of_first_block_in_substack"]`.
61
+
62
+ 6. **Asset Properties:** `assetId`, `md5ext`, `bitmapResolution`, `rotationCenterX`/`rotationCenterY` should be correct for costumes/sounds.
63
+
64
+ **When responding, you must:**
65
+ 1. **Output ONLY the complete, valid JSON object.** Do not include markdown, commentary, or conversational text.
66
+ 2. Ensure every generated block ID, input, field, and variable declaration strictly follows the Scratch 3.0 schema and the rules above.
67
+ 3. If presented with partial or problematic JSON, aim to complete or correct it based on the given instructions and your schema knowledge.
68
+ """
69
+
70
+ agent = create_react_agent(
71
+ model=llm,
72
+ tools=[], # No specific tools are defined here, but could be added later
73
+ prompt=SYSTEM_PROMPT
74
+ )
75
+
76
+ # --- Serve the form ---
77
+ @app.route("/", methods=["GET"])
78
+ def index():
79
+ return render_template("index4.html")
80
+
81
+ # --- List static assets for the front-end ---
82
+ @app.route("/list_assets", methods=["GET"])
83
+ def list_assets():
84
+ bdir = os.path.join(app.static_folder, "assets", "backdrops")
85
+ sdir = os.path.join(app.static_folder, "assets", "sprites")
86
+ backdrops = []
87
+ sprites = []
88
+ try:
89
+ if os.path.isdir(bdir):
90
+ backdrops = [f for f in os.listdir(bdir) if f.lower().endswith(".svg")]
91
+ if os.path.isdir(sdir):
92
+ sprites = [f for f in os.listdir(sdir) if f.lower().endswith(".svg")]
93
+ logger.info("Successfully listed static assets.")
94
+ except Exception as e:
95
+ logger.error(f"Error listing static assets: {e}")
96
+ return jsonify({"error": "Failed to list assets"}), 500
97
+ return jsonify(backdrops=backdrops, sprites=sprites)
98
+
99
+ # --- Helper: build costume entries ---
100
+ def make_costume_entry(folder, filename, name):
101
+ asset_id = filename.rsplit(".", 1)[0]
102
+ entry = {
103
+ "name": name,
104
+ "bitmapResolution": 1,
105
+ "dataFormat": filename.rsplit(".", 1)[1],
106
+ "assetId": asset_id,
107
+ "md5ext": filename,
108
+ }
109
+
110
+ path = os.path.join(app.static_folder, "assets", folder, filename)
111
+ ext = filename.lower().endswith(".svg")
112
+
113
+ if ext:
114
+ if folder == "backdrops":
115
+ entry["rotationCenterX"] = 240
116
+ entry["rotationCenterY"] = 180
117
+ else:
118
+ entry["rotationCenterX"] = 0
119
+ entry["rotationCenterY"] = 0
120
+ else:
121
+ try:
122
+ img = Image.open(path)
123
+ w, h = img.size
124
+ entry["rotationCenterX"] = w // 2
125
+ entry["rotationCenterY"] = h // 2
126
+ except Exception as e:
127
+ logger.warning(f"Could not determine image dimensions for {filename}: {e}. Setting center to 0,0.")
128
+ entry["rotationCenterX"] = 0
129
+ entry["rotationCenterY"] = 0
130
+
131
+ return entry
132
+
133
+ # --- New endpoint to fetch project.json ---
134
+ @app.route("/get_project/<project_id>", methods=["GET"])
135
+ def get_project(project_id):
136
+ project_folder = os.path.join("generated_projects", project_id)
137
+ project_json_path = os.path.join(project_folder, "project.json")
138
+
139
+ try:
140
+ if os.path.exists(project_json_path):
141
+ logger.info(f"Serving project.json for project ID: {project_id}")
142
+ return send_from_directory(project_folder, "project.json", as_attachment=True, download_name=f"{project_id}.json")
143
+ else:
144
+ logger.warning(f"Project JSON not found for ID: {project_id}")
145
+ return jsonify({"error": "Project not found"}), 404
146
+ except Exception as e:
147
+ logger.error(f"Error serving project.json for ID {project_id}: {e}")
148
+ return jsonify({"error": "Failed to retrieve project"}), 500
149
+
150
+ # --- New endpoint to fetch assets ---
151
+ @app.route("/get_asset/<project_id>/<filename>", methods=["GET"])
152
+ def get_asset(project_id, filename):
153
+ project_folder = os.path.join("generated_projects", project_id)
154
+ asset_path = os.path.join(project_folder, filename)
155
+
156
+ try:
157
+ if os.path.exists(asset_path):
158
+ logger.info(f"Serving asset '{filename}' for project ID: {project_id}")
159
+ return send_from_directory(project_folder, filename)
160
+ else:
161
+ logger.warning(f"Asset '{filename}' not found for project ID: {project_id}")
162
+ return jsonify({"error": "Asset not found"}), 404
163
+ except Exception as e:
164
+ logger.error(f"Error serving asset '{filename}' for project ID {project_id}: {e}")
165
+ return jsonify({"error": "Failed to retrieve asset"}), 500
166
+
167
+
168
+ ### LangGraph Workflow Definition
169
+
170
+ # Define a state for your graph using TypedDict
171
+ class GameState(TypedDict):
172
+ project_json: dict
173
+ description: str
174
+ project_id: str
175
+ sprite_initial_positions: dict # Add this as well, as it's part of your state
176
+ action_plan: Optional[Dict]
177
+ behavior_plan: Optional[Dict]
178
+
179
+ # Helper function to extract JSON from LLM response
180
+ def extract_json_from_llm_response(raw_response: str) -> dict:
181
+ m = re.search(r"```json\n(.+?)```", raw_response, re.S)
182
+ body = m.group(1) if m else raw_response
183
+ try:
184
+ json_data = json.loads(body)
185
+ logger.debug("Successfully extracted and parsed JSON from LLM response.")
186
+ return json_data
187
+ except json.JSONDecodeError as e:
188
+ logger.error(f"LLM did not return valid JSON. Error: {e}\nRaw response: {raw_response}")
189
+ raise ValueError(f"LLM did not return valid JSON: {e}\nRaw response: {raw_response}")
190
+ except Exception as e:
191
+ logger.error(f"An unexpected error occurred during JSON extraction: {e}\nRaw response: {raw_response}")
192
+ raise
193
+ # Helper function to update project JSON with sprite positions
194
+ import copy
195
+ def update_project_with_sprite_positions(project_json: dict, sprite_positions: dict) -> dict:
196
+ """
197
+ Update the 'x' and 'y' coordinates of sprites in the Scratch project JSON.
198
+
199
+ Args:
200
+ project_json (dict): Original Scratch project JSON.
201
+ sprite_positions (dict): Dict mapping sprite names to {'x': int, 'y': int}.
202
+
203
+ Returns:
204
+ dict: Updated project JSON with new sprite positions.
205
+ """
206
+ updated_project = copy.deepcopy(project_json)
207
+
208
+ for target in updated_project.get("targets", []):
209
+ if not target.get("isStage", False):
210
+ sprite_name = target.get("name")
211
+ if sprite_name in sprite_positions:
212
+ pos = sprite_positions[sprite_name]
213
+ if "x" in pos and "y" in pos:
214
+ target["x"] = pos["x"]
215
+ target["y"] = pos["y"]
216
+
217
+ return updated_project
218
+
219
+ #
220
+ # --- Global variable for the block catalog ---
221
+ ALL_SCRATCH_BLOCKS_CATALOG = {}
222
+ BLOCK_CATALOG_PATH = "block_content.json" # Define the path to your JSON file
223
+
224
+ # Helper function to load the block catalog from a JSON file
225
+ def _load_block_catalog(file_path: str) -> Dict:
226
+ """Loads the Scratch block catalog from a specified JSON file."""
227
+ try:
228
+ with open(file_path, 'r') as f:
229
+ catalog = json.load(f)
230
+ logger.info(f"Successfully loaded block catalog from {file_path}")
231
+ return catalog
232
+ except FileNotFoundError:
233
+ logger.error(f"Error: Block catalog file not found at {file_path}")
234
+ # Return an empty dict or raise an error, depending on desired behavior
235
+ return {}
236
+ except json.JSONDecodeError as e:
237
+ logger.error(f"Error decoding JSON from {file_path}: {e}")
238
+ return {}
239
+ except Exception as e:
240
+ logger.error(f"An unexpected error occurred while loading {file_path}: {e}")
241
+ return {}
242
+
243
+
244
+ # This makes ALL_SCRATCH_BLOCKS_CATALOG available globally
245
+ ALL_SCRATCH_BLOCKS_CATALOG = _load_block_catalog(BLOCK_CATALOG_PATH)
246
+
247
+ # Helper function to generate a unique block ID
248
+ def generate_block_id():
249
+ """Generates a short, unique ID for a Scratch block."""
250
+ return str(uuid.uuid4())[:10].replace('-', '') # Shorten for readability, ensure uniqueness
251
+
252
+ # Placeholder for your extract_json_from_llm_response function
253
+ def extract_json_from_llm_response(response_string: str) -> Dict:
254
+ """
255
+ Extracts a JSON object from an LLM response string.
256
+ Assumes the JSON is enclosed within triple backticks (```json ... ```).
257
+ """
258
+ try:
259
+ # Find the start and end of the JSON block
260
+ start_marker = "```json"
261
+ end_marker = "```"
262
+ start_index = response_string.find(start_marker)
263
+ end_index = response_string.rfind(end_marker)
264
+
265
+ if start_index != -1 and end_index != -1 and start_index < end_index:
266
+ # Extract the substring between the markers
267
+ json_str = response_string[start_index + len(start_marker):end_index].strip()
268
+ return json.loads(json_str)
269
+ else:
270
+ # If markers are not found, try to parse the whole string as JSON
271
+ # This handles cases where the LLM might omit markers
272
+ logger.warning("JSON markers not found in LLM response. Attempting to parse raw string.")
273
+ return json.loads(response_string.strip())
274
+ except json.JSONDecodeError as e:
275
+ logger.error(f"Failed to decode JSON from LLM response: {e}")
276
+ logger.error(f"Raw response: {response_string}")
277
+ raise ValueError("Invalid JSON response from LLM")
278
+ except Exception as e:
279
+ logger.error(f"An unexpected error occurred in extract_json_from_llm_response: {e}")
280
+ raise
281
+
282
+
283
+ # Node 1: Analysis User Query and Initial Positions
284
+ def parse_query_and_set_initial_positions(state: GameState):
285
+ logger.info("--- Running ParseQueryNode ---")
286
+
287
+ llm_query_prompt = (
288
+ f"Based on the user's game description: '{state['description']}', "
289
+ f"and the current Scratch project JSON below, "
290
+ f"determine the most appropriate initial 'x' and 'y' coordinates for each sprite. "
291
+ f"Return ONLY a JSON object with a single key 'sprite_initial_positions' mapping sprite names to their {{'x': int, 'y': int}} coordinates.\n\n"
292
+ f"The current Scratch project JSON is:\n```json\n{json.dumps(state['project_json'], indent=2)}\n```\n"
293
+ f"Example Json output:\n"
294
+ f"{{\n"
295
+ f" \"sprite_initial_positions\": {{\n"
296
+ f" \"Sprite1\": {{\"x\": -160, \"y\": -110}},\n"
297
+ f" \"Sprite2\": {{\"x\": 240, \"y\": -135}}\n"
298
+ f" }}\n"
299
+ f"}}"
300
+ )
301
+
302
+ try:
303
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_query_prompt}]})
304
+ raw_response = response["messages"][-1].content
305
+ print("Raw response from LLM:", raw_response)
306
+
307
+ updated_data = extract_json_from_llm_response(raw_response)
308
+ sprite_positions = updated_data.get("sprite_initial_positions", {})
309
+
310
+ new_project_json = update_project_with_sprite_positions(state["project_json"], sprite_positions)
311
+ print("Updated project JSON with sprite positions:", json.dumps(new_project_json, indent=2))
312
+ return {"project_json": new_project_json, "sprite_initial_positions": sprite_positions}
313
+ #return {"project_json": new_project_json}
314
+
315
+ except Exception as e:
316
+ logger.error(f"Error in ParseQueryNode: {e}")
317
+ raise
318
+
319
+ # Node 3: Sprite Action Plan Builder
320
+ def plan_sprite_actions(state: GameState):
321
+ logger.info("--- Running PlanSpriteActionsNode ---")
322
+
323
+
324
+ # Assuming 'agent' is defined globally or passed into this scope
325
+ planning_prompt = (
326
+ f"You are an AI assistant tasked with planning Scratch 3.0 block code for a game. "
327
+ f"The game description is: '{state['description']}'.\n\n"
328
+ f"Here are the sprites currently in the project: {', '.join(target['name'] for target in state['project_json']['targets'] if not target['isStage'] if state['project_json']['targets']) or 'None'}.\n"
329
+ f"Initial positions: {json.dumps(state.get('sprite_initial_positions', {}), indent=2)}\n\n"
330
+ f"Consider the main actions and interactions required for each sprite. "
331
+ f"Think step-by-step about what each sprite needs to *do*, *when* it needs to do it (events), "
332
+ f"and if any actions need to *repeat* or depend on *conditions*.\n\n"
333
+ f"Propose a high-level action flow for each sprite in the following JSON format. "
334
+ f"Do NOT generate Scratch block JSON yet. Only describe the logic using natural language or simplified pseudo-code.\n\n"
335
+ f"Example format:\n"
336
+ f"```json\n"
337
+ f"{{\n"
338
+ f" \"action_overall_flow\": {{\n"
339
+ f" \"Sprite1\": {{\n"
340
+ f" \"description\": \"Main character actions\",\n"
341
+ f" \"plans\": [\n"
342
+ f" {{\n"
343
+ f" \"event\": \"when flag clicked\",\n"
344
+ f" \"logic\": \"forever loop: move 10 steps, if on edge bounce\"\n"
345
+ f" }},\n"
346
+ f" {{\n"
347
+ f" \"event\": \"when space key pressed\",\n"
348
+ f" \"logic\": \"change y by 10, wait 0.1 seconds, change y by -10\"\n"
349
+ f" }}\n"
350
+ f" ]\n"
351
+ f" }},\n"
352
+ f" \"Ball\": {{\n"
353
+ f" \"description\": \"Projectile movement\",\n"
354
+ f" \"plans\": [\n"
355
+ f" {{\n"
356
+ f" \"event\": \"when I start as a clone\",\n"
357
+ f" \"logic\": \"glide 1 sec to random position, if touching Sprite1 then stop this script\"\n"
358
+ f" }}\n"
359
+ f" ]\n"
360
+ f" }}\n"
361
+ f" }}\n"
362
+ f"}}\n"
363
+ f"```\n\n"
364
+ f"Return ONLY the JSON object for the action overall flow."
365
+ )
366
+
367
+ try:
368
+ response = agent.invoke({"messages": [{"role": "user", "content": planning_prompt}]})
369
+ raw_response = response["messages"][-1].content
370
+ print("Raw response from LLM [PlanSpriteActionsNode]:", raw_response)
371
+ action_plan = extract_json_from_llm_response(raw_response)
372
+ logger.info("Sprite action plan generated by PlanSpriteActionsNode.")
373
+ return {"action_plan": action_plan}
374
+ except Exception as e:
375
+ logger.error(f"Error in PlanSpriteActionsNode: {e}")
376
+ raise
377
+
378
+ # Node 4: Action Node Builder
379
+ def build_action_nodes(state: GameState):
380
+ logger.info("--- Running ActionNodeBuilder ---")
381
+
382
+ # Assuming 'agent' is defined globally or passed into this scope
383
+ # from your_module import agent
384
+ global agent # Declare agent as global if it's imported in the main script scope
385
+
386
+ action_plan = state.get("action_plan", {})
387
+ if not action_plan:
388
+ raise ValueError("No action plan found in state. Run PlanSpriteActionsNode first.")
389
+
390
+ # Convert the Scratch project JSON to a mutable Python object
391
+ project_json = state["project_json"]
392
+ targets = project_json["targets"]
393
+
394
+ # We need a way to map sprite names to their actual target objects in project_json
395
+ sprite_map = {target["name"]: target for target in targets if not target["isStage"]}
396
+
397
+ # Iterate through the planned actions for each sprite
398
+ for sprite_name, sprite_actions_data in action_plan.get("action_overall_flow", {}).items():
399
+ if sprite_name in sprite_map:
400
+ current_sprite_target = sprite_map[sprite_name]
401
+ if "blocks" not in current_sprite_target:
402
+ current_sprite_target["blocks"] = {}
403
+
404
+ # Iterate through each planned event/script for the current sprite
405
+ for plan_entry in sprite_actions_data.get("plans", []):
406
+ event_description = plan_entry["event"]
407
+ logic_description = plan_entry["logic"]
408
+
409
+ llm_block_generation_prompt = (
410
+ f"You are an AI assistant generating Scratch 3.0 block JSON for a single script.\n"
411
+ f"The current sprite is '{sprite_name}'.\n"
412
+ f"The specific script to generate blocks for is:\n"
413
+ f" Event: '{event_description}'\n"
414
+ f" Logic: '{logic_description}'\n\n"
415
+ f"Here is the comprehensive catalog of all available Scratch 3.0 blocks:\n"
416
+ f"```json\n{json.dumps(ALL_SCRATCH_BLOCKS_CATALOG, indent=2)}\n```\n\n"
417
+ f"Current Scratch project JSON for this sprite (for context, its existing blocks if any, though you should generate a new script entirely if this is a hat block):\n"
418
+ f"```json\n{json.dumps(current_sprite_target, indent=2)}\n```\n\n"
419
+ f"**Instructions for generating the block JSON:**\n"
420
+ f"1. **Start with the event block (Hat Block):** This block's `topLevel` should be `true` and `parent` should be `null`. Its `x` and `y` coordinates should be set (e.g., `x: 0, y: 0` or reasonable offsets for multiple scripts).\n"
421
+ f"2. **Generate a sequence of connected blocks:** For each block, generate a **unique block ID** (e.g., 'abc123def4').\n"
422
+ f"3. **Link blocks correctly:**\n"
423
+ f" * Use the `next` field to point to the ID of the block directly below it in the stack.\n"
424
+ f" * Use the `parent` field to point to the ID of the block directly above it in the stack.\n"
425
+ f" * For the last block in a stack, `next` should be `null`.\n"
426
+ f"4. **Fill `inputs` for parameters and substacks:**\n"
427
+ f" * If a block takes a direct value input (e.g., `motion_movesteps` takes 'STEPS'), provide the input value as an array `[type_code, value_or_block_id]`. The `type_code` for the outer array must be a **number**: `1` (for a primitive/reporter input), `2` (for a block ID input), or `3` (for a variable/list reference).\n"
428
+ f" * `[1, [4, \"10\"]]` for directly embedding a simple number or string literal as a primitive. The inner `4` indicates a number primitive.\n"
429
+ f" * `[1, \"SHADOW_BLOCK_ID\"]` for inputs where a separate `shadow: true` block is used (e.g., a `math_number` block for a number, or a menu/message block for a dropdown/broadcast message).\n"
430
+ f" * `[2, \"CONNECTED_BLOCK_ID\"]` for a block that is plugged into this input (e.g., an operator block).\n"
431
+ f" * **For C-blocks (e.g., `control_forever`, `control_if`, `control_repeat`):** Use the `SUBSTACK` input. The value for `SUBSTACK` must be an array: `[2, \"FIRST_BLOCK_IN_SUBSTACK_ID\"]`. The `2` indicates that the input is a block ID, and `FIRST_BLOCK_IN_SUBSTACK_ID` is the ID of the first block inside the loop/conditional structure.\n"
432
+ f"5. **Define Shadow Blocks for Literals, Dropdowns, and Broadcast Messages:** When a block requires a number, string literal, a selection from a dropdown menu, or a broadcast message, you must define a separate `shadow: true` block and link it.\n"
433
+ f" * **For a number literal (e.g., '10'):**\n"
434
+ f" ```json\n"
435
+ f" \"UNIQUE_SHADOW_NUM_ID\": {{\n"
436
+ f" \"opcode\": \"math_number\",\n"
437
+ f" \"fields\": {{\n"
438
+ f" \"NUM\": [\"10\", null]\n"
439
+ f" }},\n"
440
+ f" \"shadow\": true,\n"
441
+ f" \"parent\": \"PARENT_BLOCK_ID\",\n"
442
+ f" \"topLevel\": false\n"
443
+ f" }}\n"
444
+ f" ```\n"
445
+ f" Then, in the parent block's `inputs`, link to this shadow block: `\"INPUT_NAME\": [1, \"UNIQUE_SHADOW_NUM_ID\"]`\n"
446
+ f" * **For a dropdown menu selection (e.g., 'space' key for `when key pressed`):**\n"
447
+ f" ```json\n"
448
+ f" \"UNIQUE_SHADOW_MENU_ID\": {{\n"
449
+ f" \"opcode\": \"event_whenkeypressed_keymenu\", // Example opcode for a key menu shadow\n"
450
+ f" \"fields\": {{\n"
451
+ f" \"KEY_OPTION\": [\"space\", null]\n"
452
+ f" }},\n"
453
+ f" \"shadow\": true,\n"
454
+ f" \"parent\": \"PARENT_BLOCK_ID\",\n"
455
+ f" \"topLevel\": false\n"
456
+ f" }}\n"
457
+ f" ```\n"
458
+ f" Then, in the parent block's `inputs`, link to this shadow block: `\"KEY_OPTION\": [1, \"UNIQUE_SHADOW_MENU_ID\"]`\n"
459
+ f" * **For a Broadcast Message (e.g., 'gameover' for `broadcast message`):**\n"
460
+ f" ```json\n"
461
+ f" \"UNIQUE_SHADOW_BROADCAST_ID\": {{\n"
462
+ f" \"opcode\": \"event_broadcast_menu\", // This opcode specifically handles the message name\n"
463
+ f" \"fields\": {{\n"
464
+ f" \"BROADCAST_OPTION\": [\"gameover\", null]\n"
465
+ f" }},\n"
466
+ f" \"shadow\": true,\n"
467
+ f" \"parent\": \"PARENT_BROADCAST_BLOCK_ID\",\n"
468
+ f" \"topLevel\": false\n"
469
+ f" }}\n"
470
+ f" ```\n"
471
+ f" Then, in the parent `event_broadcast` or `event_broadcastandwait` block's `inputs`, link to this shadow block: `\"BROADCAST\": [1, \"UNIQUE_SHADOW_BROADCAST_ID\"]`\n"
472
+ f"6. **`fields` for direct dropdown values/text:** If the block directly uses a dropdown value or text field without an `inputs` connection, use the `fields` dictionary. Example: `\"fields\": {{\"KEY_OPTION\": [\"space\", null]}}`.\n"
473
+ f"7. **`topLevel: true` for hat blocks:** Only the starting (hat) block of a script should have `\"topLevel\": true`.\n"
474
+ f"8. **Return ONLY the JSON object representing all the blocks for THIS SINGLE SCRIPT.** Do NOT wrap it in a 'blocks' key or the full project JSON. The output should be a dictionary where keys are block IDs and values are block definitions."
475
+ )
476
+ try:
477
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_block_generation_prompt}]})
478
+ raw_response = response["messages"][-1].content
479
+ print(f"Raw response from LLM [ActionNodeBuilder - {sprite_name} - {event_description}]:", raw_response)
480
+
481
+ # Directly extract the dictionary of blocks
482
+ generated_blocks = extract_json_from_llm_response(raw_response)
483
+
484
+ # Check for and unwrap any accidental 'blocks' nesting
485
+ if "blocks" in generated_blocks and isinstance(generated_blocks["blocks"], dict):
486
+ logger.warning(f"LLM returned nested 'blocks' key for {sprite_name}. Unwrapping.")
487
+ generated_blocks = generated_blocks["blocks"]
488
+
489
+ current_sprite_target["blocks"].update(generated_blocks)
490
+ logger.info(f"Action blocks added for sprite '{sprite_name}', script '{event_description}' by ActionNodeBuilder.")
491
+ except Exception as e:
492
+ logger.error(f"Error generating blocks for sprite '{sprite_name}', script '{event_description}': {e}")
493
+ raise
494
+ print("Updated project JSON with action nodes:", json.dumps(project_json, indent=2))
495
+ return {"project_json": project_json}
496
+
497
+ # Node 5: Behaviour Collector (Sensing/Collision)
498
+ def plan_behaviors_node(state: GameState):
499
+ logger.info("--- Running PlanBehaviorsNode ---")
500
+
501
+ # Get relevant context from the state
502
+ description = state.get("description","")
503
+ action_plan = state.get("action_plan", {})
504
+ initial_positions = state.get("initial_positions", {}) # Assuming this will be populated by a prior node if needed
505
+
506
+ # Construct the prompt with comprehensive context
507
+ planning_prompt = (
508
+ "Generate a detailed plan for the game's behaviors based on the following context:\n\n"
509
+ f"The game description is: '{state['description']}'.\n\n"
510
+ f"Action Plan: {json.dumps(action_plan, indent=2)}\n"
511
+ f"Initial Positions: {json.dumps(initial_positions, indent=2)}\n\n"
512
+ "Your task is to define the game's behaviors, including scoring, collision detection, "
513
+ "game state management (e.g., win/loss conditions, resets), and any necessary sprite "
514
+ "initialization (like starting positions or visibility)."
515
+ "The output should be a JSON object with a single key 'behavior_overall_flow'. "
516
+ "The value should be a dictionary where keys are sprite names (e.g., 'Stage', 'Player', 'Enemy', 'Coin') "
517
+ "and values are dictionaries containing a 'plans' list. "
518
+ "Each plan in the list should have an 'event' (a high-level trigger) and 'logic' (a natural language description "
519
+ "of the behavior, suitable for block generation)."
520
+ "\n\nExample structure for 'behavior_overall_flow':\n"
521
+ "```json\n"
522
+ "{\n"
523
+ " \"behavior_overall_flow\": {\n"
524
+ " \"Stage\": {\n"
525
+ " \"plans\": [\n"
526
+ " {\"event\": \"Game Initialization\", \"logic\": \"When green flag clicked, reset score, lives, and timer.\"}\n"
527
+ " ]\n"
528
+ " },\n"
529
+ " \"Player\": {\n"
530
+ " \"plans\": [\n"
531
+ " {\"event\": \"Start Position\", \"logic\": \"When green flag clicked, go to x:0 y:0.\"},\n"
532
+ " {\"event\": \"Collision with Enemy\", \"logic\": \"If the Player is touching 'Enemy', change score by -1, change lives by -1, and broadcast 'hit'.\"},\n"
533
+ " {\"event\": \"Collect Coin\", \"logic\": \"If the Player is touching 'Coin', change score by 10, and hide 'Coin'.\"}\n"
534
+ " ]\n"
535
+ " },\n"
536
+ " \"Enemy\": {\n"
537
+ " \"plans\": [\n"
538
+ " {\"event\": \"Game Reset\", \"logic\": \"When 'hit' is received, move Enemy to a random position and show it.\"}\n"
539
+ " ]\n"
540
+ " },\n"
541
+ " \"Coin\": {\n"
542
+ " \"plans\": [\n"
543
+ " {\"event\": \"Game Reset (Coin)\", \"logic\": \"When green flag clicked, show the Coin.\"}\n"
544
+ " ]\n"
545
+ " }\n"
546
+ " }\n"
547
+ "}\n"
548
+ "```\n"
549
+ "Based on the provided context, generate the `behavior_overall_flow`."
550
+ )
551
+
552
+ try:
553
+ response = agent.invoke({"messages": [{"role": "user", "content": planning_prompt}]})
554
+ raw_response = response["messages"][-1].content
555
+ logger.info(f"Raw response from LLM [PlanBehaviorsNode]: {raw_response[:200]}...")
556
+
557
+ behavior_plan = extract_json_from_llm_response(raw_response)
558
+ if "behavior_overall_flow" not in behavior_plan:
559
+ raise ValueError("LLM response for behavior plan missing 'behavior_overall_flow' key.")
560
+
561
+ state["behavior_plan"] = behavior_plan
562
+ logger.info("Behavior plan generated and added to state.")
563
+ return {"behavior_plan": behavior_plan}
564
+ except Exception as e:
565
+ logger.error(f"Error generating behavior plan: {e}")
566
+ raise
567
+
568
+
569
+
570
+ def build_behavior_nodes(state: GameState):
571
+ logger.info("--- Running BehaviorNodeBuilder ---")
572
+
573
+ project_json = state["project_json"]
574
+ targets = project_json["targets"]
575
+
576
+ sprite_map = {target["name"]: target for target in targets if not target["isStage"]}
577
+
578
+ behavior_plan = state.get("behavior_plan", {})
579
+ if not behavior_plan:
580
+ logger.warning("No behavior plan found in state. Skipping BehaviorNodeBuilder.")
581
+ return state
582
+
583
+ for sprite_name, sprite_behaviors_data in behavior_plan.get("behavior_overall_flow", {}).items():
584
+ if sprite_name in sprite_map:
585
+ current_sprite_target = sprite_map[sprite_name]
586
+ if "blocks" not in current_sprite_target:
587
+ current_sprite_target["blocks"] = {}
588
+
589
+ for plan_entry in sprite_behaviors_data.get("plans", []):
590
+ event_description = plan_entry["event"]
591
+ logic_description = plan_entry["logic"]
592
+
593
+ llm_block_generation_prompt = (
594
+ f"You are an AI assistant generating Scratch 3.0 block JSON for a single script.\n"
595
+ f"The current sprite is '{sprite_name}'.\n"
596
+ f"The specific script to generate blocks for is:\n"
597
+ f" Event: '{event_description}'\n"
598
+ f" Logic: '{logic_description}'\n\n"
599
+ f"Here is the comprehensive catalog of all available Scratch 3.0 blocks:\n"
600
+ f"```json\n{json.dumps(ALL_SCRATCH_BLOCKS_CATALOG, indent=2)}\n```\n\n"
601
+ f"Current Scratch project JSON for this sprite (for context, its existing blocks if any, though you should generate a new script entirely if this is a hat block):\n"
602
+ f"```json\n{json.dumps(current_sprite_target, indent=2)}\n```\n\n"
603
+ f"**Instructions for generating the block JSON:**\n"
604
+ f"1. **Start with the event block (Hat Block):** This block's `topLevel` should be `true` and `parent` should be `null`. Its `x` and `y` coordinates should be set (e.g., `x: 0, y: 0` or reasonable offsets for multiple scripts).\n"
605
+ f"2. **Generate a sequence of connected blocks:** For each block, generate a **unique block ID** (e.g., 'abc123def4').\n"
606
+ f"3. **Link blocks correctly:**\n"
607
+ f" * Use the `next` field to point to the ID of the block directly below it in the stack.\n"
608
+ f" * Use the `parent` field to point to the ID of the block directly above it in the stack.\n"
609
+ f" * For the last block in a stack, `next` should be `null`.\n"
610
+ f"4. **Fill `inputs` for parameters and substacks:**\n"
611
+ f" * If a block takes a direct value input (e.g., `motion_movesteps` takes 'STEPS'), provide the input value as an array `[type_code, value_or_block_id]`. The `type_code` for the outer array must be a **number**: `1` (for a primitive/reporter input), `2` (for a block ID input), or `3` (for a variable/list reference).\n"
612
+ f" * `[1, [4, \"10\"]]` for directly embedding a simple number or string literal as a primitive. The inner `4` indicates a number primitive.\n"
613
+ f" * `[1, \"SHADOW_BLOCK_ID\"]` for inputs where a separate `shadow: true` block is used (e.g., a `math_number` block for a number, or a menu/message block for a dropdown/broadcast message).\n"
614
+ f" * `[2, \"CONNECTED_BLOCK_ID\"]` for a block that is plugged into this input (e.g., an operator block).\n"
615
+ f" * **For C-blocks (e.g., `control_forever`, `control_if`, `control_repeat`):** Use the `SUBSTACK` input. The value for `SUBSTACK` must be an array: `[2, \"FIRST_BLOCK_IN_SUBSTACK_ID\"]`. The `2` indicates that the input is a block ID, and `FIRST_BLOCK_IN_SUBSTACK_ID` is the ID of the first block inside the loop/conditional structure.\n"
616
+ f"5. **Define Shadow Blocks for Literals, Dropdowns, and Broadcast Messages:** When a block requires a number, string literal, a selection from a dropdown menu, or a broadcast message, you must define a separate `shadow: true` block and link it.\n"
617
+ f" * **For a number literal (e.g., '10'):**\n"
618
+ f" ```json\n"
619
+ f" \"UNIQUE_SHADOW_NUM_ID\": {{\n"
620
+ f" \"opcode\": \"math_number\",\n"
621
+ f" \"fields\": {{\n"
622
+ f" \"NUM\": [\"10\", null]\n"
623
+ f" }},\n"
624
+ f" \"shadow\": true,\n"
625
+ f" \"parent\": \"PARENT_BLOCK_ID\",\n"
626
+ f" \"topLevel\": false\n"
627
+ f" }}\n"
628
+ f" ```\n"
629
+ f" Then, in the parent block's `inputs`, link to this shadow block: `\"INPUT_NAME\": [1, \"UNIQUE_SHADOW_NUM_ID\"]`\n"
630
+ f" * **For a dropdown menu selection (e.g., 'space' key for `when key pressed`):**\n"
631
+ f" ```json\n"
632
+ f" \"UNIQUE_SHADOW_MENU_ID\": {{\n"
633
+ f" \"opcode\": \"event_whenkeypressed_keymenu\", // Example opcode for a key menu shadow\n"
634
+ f" \"fields\": {{\n"
635
+ f" \"KEY_OPTION\": [\"space\", null]\n"
636
+ f" }},\n"
637
+ f" \"shadow\": true,\n"
638
+ f" \"parent\": \"PARENT_BLOCK_ID\",\n"
639
+ f" \"topLevel\": false\n"
640
+ f" }}\n"
641
+ f" ```\n"
642
+ f" Then, in the parent block's `inputs`, link to this shadow block: `\"KEY_OPTION\": [1, \"UNIQUE_SHADOW_MENU_ID\"]`\n"
643
+ f" * **For a Broadcast Message (e.g., 'gameover' for `broadcast message`):**\n"
644
+ f" ```json\n"
645
+ f" \"UNIQUE_SHADOW_BROADCAST_ID\": {{\n"
646
+ f" \"opcode\": \"event_broadcast_menu\", // This opcode specifically handles the message name\n"
647
+ f" \"fields\": {{\n"
648
+ f" \"BROADCAST_OPTION\": [\"gameover\", null]\n"
649
+ f" }},\n"
650
+ f" \"shadow\": true,\n"
651
+ f" \"parent\": \"PARENT_BROADCAST_BLOCK_ID\",\n"
652
+ f" \"topLevel\": false\n"
653
+ f" }}\n"
654
+ f" ```\n"
655
+ f" Then, in the parent `event_broadcast` or `event_broadcastandwait` block's `inputs`, link to this shadow block: `\"BROADCAST\": [1, \"UNIQUE_SHADOW_BROADCAST_ID\"]`\n"
656
+ f" * **For Sensing Menu Options (e.g., touching 'edge', 'mouse-pointer'):**\n"
657
+ f" ```json\n"
658
+ f" \"UNIQUE_SHADOW_SENSING_MENU_ID\": {{\n"
659
+ f" \"opcode\": \"sensing_touchingobjectmenu\", // Or other sensing menu opcodes like sensing_keyoptions\n"
660
+ f" \"fields\": {{\n"
661
+ f" \"TOUCHINGOBJECTMENU\": [\"_mouse_\", null]\n"
662
+ f" }},\n"
663
+ f" \"shadow\": true,\n"
664
+ f" \"parent\": \"PARENT_SENSING_BLOCK_ID\",\n"
665
+ f" \"topLevel\": false\n"
666
+ f" }}\n"
667
+ f" ```\n"
668
+ f" Then, in the parent `sensing_touchingobject` block's `inputs`, link: `\"TOUCHINGOBJECTMENU\": [1, \"UNIQUE_SHADOW_SENSING_MENU_ID\"]`\n"
669
+ f"6. **`fields` for direct dropdown values/text:** If the block directly uses a dropdown value or text field without an `inputs` connection, use the `fields` dictionary. Example: `\"fields\": {{\"KEY_OPTION\": [\"space\", null]}}`.\n"
670
+ f"7. **`topLevel: true` for hat blocks:** Only the starting (hat) block of a script should have `\"topLevel\": true`.\n"
671
+ f"8. **Return ONLY the JSON object representing all the blocks for THIS SINGLE SCRIPT.** Do NOT wrap it in a 'blocks' key or the full project JSON. The output should be a dictionary where keys are block IDs and values are block definitions."
672
+ )
673
+
674
+ try:
675
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_block_generation_prompt}]})
676
+ raw_response = response["messages"][-1].content
677
+ print(f"Raw response from LLM [BehaviorNodeBuilder - {sprite_name} - {event_description}]:", raw_response)
678
+
679
+ generated_blocks = extract_json_from_llm_response(raw_response)
680
+
681
+ if "blocks" in generated_blocks and isinstance(generated_blocks["blocks"], dict):
682
+ logger.warning(f"LLM returned nested 'blocks' key for {sprite_name}. Unwrapping.")
683
+ generated_blocks = generated_blocks["blocks"]
684
+
685
+ current_sprite_target["blocks"].update(generated_blocks)
686
+ logger.info(f"Behavior blocks added for sprite '{sprite_name}', script '{event_description}' by BehaviorNodeBuilder.")
687
+ except Exception as e:
688
+ logger.error(f"Error generating blocks for sprite '{sprite_name}', script '{event_description}': {e}")
689
+ raise
690
+
691
+ logger.info("Updated project JSON with behavior nodes.")
692
+ print("Updated project JSON with behavior nodes:", json.dumps(project_json, indent=2))
693
+ return {"project_json": project_json}
694
+
695
+ # Node 4: Scoring Function
696
+ def add_scoring_function(state: GameState):
697
+ logger.info("--- Running ScoringNode ---")
698
+ if not state['parsed_game_elements'].get("scoring_goals"):
699
+ logger.info("No scoring goals identified, skipping scoring node.")
700
+ return {} # Return an empty dictionary if no changes are made
701
+
702
+ llm_scoring_prompt = (
703
+ f"Based on the game description '{state['description']}' "
704
+ f"add a scoring mechanism to the Scratch project JSON. "
705
+ f"**Crucially, add a 'score' variable to the Stage's 'variables' dictionary.** The format for variables in `targets[].variables` is "
706
+ f"a dictionary where keys are unique variable IDs (e.g., 'scoreVarId123') and values are arrays `[\"variable_name\", initial_value]`. "
707
+ f"Example: `\"variables\": {{ \"scoreVarId123\": [\"score\", 0] }}`.\n"
708
+ f"Then, add blocks to update it (e.g., 'data_setvariableto', 'data_changevariableby') "
709
+ f"based on positive actions identified in 'scoring_goals' (e.g., jumping over an obstacle). "
710
+ f"**Strictly adhere to the Scratch 3.0 block schema rules for `inputs` and `fields`:**\n"
711
+ f"- For `data_setvariableto` or `data_changevariableby`, the variable name is a `field`: `\"VARIABLE\": [\"score\", null]`.\n"
712
+ f"- The value for `data_setvariableto` or `data_changevariableby` is an `input` linking to a shadow block: `\"VALUE\": [1, [\"num\", \"0\"]]` or `\"VALUE\": [1, [\"num\", \"10\"]]`.\n"
713
+ f"Ensure all block IDs are unique and relationships are correct. No nested `blocks` dictionaries.\n"
714
+ f"The current project JSON is:\n```json\n{json.dumps(state['project_json'], indent=2)}\n```\n"
715
+ f"Output the **complete, valid and updated JSON**."
716
+ )
717
+
718
+ try:
719
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_scoring_prompt}]})
720
+ raw_response = response["messages"][-1].content
721
+ updated_project = extract_json_from_llm_response(raw_response)
722
+ logger.info("Scoring function added by ScoringNode.")
723
+ return {"project_json": updated_project}
724
+ except ValueError as e:
725
+ logger.error(f"ValueError in ScoringNode: {e}")
726
+ raise
727
+ except Exception as e:
728
+ logger.error(f"An unexpected error occurred in ScoringNode: {e}")
729
+ raise
730
+
731
+ # Node 5: Reset Option
732
+ def add_reset_option(state: GameState):
733
+ logger.info("--- Running ResetNode ---")
734
+ if not state['parsed_game_elements'].get("reset_triggers"):
735
+ logger.info("No reset triggers identified, skipping reset node.")
736
+ return {} # Return an empty dictionary if no changes are made
737
+
738
+ llm_reset_prompt = (
739
+ f"Based on the game description '{state['description']}'"
740
+ f"add a reset mechanism to the Scratch project JSON. "
741
+ f"This should typically involve an 'event_whenkeypressed' (e.g., 'r' key) or 'event_whenflagclicked' that resets sprite positions ('motion_gotoxy') "
742
+ f"and any variables (like score) to their initial states, potentially broadcasting a 'reset_game' message. "
743
+ f"**Ensure variable IDs and names are consistent with those in the Stage's 'variables' dictionary.**\n"
744
+ f"**Strictly adhere to the Scratch 3.0 block schema rules provided in the SYSTEM_PROMPT for `inputs` and `fields`:**\n"
745
+ f"- For `event_whenkeypressed`, the key is a `field`: `\"KEY\": [\"r\", null]`.\n"
746
+ f"- For `motion_gotoxy`, `X` and `Y` are `inputs` that link to number reporter shadow blocks: `\"X\": [1, [\"num\", \"-160\"]], \"Y\": [1, [\"num\", \"-110\"]]`.\n"
747
+ f"- For `data_setvariableto`, the variable name is a `field`: `\"VARIABLE\": [\"score\", null]`, and value is an `input` like `\"VALUE\": [1, [\"num\", \"0\"]]`.\n"
748
+ f"- For `event_broadcast`, the message is a `field`: `\"BROADCAST_OPTION\": [\"reset_game\", null]`.\n"
749
+ f"- Ensure all block IDs are unique and relationships are correct. No nested `blocks` dictionaries.\n"
750
+ f"The current project JSON is:\n```json\n{json.dumps(state['project_json'], indent=2)}\n```\n"
751
+ f"Output the **complete, valid and updated JSON**."
752
+ )
753
+
754
+ try:
755
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_reset_prompt}]})
756
+ raw_response = response["messages"][-1].content
757
+ updated_project = extract_json_from_llm_response(raw_response)
758
+ logger.info("Reset option added by ResetNode.")
759
+ return {"project_json": updated_project}
760
+ except ValueError as e:
761
+ logger.error(f"ValueError in ResetNode: {e}")
762
+ raise
763
+ except Exception as e:
764
+ logger.error(f"An unexpected error occurred in ResetNode: {e}")
765
+ raise
766
+
767
+ # Node 6: JSON Validation (and optional Refinement)
768
+ def validate_json(state: GameState):
769
+ logger.info("--- Running JSONValidatorNode ---")
770
+ validation_errors = []
771
+
772
+ try:
773
+ # Basic check if it's a valid JSON structure at least
774
+ json.dumps(state['project_json'])
775
+
776
+ for i, target in enumerate(state['project_json'].get("targets", [])):
777
+ target_name = target.get('name', f'index {i}')
778
+ # Validate objName presence for all targets
779
+ if "objName" not in target:
780
+ validation_errors.append(f"Target '{target_name}' is missing 'objName'.")
781
+
782
+ # Validate variables structure
783
+ if "variables" in target and isinstance(target["variables"], dict):
784
+ for var_id, var_data in target["variables"].items():
785
+ if not isinstance(var_data, list) or len(var_data) != 2 or not isinstance(var_data[0], str):
786
+ validation_errors.append(f"Target '{target_name}' variable '{var_id}' has incorrect format: {var_data}. Expected [\"name\", initial_value].")
787
+ else:
788
+ validation_errors.append(f"Target '{target_name}' has missing or malformed 'variables' section.")
789
+
790
+
791
+ block_ids = set()
792
+ for block_id, block_data in target.get("blocks", {}).items():
793
+ if block_id in block_ids:
794
+ validation_errors.append(f"Duplicate block ID found: '{block_id}' in target '{target_name}'.")
795
+ block_ids.add(block_id)
796
+
797
+ # Validate inputs
798
+ for input_name, input_data in block_data.get("inputs", {}).items():
799
+ if not isinstance(input_data, list):
800
+ validation_errors.append(f"Block '{block_id}' in target '{target_name}' has non-list input '{input_name}': {input_data}. Should be a list.")
801
+ continue
802
+
803
+ if len(input_data) < 2:
804
+ validation_errors.append(f"Block '{block_id}' in target '{target_name}' has malformed input '{input_name}': {input_data}. Expected at least 2 elements.")
805
+ continue
806
+
807
+ if input_data[0] in [1, 2, 3] and isinstance(input_data[1], str) and input_data[1] not in block_ids and input_data[1] != "":
808
+ validation_errors.append(f"Block '{block_id}' in target '{target_name}' references non-existent input block '{input_data[1]}' for input '{input_name}'.")
809
+
810
+ # Validate fields
811
+ for field_name, field_data in block_data.get("fields", {}).items():
812
+ if not isinstance(field_data, list) or len(field_data) < 1 or (len(field_data) > 1 and field_data[1] is not None):
813
+ validation_errors.append(f"Block '{block_id}' in target '{target_name}' has malformed field '{field_name}': {field_data}. Should be [\"value\", null].")
814
+
815
+ # Validate parent
816
+ if "parent" in block_data and block_data["parent"] is not None and block_data["parent"] not in block_ids:
817
+ validation_errors.append(f"Block '{block_id}' in target '{target_name}' references non-existent parent '{block_data['parent']}'.")
818
+
819
+ # Validate next
820
+ if "next" in block_data and block_data["next"] is not None and block_data["next"] not in block_ids:
821
+ validation_errors.append(f"Block '{block_id}' in target '{target_name}' references non-existent next block '{block_data['next']}'.")
822
+
823
+ # Check for nested 'blocks' (common LLM error)
824
+ if "blocks" in block_data and isinstance(block_data["blocks"], dict) and len(block_data["blocks"]) > 0:
825
+ validation_errors.append(f"Block '{block_id}' in target '{target_name}' has a nested 'blocks' dictionary. Blocks should only be at the target's top level and linked via inputs (e.g., SUBSTACK).")
826
+
827
+
828
+ except json.JSONDecodeError as e:
829
+ validation_errors.append(f"Structural JSON error (invalid format) during validation: {e}")
830
+ except Exception as e:
831
+ validation_errors.append(f"An unexpected error occurred during JSON validation: {e}")
832
+
833
+ if validation_errors:
834
+ error_message = "; ".join(validation_errors)
835
+ logger.warning(f"JSON validation failed: {error_message}. Attempting refinement...")
836
+
837
+ llm_refine_prompt = (
838
+ f"The Scratch project JSON you generated failed validation due to the following issues: {error_message}. "
839
+ f"Please review and correct the JSON, ensuring all rules from your SYSTEM_PROMPT are followed, "
840
+ f"especially regarding unique block IDs, correct structure (parent/next/inputs/fields), and valid opcodes. "
841
+ f"Pay extremely close attention to the **exact array format for inputs and fields** (e.g., `\"FIELD_NAME\": [\"value\", null]` and `\"INPUT_NAME\": [1, [\"num\", \"value\"]]` or `[2, \"linked_block_id\"]`). "
842
+ f"Crucially, ensure the `variables` section within each target is formatted as a dictionary where keys are unique IDs and values are `[\"variable_name\", initial_value]`. "
843
+ f"Also, ensure no `blocks` dictionary is nested inside a block definition; use `SUBSTACK` input for C-blocks. "
844
+ f"The current problematic JSON is:\n```json\n{json.dumps(state['project_json'], indent=2)}\n```\n"
845
+ f"Output the **corrected complete JSON**."
846
+ )
847
+ try:
848
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_refine_prompt}]})
849
+ raw_response = response["messages"][-1].content
850
+ refined_project_json = extract_json_from_llm_response(raw_response)
851
+ logger.info("JSON refined and updated by JSONValidatorNode.")
852
+ return {"project_json": refined_project_json}
853
+ except ValueError as e_refine:
854
+ logger.error(f"Refinement also failed to return valid JSON: {e_refine}")
855
+ raise ValueError(f"JSON validation and refinement failed: {e_refine}")
856
+ except Exception as e_refine:
857
+ logger.error(f"An unexpected error occurred during JSON refinement: {e_refine}")
858
+ raise ValueError(f"JSON validation and refinement failed due to unexpected error: {e_refine}")
859
+ else:
860
+ logger.info("JSON validation successful (all checks passed).")
861
+ return {} # Return an empty dictionary if no changes are needed or made in this pass
862
+
863
+ # Build the LangGraph workflow
864
+ workflow = StateGraph(GameState)
865
+
866
+ workflow.add_node("parse_query", parse_query_and_set_initial_positions)
867
+ workflow.add_node("plan_actions", plan_sprite_actions)
868
+ workflow.add_node("build_actions", build_action_nodes)
869
+ workflow.add_node("plan_behaviors", plan_behaviors_node)
870
+ workflow.add_node("build_behaviors", build_behavior_nodes)
871
+ workflow.add_node("add_scoring", add_scoring_function)
872
+ workflow.add_node("add_reset", add_reset_option)
873
+ workflow.add_node("validate_json", validate_json)
874
+
875
+ # Define the flow (edges)
876
+ workflow.set_entry_point("parse_query")
877
+ workflow.add_edge("parse_query", "plan_actions")
878
+ workflow.add_edge("plan_actions", "build_actions")
879
+ workflow.add_edge("build_actions", "plan_behaviors")
880
+ workflow.add_edge("plan_behaviors", "build_behaviors")
881
+ workflow.add_edge("build_behaviors", "add_scoring")
882
+ workflow.add_edge("add_scoring", "add_reset")
883
+ workflow.add_edge("add_reset", "validate_json")
884
+ workflow.add_edge("validate_json", END) # End of the main flow
885
+
886
+ app_graph = workflow.compile()
887
+
888
+ ### Modified `generate_game` Endpoint
889
+
890
+ @app.route("/generate_game", methods=["POST"])
891
+ def generate_game():
892
+ payload = request.json
893
+ desc = payload.get("description", "")
894
+ backdrops = payload.get("backdrops", [])
895
+ sprites = payload.get("sprites", [])
896
+
897
+ logger.info(f"Starting game generation for description: '{desc}'")
898
+
899
+ # 1) Initial skeleton generation
900
+ project_skeleton = {
901
+ "targets": [
902
+ {
903
+ "isStage": True,
904
+ "name":"Stage",
905
+ "objName": "Stage", # ADDED THIS FOR THE STAGE
906
+ "variables":{}, # This must be a dict: { "var_id": ["var_name", value] }
907
+ "lists":{}, "broadcasts":{},
908
+ "blocks":{}, "comments":{},
909
+ "currentCostume": len(backdrops)-1 if backdrops else 0,
910
+ "costumes": [make_costume_entry("backdrops",b["filename"],b["name"])
911
+ for b in backdrops],
912
+ "sounds": [], "volume":100,"layerOrder":0,
913
+ "tempo":60,"videoTransparency":50,"videoState":"on",
914
+ "textToSpeechLanguage": None
915
+ }
916
+ ],
917
+ "monitors": [], "extensions": [], "meta":{
918
+ "semver":"3.0.0","vm":"11.1.0",
919
+ "agent": request.headers.get("User-Agent","")
920
+ }
921
+ }
922
+
923
+ for idx, s in enumerate(sprites, start=1):
924
+ costume = make_costume_entry("sprites", s["filename"], s["name"])
925
+ project_skeleton["targets"].append({
926
+ "isStage": False,
927
+ "name": s["name"],
928
+ "objName": s["name"], # objName is also important for sprites
929
+ "variables":{}, "lists":{}, "broadcasts":{},
930
+ "blocks":{},
931
+ "comments":{},
932
+ "currentCostume":0,
933
+ "costumes":[costume],
934
+ "sounds":[], "volume":100,
935
+ "layerOrder": idx+1,
936
+ "visible":True, "x":0,"y":0,"size":100,"direction":90,
937
+ "draggable":False, "rotationStyle":"all around"
938
+ })
939
+
940
+ logger.info("Initial project skeleton created.")
941
+
942
+ project_id = str(uuid.uuid4())
943
+ project_folder = os.path.join("generated_projects", project_id)
944
+
945
+ try:
946
+ os.makedirs(project_folder, exist_ok=True)
947
+ # Save initial skeleton and copy assets
948
+ project_json_path = os.path.join(project_folder, "project.json")
949
+ with open(project_json_path, "w") as f:
950
+ json.dump(project_skeleton, f, indent=2)
951
+ logger.info(f"Initial project skeleton saved to {project_json_path}")
952
+
953
+ for b in backdrops:
954
+ src_path = os.path.join(app.static_folder, "assets", "backdrops", b["filename"])
955
+ dst_path = os.path.join(project_folder, b["filename"])
956
+ if os.path.exists(src_path):
957
+ shutil.copy(src_path, dst_path)
958
+ else:
959
+ logger.warning(f"Source backdrop asset not found: {src_path}")
960
+ for s in sprites:
961
+ src_path = os.path.join(app.static_folder, "assets", "sprites", s["filename"])
962
+ dst_path = os.path.join(project_folder, s["filename"])
963
+ if os.path.exists(src_path):
964
+ shutil.copy(src_path, dst_path)
965
+ else:
966
+ logger.warning(f"Source sprite asset not found: {src_path}")
967
+ logger.info("Assets copied to project folder.")
968
+
969
+ # Initialize the state for LangGraph as a dictionary matching the TypedDict structure
970
+ initial_state_dict: GameState = { # Type hint for clarity
971
+ "project_json": project_skeleton,
972
+ "description": desc,
973
+ "project_id": project_id,
974
+ "parsed_game_elements": {} # Initialize this as well
975
+ }
976
+
977
+ # Run the LangGraph workflow with the dictionary input.
978
+ final_state_dict: GameState = app_graph.invoke(initial_state_dict)
979
+
980
+ # Access elements from the final_state_dict
981
+ final_project_json = final_state_dict['project_json']
982
+
983
+ # Save the *final* filled project JSON, overwriting the skeleton
984
+ with open(project_json_path, "w") as f:
985
+ json.dump(final_project_json, f, indent=2)
986
+ logger.info(f"Final project JSON saved to {project_json_path}")
987
+
988
+ return jsonify({"message": "Game generated successfully", "project_id": project_id})
989
+
990
+ except Exception as e:
991
+ logger.error(f"Error during game generation workflow for project ID {project_id}: {e}", exc_info=True)
992
+ return jsonify(error=f"Error generating game: {e}"), 500
993
+
994
+
995
+ if __name__=="__main__":
996
+ logger.info("Starting Flask application...")
997
+ app.run(debug=True,port=5000)
scratch_agent/blocks_content.json ADDED
@@ -0,0 +1,1367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "motion_movesteps": {
3
+ "opcode": "motion_movesteps",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "STEPS": [
8
+ 1,
9
+ [
10
+ 4,
11
+ "10"
12
+ ]
13
+ ]
14
+ },
15
+ "fields": {},
16
+ "shadow": false,
17
+ "topLevel": true,
18
+ "x": 464,
19
+ "y": -416
20
+ },
21
+ "motion_turnright": {
22
+ "opcode": "motion_turnright",
23
+ "next": null,
24
+ "parent": null,
25
+ "inputs": {
26
+ "DEGREES": [
27
+ 1,
28
+ [
29
+ 4,
30
+ "15"
31
+ ]
32
+ ]
33
+ },
34
+ "fields": {},
35
+ "shadow": false,
36
+ "topLevel": true,
37
+ "x": 467,
38
+ "y": -316
39
+ },
40
+ "motion_turnleft": {
41
+ "opcode": "motion_turnleft",
42
+ "next": null,
43
+ "parent": null,
44
+ "inputs": {
45
+ "DEGREES": [
46
+ 1,
47
+ [
48
+ 4,
49
+ "15"
50
+ ]
51
+ ]
52
+ },
53
+ "fields": {},
54
+ "shadow": false,
55
+ "topLevel": true,
56
+ "x": 464,
57
+ "y": -210
58
+ },
59
+ "motion_goto": {
60
+ "opcode": "motion_goto",
61
+ "next": null,
62
+ "parent": null,
63
+ "inputs": {
64
+ "TO": [
65
+ 1,
66
+ "@iM=Z?~GCbpC}gT7KAKY"
67
+ ]
68
+ },
69
+ "fields": {},
70
+ "shadow": false,
71
+ "topLevel": true,
72
+ "x": 465,
73
+ "y": -95
74
+ },
75
+ "motion_goto_menu": {
76
+ "opcode": "motion_goto_menu",
77
+ "next": null,
78
+ "parent": "d|J?C902/xy6tD5,|dmB",
79
+ "inputs": {},
80
+ "fields": {
81
+ "TO": [
82
+ "_random_",
83
+ null
84
+ ]
85
+ },
86
+ "shadow": true,
87
+ "topLevel": false
88
+ },
89
+ "motion_gotoxy": {
90
+ "opcode": "motion_gotoxy",
91
+ "next": null,
92
+ "parent": null,
93
+ "inputs": {
94
+ "X": [
95
+ 1,
96
+ [
97
+ 4,
98
+ "0"
99
+ ]
100
+ ],
101
+ "Y": [
102
+ 1,
103
+ [
104
+ 4,
105
+ "0"
106
+ ]
107
+ ]
108
+ },
109
+ "fields": {},
110
+ "shadow": false,
111
+ "topLevel": true,
112
+ "x": 468,
113
+ "y": 12
114
+ },
115
+ "motion_glideto": {
116
+ "opcode": "motion_glideto",
117
+ "next": null,
118
+ "parent": null,
119
+ "inputs": {
120
+ "SECS": [
121
+ 1,
122
+ [
123
+ 4,
124
+ "1"
125
+ ]
126
+ ],
127
+ "TO": [
128
+ 1,
129
+ "{id to destination position}"
130
+ ]
131
+ },
132
+ "fields": {},
133
+ "shadow": false,
134
+ "topLevel": true,
135
+ "x": 470,
136
+ "y": 129
137
+ },
138
+ "motion_glideto_menu": {
139
+ "opcode": "motion_glideto_menu",
140
+ "next": null,
141
+ "parent": null,
142
+ "inputs": {},
143
+ "fields": {
144
+ "TO": [
145
+ "_random_",
146
+ null
147
+ ]
148
+ },
149
+ "shadow": true,
150
+ "topLevel": false
151
+ },
152
+ "motion_glidesecstoxy": {
153
+ "opcode": "motion_glidesecstoxy",
154
+ "next": null,
155
+ "parent": null,
156
+ "inputs": {
157
+ "SECS": [
158
+ 1,
159
+ [
160
+ 4,
161
+ "1"
162
+ ]
163
+ ],
164
+ "X": [
165
+ 1,
166
+ [
167
+ 4,
168
+ "0"
169
+ ]
170
+ ],
171
+ "Y": [
172
+ 1,
173
+ [
174
+ 4,
175
+ "0"
176
+ ]
177
+ ]
178
+ },
179
+ "fields": {},
180
+ "shadow": false,
181
+ "topLevel": true,
182
+ "x": 476,
183
+ "y": 239
184
+ },
185
+ "motion_pointindirection": {
186
+ "opcode": "motion_pointindirection",
187
+ "next": null,
188
+ "parent": null,
189
+ "inputs": {
190
+ "DIRECTION": [
191
+ 1,
192
+ [
193
+ 8,
194
+ "90"
195
+ ]
196
+ ]
197
+ },
198
+ "fields": {},
199
+ "shadow": false,
200
+ "topLevel": true,
201
+ "x": 493,
202
+ "y": 361
203
+ },
204
+ "motion_pointtowards": {
205
+ "opcode": "motion_pointtowards",
206
+ "next": null,
207
+ "parent": null,
208
+ "inputs": {
209
+ "TOWARDS": [
210
+ 1,
211
+ "6xQl1pPk%9E~Znhm*:ng"
212
+ ]
213
+ },
214
+ "fields": {},
215
+ "shadow": false,
216
+ "topLevel": true,
217
+ "x": 492,
218
+ "y": 463
219
+ },
220
+ "motion_pointtowards_menu": {
221
+ "opcode": "motion_pointtowards_menu",
222
+ "next": null,
223
+ "parent": "Ucm$YBs*^9GFTGXCbal@",
224
+ "inputs": {},
225
+ "fields": {
226
+ "TOWARDS": [
227
+ "_mouse_",
228
+ null
229
+ ]
230
+ },
231
+ "shadow": true,
232
+ "topLevel": false
233
+ },
234
+ "motion_changexby": {
235
+ "opcode": "motion_changexby",
236
+ "next": null,
237
+ "parent": null,
238
+ "inputs": {
239
+ "DX": [
240
+ 1,
241
+ [
242
+ 4,
243
+ "10"
244
+ ]
245
+ ]
246
+ },
247
+ "fields": {},
248
+ "shadow": false,
249
+ "topLevel": true,
250
+ "x": 851,
251
+ "y": -409
252
+ },
253
+ "motion_setx": {
254
+ "opcode": "motion_setx",
255
+ "next": null,
256
+ "parent": null,
257
+ "inputs": {
258
+ "X": [
259
+ 1,
260
+ [
261
+ 4,
262
+ "0"
263
+ ]
264
+ ]
265
+ },
266
+ "fields": {},
267
+ "shadow": false,
268
+ "topLevel": true,
269
+ "x": 864,
270
+ "y": -194
271
+ },
272
+ "motion_changeyby": {
273
+ "opcode": "motion_changeyby",
274
+ "next": null,
275
+ "parent": null,
276
+ "inputs": {
277
+ "DY": [
278
+ 1,
279
+ [
280
+ 4,
281
+ "10"
282
+ ]
283
+ ]
284
+ },
285
+ "fields": {},
286
+ "shadow": false,
287
+ "topLevel": true,
288
+ "x": 861,
289
+ "y": -61
290
+ },
291
+ "motion_sety": {
292
+ "opcode": "motion_sety",
293
+ "next": null,
294
+ "parent": null,
295
+ "inputs": {
296
+ "Y": [
297
+ 1,
298
+ [
299
+ 4,
300
+ "0"
301
+ ]
302
+ ]
303
+ },
304
+ "fields": {},
305
+ "shadow": false,
306
+ "topLevel": true,
307
+ "x": 864,
308
+ "y": 66
309
+ },
310
+ "motion_ifonedgebounce": {
311
+ "opcode": "motion_ifonedgebounce",
312
+ "next": null,
313
+ "parent": null,
314
+ "inputs": {},
315
+ "fields": {},
316
+ "shadow": false,
317
+ "topLevel": true,
318
+ "x": 1131,
319
+ "y": -397
320
+ },
321
+ "motion_setrotationstyle": {
322
+ "opcode": "motion_setrotationstyle",
323
+ "next": null,
324
+ "parent": null,
325
+ "inputs": {},
326
+ "fields": {
327
+ "STYLE": [
328
+ "left-right",
329
+ null
330
+ ]
331
+ },
332
+ "shadow": false,
333
+ "topLevel": true,
334
+ "x": 1128,
335
+ "y": -287
336
+ },
337
+ "motion_xposition": {
338
+ "opcode": "motion_xposition",
339
+ "next": null,
340
+ "parent": null,
341
+ "inputs": {},
342
+ "fields": {},
343
+ "shadow": false,
344
+ "topLevel": true,
345
+ "x": 1193,
346
+ "y": -136
347
+ },
348
+ "motion_yposition": {
349
+ "opcode": "motion_yposition",
350
+ "next": null,
351
+ "parent": null,
352
+ "inputs": {},
353
+ "fields": {},
354
+ "shadow": false,
355
+ "topLevel": true,
356
+ "x": 1181,
357
+ "y": -64
358
+ },
359
+ "motion_direction": {
360
+ "opcode": "motion_direction",
361
+ "next": null,
362
+ "parent": null,
363
+ "inputs": {},
364
+ "fields": {},
365
+ "shadow": false,
366
+ "topLevel": true,
367
+ "x": 1188,
368
+ "y": 21
369
+ },
370
+ "control_wait": {
371
+ "opcode": "control_wait",
372
+ "next": null,
373
+ "parent": null,
374
+ "inputs": {
375
+ "DURATION": [
376
+ 1,
377
+ [
378
+ 5,
379
+ "1"
380
+ ]
381
+ ]
382
+ },
383
+ "fields": {},
384
+ "shadow": false,
385
+ "topLevel": true,
386
+ "x": 337,
387
+ "y": 129
388
+ },
389
+ "control_repeat": {
390
+ "opcode": "control_repeat",
391
+ "next": null,
392
+ "parent": null,
393
+ "inputs": {
394
+ "TIMES": [
395
+ 1,
396
+ [
397
+ 6,
398
+ "10"
399
+ ]
400
+ ]
401
+ },
402
+ "fields": {},
403
+ "shadow": false,
404
+ "topLevel": true,
405
+ "x": 348,
406
+ "y": 265
407
+ },
408
+ "control_forever": {
409
+ "opcode": "control_forever",
410
+ "next": null,
411
+ "parent": null,
412
+ "inputs": {},
413
+ "fields": {},
414
+ "shadow": false,
415
+ "topLevel": true,
416
+ "x": 334,
417
+ "y": 439
418
+ },
419
+ "control_if": {
420
+ "opcode": "control_if",
421
+ "next": null,
422
+ "parent": null,
423
+ "inputs": {},
424
+ "fields": {},
425
+ "shadow": false,
426
+ "topLevel": true,
427
+ "x": 331,
428
+ "y": 597
429
+ },
430
+ "control_if_else": {
431
+ "opcode": "control_if_else",
432
+ "next": null,
433
+ "parent": null,
434
+ "inputs": {},
435
+ "fields": {},
436
+ "shadow": false,
437
+ "topLevel": true,
438
+ "x": 335,
439
+ "y": 779
440
+ },
441
+ "control_wait_until": {
442
+ "opcode": "control_wait_until",
443
+ "next": null,
444
+ "parent": null,
445
+ "inputs": {},
446
+ "fields": {},
447
+ "shadow": false,
448
+ "topLevel": true,
449
+ "x": 676,
450
+ "y": 285
451
+ },
452
+ "control_repeat_until": {
453
+ "opcode": "control_repeat_until",
454
+ "next": null,
455
+ "parent": null,
456
+ "inputs": {},
457
+ "fields": {},
458
+ "shadow": false,
459
+ "topLevel": true,
460
+ "x": 692,
461
+ "y": 381
462
+ },
463
+ "control_stop": {
464
+ "opcode": "control_stop",
465
+ "next": null,
466
+ "parent": null,
467
+ "inputs": {},
468
+ "fields": {
469
+ "STOP_OPTION": [
470
+ "all",
471
+ null
472
+ ]
473
+ },
474
+ "shadow": false,
475
+ "topLevel": true,
476
+ "x": 708,
477
+ "y": 545,
478
+ "mutation": {
479
+ "tagName": "mutation",
480
+ "children": [],
481
+ "hasnext": "false"
482
+ }
483
+ },
484
+ "control_start_as_clone": {
485
+ "opcode": "control_start_as_clone",
486
+ "next": null,
487
+ "parent": null,
488
+ "inputs": {},
489
+ "fields": {},
490
+ "shadow": false,
491
+ "topLevel": true,
492
+ "x": 665,
493
+ "y": 672
494
+ },
495
+ "control_create_clone_of": {
496
+ "opcode": "control_create_clone_of",
497
+ "next": null,
498
+ "parent": null,
499
+ "inputs": {
500
+ "CLONE_OPTION": [
501
+ 1,
502
+ "t))DW9(QSKB]3C/3Ou+J"
503
+ ]
504
+ },
505
+ "fields": {},
506
+ "shadow": false,
507
+ "topLevel": true,
508
+ "x": 648,
509
+ "y": 797
510
+ },
511
+ "control_create_clone_of_menu": {
512
+ "opcode": "control_create_clone_of_menu",
513
+ "next": null,
514
+ "parent": "80yo/}Cw++Z.;x[ohh|7",
515
+ "inputs": {},
516
+ "fields": {
517
+ "CLONE_OPTION": [
518
+ "_myself_",
519
+ null
520
+ ]
521
+ },
522
+ "shadow": true,
523
+ "topLevel": false
524
+ },
525
+ "control_delete_this_clone": {
526
+ "opcode": "control_delete_this_clone",
527
+ "next": null,
528
+ "parent": null,
529
+ "inputs": {},
530
+ "fields": {},
531
+ "shadow": false,
532
+ "topLevel": true,
533
+ "x": 642,
534
+ "y": 914
535
+ },
536
+ "event_whenflagclicked": {
537
+ "opcode": "event_whenflagclicked",
538
+ "next": null,
539
+ "parent": null,
540
+ "inputs": {},
541
+ "fields": {},
542
+ "shadow": false,
543
+ "topLevel": true,
544
+ "x": 166,
545
+ "y": -422
546
+ },
547
+ "event_whenkeypressed": {
548
+ "opcode": "event_whenkeypressed",
549
+ "next": null,
550
+ "parent": null,
551
+ "inputs": {},
552
+ "fields": {
553
+ "KEY_OPTION": [
554
+ "space",
555
+ null
556
+ ]
557
+ },
558
+ "shadow": false,
559
+ "topLevel": true,
560
+ "x": 151,
561
+ "y": -329
562
+ },
563
+ "event_whenthisspriteclicked": {
564
+ "opcode": "event_whenthisspriteclicked",
565
+ "next": null,
566
+ "parent": null,
567
+ "inputs": {},
568
+ "fields": {},
569
+ "shadow": false,
570
+ "topLevel": true,
571
+ "x": 156,
572
+ "y": -223
573
+ },
574
+ "event_whenbackdropswitchesto": {
575
+ "opcode": "event_whenbackdropswitchesto",
576
+ "next": null,
577
+ "parent": null,
578
+ "inputs": {},
579
+ "fields": {
580
+ "BACKDROP": [
581
+ "backdrop1",
582
+ null
583
+ ]
584
+ },
585
+ "shadow": false,
586
+ "topLevel": true,
587
+ "x": 148,
588
+ "y": -101
589
+ },
590
+ "event_whengreaterthan": {
591
+ "opcode": "event_whengreaterthan",
592
+ "next": null,
593
+ "parent": null,
594
+ "inputs": {
595
+ "VALUE": [
596
+ 1,
597
+ [
598
+ 4,
599
+ "10"
600
+ ]
601
+ ]
602
+ },
603
+ "fields": {
604
+ "WHENGREATERTHANMENU": [
605
+ "LOUDNESS",
606
+ null
607
+ ]
608
+ },
609
+ "shadow": false,
610
+ "topLevel": true,
611
+ "x": 150,
612
+ "y": 10
613
+ },
614
+ "event_whenbroadcastreceived": {
615
+ "opcode": "event_whenbroadcastreceived",
616
+ "next": null,
617
+ "parent": null,
618
+ "inputs": {},
619
+ "fields": {
620
+ "BROADCAST_OPTION": [
621
+ "message1",
622
+ "5O!nei;S$!c!=hCT}0:a"
623
+ ]
624
+ },
625
+ "shadow": false,
626
+ "topLevel": true,
627
+ "x": 141,
628
+ "y": 118
629
+ },
630
+ "event_broadcast": {
631
+ "opcode": "event_broadcast",
632
+ "next": null,
633
+ "parent": null,
634
+ "inputs": {
635
+ "BROADCAST_INPUT": [
636
+ 1,
637
+ [
638
+ 11,
639
+ "message1",
640
+ "5O!nei;S$!c!=hCT}0:a"
641
+ ]
642
+ ]
643
+ },
644
+ "fields": {},
645
+ "shadow": false,
646
+ "topLevel": true,
647
+ "x": 151,
648
+ "y": 229
649
+ },
650
+ "event_broadcastandwait": {
651
+ "opcode": "event_broadcastandwait",
652
+ "next": null,
653
+ "parent": null,
654
+ "inputs": {
655
+ "BROADCAST_INPUT": [
656
+ 1,
657
+ [
658
+ 11,
659
+ "message1",
660
+ "5O!nei;S$!c!=hCT}0:a"
661
+ ]
662
+ ]
663
+ },
664
+ "fields": {},
665
+ "shadow": false,
666
+ "topLevel": true,
667
+ "x": 157,
668
+ "y": 340
669
+ },
670
+ "sensing_touchingobject": {
671
+ "opcode": "sensing_touchingobject",
672
+ "next": null,
673
+ "parent": null,
674
+ "inputs": {
675
+ "TOUCHINGOBJECTMENU": [
676
+ 1,
677
+ "xSKW9a+wTnM~h~So8Jc]"
678
+ ]
679
+ },
680
+ "fields": {},
681
+ "shadow": false,
682
+ "topLevel": true,
683
+ "x": 359,
684
+ "y": 116
685
+ },
686
+ "sensing_touchingobjectmenu": {
687
+ "opcode": "sensing_touchingobjectmenu",
688
+ "next": null,
689
+ "parent": "Y(n,F@BYzwd4CiN|Bh[P",
690
+ "inputs": {},
691
+ "fields": {
692
+ "TOUCHINGOBJECTMENU": [
693
+ "_mouse_",
694
+ null
695
+ ]
696
+ },
697
+ "shadow": true,
698
+ "topLevel": false
699
+ },
700
+ "sensing_touchingcolor": {
701
+ "opcode": "sensing_touchingcolor",
702
+ "next": null,
703
+ "parent": null,
704
+ "inputs": {
705
+ "COLOR": [
706
+ 1,
707
+ [
708
+ 9,
709
+ "#55b888"
710
+ ]
711
+ ]
712
+ },
713
+ "fields": {},
714
+ "shadow": false,
715
+ "topLevel": true,
716
+ "x": 360,
717
+ "y": 188
718
+ },
719
+ "sensing_coloristouchingcolor": {
720
+ "opcode": "sensing_coloristouchingcolor",
721
+ "next": null,
722
+ "parent": null,
723
+ "inputs": {
724
+ "COLOR": [
725
+ 1,
726
+ [
727
+ 9,
728
+ "#d019f2"
729
+ ]
730
+ ],
731
+ "COLOR2": [
732
+ 1,
733
+ [
734
+ 9,
735
+ "#2b0de3"
736
+ ]
737
+ ]
738
+ },
739
+ "fields": {},
740
+ "shadow": false,
741
+ "topLevel": true,
742
+ "x": 348,
743
+ "y": 277
744
+ },
745
+ "sensing_askandwait": {
746
+ "opcode": "sensing_askandwait",
747
+ "next": null,
748
+ "parent": null,
749
+ "inputs": {
750
+ "QUESTION": [
751
+ 1,
752
+ [
753
+ 10,
754
+ "What's your name?"
755
+ ]
756
+ ]
757
+ },
758
+ "fields": {},
759
+ "shadow": false,
760
+ "topLevel": true,
761
+ "x": 338,
762
+ "y": 354
763
+ },
764
+ "sensing_answer": {
765
+ "opcode": "sensing_answer",
766
+ "next": null,
767
+ "parent": null,
768
+ "inputs": {},
769
+ "fields": {},
770
+ "shadow": false,
771
+ "topLevel": true,
772
+ "x": 782,
773
+ "y": 111
774
+ },
775
+ "sensing_keypressed": {
776
+ "opcode": "sensing_keypressed",
777
+ "next": null,
778
+ "parent": null,
779
+ "inputs": {
780
+ "KEY_OPTION": [
781
+ 1,
782
+ "SNlf@Im$sv%.6ULi-f3i"
783
+ ]
784
+ },
785
+ "fields": {},
786
+ "shadow": false,
787
+ "topLevel": true,
788
+ "x": 762,
789
+ "y": 207
790
+ },
791
+ "sensing_keyoptions": {
792
+ "opcode": "sensing_keyoptions",
793
+ "next": null,
794
+ "parent": "7$xEUO.2hH2R6vh!$(Uj",
795
+ "inputs": {},
796
+ "fields": {
797
+ "KEY_OPTION": [
798
+ "space",
799
+ null
800
+ ]
801
+ },
802
+ "shadow": true,
803
+ "topLevel": false
804
+ },
805
+ "sensing_mousedown": {
806
+ "opcode": "sensing_mousedown",
807
+ "next": null,
808
+ "parent": null,
809
+ "inputs": {},
810
+ "fields": {},
811
+ "shadow": false,
812
+ "topLevel": true,
813
+ "x": 822,
814
+ "y": 422
815
+ },
816
+ "sensing_mousex": {
817
+ "opcode": "sensing_mousex",
818
+ "next": null,
819
+ "parent": null,
820
+ "inputs": {},
821
+ "fields": {},
822
+ "shadow": false,
823
+ "topLevel": true,
824
+ "x": 302,
825
+ "y": 528
826
+ },
827
+ "sensing_mousey": {
828
+ "opcode": "sensing_mousey",
829
+ "next": null,
830
+ "parent": null,
831
+ "inputs": {},
832
+ "fields": {},
833
+ "shadow": false,
834
+ "topLevel": true,
835
+ "x": 668,
836
+ "y": 547
837
+ },
838
+ "sensing_setdragmode": {
839
+ "opcode": "sensing_setdragmode",
840
+ "next": null,
841
+ "parent": null,
842
+ "inputs": {},
843
+ "fields": {
844
+ "DRAG_MODE": [
845
+ "draggable",
846
+ null
847
+ ]
848
+ },
849
+ "shadow": false,
850
+ "topLevel": true,
851
+ "x": 950,
852
+ "y": 574
853
+ },
854
+ "sensing_loudness": {
855
+ "opcode": "sensing_loudness",
856
+ "next": null,
857
+ "parent": null,
858
+ "inputs": {},
859
+ "fields": {},
860
+ "shadow": false,
861
+ "topLevel": true,
862
+ "x": 658,
863
+ "y": 703
864
+ },
865
+ "sensing_timer": {
866
+ "opcode": "sensing_timer",
867
+ "next": null,
868
+ "parent": null,
869
+ "inputs": {},
870
+ "fields": {},
871
+ "shadow": false,
872
+ "topLevel": true,
873
+ "x": 459,
874
+ "y": 671
875
+ },
876
+ "sensing_resettimer": {
877
+ "opcode": "sensing_resettimer",
878
+ "next": null,
879
+ "parent": null,
880
+ "inputs": {},
881
+ "fields": {},
882
+ "shadow": false,
883
+ "topLevel": true,
884
+ "x": 462,
885
+ "y": 781
886
+ },
887
+ "sensing_of": {
888
+ "opcode": "sensing_of",
889
+ "next": null,
890
+ "parent": null,
891
+ "inputs": {
892
+ "OBJECT": [
893
+ 1,
894
+ "t+o*y;iz,!O#aT|qM_+O"
895
+ ]
896
+ },
897
+ "fields": {
898
+ "PROPERTY": [
899
+ "backdrop #",
900
+ null
901
+ ]
902
+ },
903
+ "shadow": false,
904
+ "topLevel": true,
905
+ "x": 997,
906
+ "y": 754
907
+ },
908
+ "sensing_of_object_menu": {
909
+ "opcode": "sensing_of_object_menu",
910
+ "next": null,
911
+ "parent": "[4I2wIG/tNc@LQ-;FbsB",
912
+ "inputs": {},
913
+ "fields": {
914
+ "OBJECT": [
915
+ "_stage_",
916
+ null
917
+ ]
918
+ },
919
+ "shadow": true,
920
+ "topLevel": false
921
+ },
922
+ "sensing_current": {
923
+ "opcode": "sensing_current",
924
+ "next": null,
925
+ "parent": null,
926
+ "inputs": {},
927
+ "fields": {
928
+ "CURRENTMENU": [
929
+ "YEAR",
930
+ null
931
+ ]
932
+ },
933
+ "shadow": false,
934
+ "topLevel": true,
935
+ "x": 627,
936
+ "y": 884
937
+ },
938
+ "sensing_dayssince2000": {
939
+ "opcode": "sensing_dayssince2000",
940
+ "next": null,
941
+ "parent": null,
942
+ "inputs": {},
943
+ "fields": {},
944
+ "shadow": false,
945
+ "topLevel": true,
946
+ "x": 959,
947
+ "y": 903
948
+ },
949
+ "sensing_username": {
950
+ "opcode": "sensing_username",
951
+ "next": null,
952
+ "parent": null,
953
+ "inputs": {},
954
+ "fields": {},
955
+ "shadow": false,
956
+ "topLevel": true,
957
+ "x": 833,
958
+ "y": 757
959
+ },
960
+ "operator_add": {
961
+ "opcode": "operator_add",
962
+ "next": null,
963
+ "parent": null,
964
+ "inputs": {
965
+ "NUM1": [
966
+ 1,
967
+ [
968
+ 4,
969
+ ""
970
+ ]
971
+ ],
972
+ "NUM2": [
973
+ 1,
974
+ [
975
+ 4,
976
+ ""
977
+ ]
978
+ ]
979
+ },
980
+ "fields": {},
981
+ "shadow": false,
982
+ "topLevel": true,
983
+ "x": 128,
984
+ "y": 153
985
+ },
986
+ "operator_subtract": {
987
+ "opcode": "operator_subtract",
988
+ "next": null,
989
+ "parent": null,
990
+ "inputs": {
991
+ "NUM1": [
992
+ 1,
993
+ [
994
+ 4,
995
+ ""
996
+ ]
997
+ ],
998
+ "NUM2": [
999
+ 1,
1000
+ [
1001
+ 4,
1002
+ ""
1003
+ ]
1004
+ ]
1005
+ },
1006
+ "fields": {},
1007
+ "shadow": false,
1008
+ "topLevel": true,
1009
+ "x": 134,
1010
+ "y": 214
1011
+ },
1012
+ "operator_multiply": {
1013
+ "opcode": "operator_multiply",
1014
+ "next": null,
1015
+ "parent": null,
1016
+ "inputs": {
1017
+ "NUM1": [
1018
+ 1,
1019
+ [
1020
+ 4,
1021
+ ""
1022
+ ]
1023
+ ],
1024
+ "NUM2": [
1025
+ 1,
1026
+ [
1027
+ 4,
1028
+ ""
1029
+ ]
1030
+ ]
1031
+ },
1032
+ "fields": {},
1033
+ "shadow": false,
1034
+ "topLevel": true,
1035
+ "x": 134,
1036
+ "y": 278
1037
+ },
1038
+ "operator_divide": {
1039
+ "opcode": "operator_divide",
1040
+ "next": null,
1041
+ "parent": null,
1042
+ "inputs": {
1043
+ "NUM1": [
1044
+ 1,
1045
+ [
1046
+ 4,
1047
+ ""
1048
+ ]
1049
+ ],
1050
+ "NUM2": [
1051
+ 1,
1052
+ [
1053
+ 4,
1054
+ ""
1055
+ ]
1056
+ ]
1057
+ },
1058
+ "fields": {},
1059
+ "shadow": false,
1060
+ "topLevel": true,
1061
+ "x": 138,
1062
+ "y": 359
1063
+ },
1064
+ "operator_random": {
1065
+ "opcode": "operator_random",
1066
+ "next": null,
1067
+ "parent": null,
1068
+ "inputs": {
1069
+ "FROM": [
1070
+ 1,
1071
+ [
1072
+ 4,
1073
+ "1"
1074
+ ]
1075
+ ],
1076
+ "TO": [
1077
+ 1,
1078
+ [
1079
+ 4,
1080
+ "10"
1081
+ ]
1082
+ ]
1083
+ },
1084
+ "fields": {},
1085
+ "shadow": false,
1086
+ "topLevel": true,
1087
+ "x": 311,
1088
+ "y": 157
1089
+ },
1090
+ "operator_gt": {
1091
+ "opcode": "operator_gt",
1092
+ "next": null,
1093
+ "parent": null,
1094
+ "inputs": {
1095
+ "OPERAND1": [
1096
+ 1,
1097
+ [
1098
+ 10,
1099
+ ""
1100
+ ]
1101
+ ],
1102
+ "OPERAND2": [
1103
+ 1,
1104
+ [
1105
+ 10,
1106
+ "50"
1107
+ ]
1108
+ ]
1109
+ },
1110
+ "fields": {},
1111
+ "shadow": false,
1112
+ "topLevel": true,
1113
+ "x": 348,
1114
+ "y": 217
1115
+ },
1116
+ "operator_lt": {
1117
+ "opcode": "operator_lt",
1118
+ "next": null,
1119
+ "parent": null,
1120
+ "inputs": {
1121
+ "OPERAND1": [
1122
+ 1,
1123
+ [
1124
+ 10,
1125
+ ""
1126
+ ]
1127
+ ],
1128
+ "OPERAND2": [
1129
+ 1,
1130
+ [
1131
+ 10,
1132
+ "50"
1133
+ ]
1134
+ ]
1135
+ },
1136
+ "fields": {},
1137
+ "shadow": false,
1138
+ "topLevel": true,
1139
+ "x": 345,
1140
+ "y": 286
1141
+ },
1142
+ "operator_equals": {
1143
+ "opcode": "operator_equals",
1144
+ "next": null,
1145
+ "parent": null,
1146
+ "inputs": {
1147
+ "OPERAND1": [
1148
+ 1,
1149
+ [
1150
+ 10,
1151
+ ""
1152
+ ]
1153
+ ],
1154
+ "OPERAND2": [
1155
+ 1,
1156
+ [
1157
+ 10,
1158
+ "50"
1159
+ ]
1160
+ ]
1161
+ },
1162
+ "fields": {},
1163
+ "shadow": false,
1164
+ "topLevel": true,
1165
+ "x": 345,
1166
+ "y": 372
1167
+ },
1168
+ "operator_and": {
1169
+ "opcode": "operator_and",
1170
+ "next": null,
1171
+ "parent": null,
1172
+ "inputs": {},
1173
+ "fields": {},
1174
+ "shadow": false,
1175
+ "topLevel": true,
1176
+ "x": 701,
1177
+ "y": 158
1178
+ },
1179
+ "operator_or": {
1180
+ "opcode": "operator_or",
1181
+ "next": null,
1182
+ "parent": null,
1183
+ "inputs": {},
1184
+ "fields": {},
1185
+ "shadow": false,
1186
+ "topLevel": true,
1187
+ "x": 705,
1188
+ "y": 222
1189
+ },
1190
+ "operator_not": {
1191
+ "opcode": "operator_not",
1192
+ "next": null,
1193
+ "parent": null,
1194
+ "inputs": {},
1195
+ "fields": {},
1196
+ "shadow": false,
1197
+ "topLevel": true,
1198
+ "x": 734,
1199
+ "y": 283
1200
+ },
1201
+ "operator_join": {
1202
+ "opcode": "operator_join",
1203
+ "next": null,
1204
+ "parent": null,
1205
+ "inputs": {
1206
+ "STRING1": [
1207
+ 1,
1208
+ [
1209
+ 10,
1210
+ "apple "
1211
+ ]
1212
+ ],
1213
+ "STRING2": [
1214
+ 1,
1215
+ [
1216
+ 10,
1217
+ "banana"
1218
+ ]
1219
+ ]
1220
+ },
1221
+ "fields": {},
1222
+ "shadow": false,
1223
+ "topLevel": true,
1224
+ "x": 663,
1225
+ "y": 378
1226
+ },
1227
+ "operator_letter_of": {
1228
+ "opcode": "operator_letter_of",
1229
+ "next": null,
1230
+ "parent": null,
1231
+ "inputs": {
1232
+ "LETTER": [
1233
+ 1,
1234
+ [
1235
+ 6,
1236
+ "1"
1237
+ ]
1238
+ ],
1239
+ "STRING": [
1240
+ 1,
1241
+ [
1242
+ 10,
1243
+ "apple"
1244
+ ]
1245
+ ]
1246
+ },
1247
+ "fields": {},
1248
+ "shadow": false,
1249
+ "topLevel": true,
1250
+ "x": 664,
1251
+ "y": 445
1252
+ },
1253
+ "operator_length": {
1254
+ "opcode": "operator_length",
1255
+ "next": null,
1256
+ "parent": null,
1257
+ "inputs": {
1258
+ "STRING": [
1259
+ 1,
1260
+ [
1261
+ 10,
1262
+ "apple"
1263
+ ]
1264
+ ]
1265
+ },
1266
+ "fields": {},
1267
+ "shadow": false,
1268
+ "topLevel": true,
1269
+ "x": 664,
1270
+ "y": 521
1271
+ },
1272
+ "operator_contains": {
1273
+ "opcode": "operator_contains",
1274
+ "next": null,
1275
+ "parent": null,
1276
+ "inputs": {
1277
+ "STRING1": [
1278
+ 1,
1279
+ [
1280
+ 10,
1281
+ "apple"
1282
+ ]
1283
+ ],
1284
+ "STRING2": [
1285
+ 1,
1286
+ [
1287
+ 10,
1288
+ "a"
1289
+ ]
1290
+ ]
1291
+ },
1292
+ "fields": {},
1293
+ "shadow": false,
1294
+ "topLevel": true,
1295
+ "x": 634,
1296
+ "y": 599
1297
+ },
1298
+ "operator_mod": {
1299
+ "opcode": "operator_mod",
1300
+ "next": null,
1301
+ "parent": null,
1302
+ "inputs": {
1303
+ "NUM1": [
1304
+ 1,
1305
+ [
1306
+ 4,
1307
+ ""
1308
+ ]
1309
+ ],
1310
+ "NUM2": [
1311
+ 1,
1312
+ [
1313
+ 4,
1314
+ ""
1315
+ ]
1316
+ ]
1317
+ },
1318
+ "fields": {},
1319
+ "shadow": false,
1320
+ "topLevel": true,
1321
+ "x": 295,
1322
+ "y": 594
1323
+ },
1324
+ "operator_round": {
1325
+ "opcode": "operator_round",
1326
+ "next": null,
1327
+ "parent": null,
1328
+ "inputs": {
1329
+ "NUM": [
1330
+ 1,
1331
+ [
1332
+ 4,
1333
+ ""
1334
+ ]
1335
+ ]
1336
+ },
1337
+ "fields": {},
1338
+ "shadow": false,
1339
+ "topLevel": true,
1340
+ "x": 307,
1341
+ "y": 674
1342
+ },
1343
+ "operator_mathop": {
1344
+ "opcode": "operator_mathop",
1345
+ "next": null,
1346
+ "parent": null,
1347
+ "inputs": {
1348
+ "NUM": [
1349
+ 1,
1350
+ [
1351
+ 4,
1352
+ ""
1353
+ ]
1354
+ ]
1355
+ },
1356
+ "fields": {
1357
+ "OPERATOR": [
1358
+ "abs",
1359
+ null
1360
+ ]
1361
+ },
1362
+ "shadow": false,
1363
+ "topLevel": true,
1364
+ "x": 280,
1365
+ "y": 754
1366
+ }
1367
+ }
scratch_agent/blocks_defination.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ALL_SCRATCH_BLOCKS_CATALOG = {
2
+ "motion_movesteps": {
3
+ "opcode": "motion_movesteps",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "STEPS": [
8
+ 1,
9
+ [
10
+ 4,
11
+ "10"
12
+ ]
13
+ ]
14
+ },
15
+ "fields": {},
16
+ "shadow": False,
17
+ "topLevel": True,
18
+ "x": 464,
19
+ "y": -416
20
+ },
21
+ "motion_turnright": {
22
+ "opcode": "motion_turnright",
23
+ "inputs": {"DEGREES": [1, [4, "15"]]},
24
+ "fields": {},
25
+ "shadow": False
26
+ },
27
+ "motion_turnleft": {
28
+ "opcode": "motion_turnleft",
29
+ "inputs": {"DEGREES": [1, [4, "15"]]},
30
+ "fields": {},
31
+ "shadow": False
32
+ },
33
+ "motion_goto": {
34
+ "opcode": "motion_goto",
35
+ "inputs": {"TO": [1, None]}, # TO input will be filled by a menu block ID
36
+ "fields": {},
37
+ "shadow": False
38
+ },
39
+ "motion_goto_menu": { # Shadow block for motion_goto
40
+ "opcode": "motion_goto_menu",
41
+ "inputs": {},
42
+ "fields": {"TO": ["_random_", None]},
43
+ "shadow": True
44
+ },
45
+ "motion_gotoxy": {
46
+ "opcode": "motion_gotoxy",
47
+ "inputs": {"X": [1, [4, "0"]], "Y": [1, [4, "0"]]},
48
+ "fields": {},
49
+ "shadow": False
50
+ },
51
+ "motion_glideto": {
52
+ "opcode": "motion_glideto",
53
+ "inputs": {"SECS": [1, [4, "1"]], "TO": [1, None]}, # TO input will be filled by a menu block ID
54
+ "fields": {},
55
+ "shadow": False
56
+ },
57
+ "motion_glideto_menu": { # Shadow block for motion_glideto
58
+ "opcode": "motion_glideto_menu",
59
+ "inputs": {},
60
+ "fields": {"TO": ["_random_", None]},
61
+ "shadow": True
62
+ },
63
+ "motion_glidesecstoxy": {
64
+ "opcode": "motion_glidesecstoxy",
65
+ "inputs": {"SECS": [1, [4, "1"]], "X": [1, [4, "0"]], "Y": [1, [4, "0"]]},
66
+ "fields": {},
67
+ "shadow": False
68
+ },
69
+ "motion_pointindirection": {
70
+ "opcode": "motion_pointindirection",
71
+ "inputs": {"DIRECTION": [1, [8, "90"]]},
72
+ "fields": {},
73
+ "shadow": False
74
+ },
75
+ "motion_pointtowards": {
76
+ "opcode": "motion_pointtowards",
77
+ "inputs": {"TOWARDS": [1, None]}, # TOWARDS input will be filled by a menu block ID
78
+ "fields": {},
79
+ "shadow": False
80
+ },
81
+ "motion_pointtowards_menu": { # Shadow block for motion_pointtowards
82
+ "opcode": "motion_pointtowards_menu",
83
+ "inputs": {},
84
+ "fields": {"TOWARDS": ["_mouse_", None]},
85
+ "shadow": True
86
+ },
87
+ "motion_changexby": {
88
+ "opcode": "motion_changexby",
89
+ "inputs": {"DX": [1, [4, "10"]]},
90
+ "fields": {},
91
+ "shadow": False
92
+ },
93
+ "motion_setx": {
94
+ "opcode": "motion_setx",
95
+ "inputs": {"X": [1, [4, "0"]]},
96
+ "fields": {},
97
+ "shadow": False
98
+ },
99
+ "motion_changeyby": {
100
+ "opcode": "motion_changeyby",
101
+ "inputs": {"DY": [1, [4, "10"]]},
102
+ "fields": {},
103
+ "shadow": False
104
+ },
105
+ "motion_sety": {
106
+ "opcode": "motion_sety",
107
+ "inputs": {"Y": [1, [4, "0"]]},
108
+ "fields": {},
109
+ "shadow": False
110
+ },
111
+ "motion_ifonedgebounce": {
112
+ "opcode": "motion_ifonedgebounce",
113
+ "next": None, "parent": None, "inputs": {}, "fields": {}, "shadow": False
114
+ },
115
+ "motion_setrotationstyle": {
116
+ "opcode": "motion_setrotationstyle",
117
+ "inputs": {},
118
+ "fields": {"STYLE": ["left-right", None]},
119
+ "shadow": False
120
+ },
121
+ "motion_xposition": {
122
+ "opcode": "motion_xposition",
123
+ "inputs": {}, "fields": {}, "shadow": False
124
+ },
125
+ "motion_yposition": {
126
+ "opcode": "motion_yposition",
127
+ "inputs": {}, "fields": {}, "shadow": False
128
+ },
129
+ "motion_direction": {
130
+ "opcode": "motion_direction",
131
+ "inputs": {}, "fields": {}, "shadow": False
132
+ },
133
+ # Control Blocks
134
+ "control_wait": {
135
+ "opcode": "control_wait",
136
+ "inputs": {"DURATION": [1, [5, "1"]]},
137
+ "fields": {},
138
+ "shadow": False
139
+ },
140
+ "control_repeat": {
141
+ "opcode": "control_repeat",
142
+ "inputs": {"TIMES": [1, [6, "10"]], "SUBSTACK": [2, None]}, # SUBSTACK to be filled
143
+ "fields": {},
144
+ "shadow": False
145
+ },
146
+ "control_forever": {
147
+ "opcode": "control_forever",
148
+ "inputs": {"SUBSTACK": [2, None]}, # SUBSTACK to be filled
149
+ "fields": {},
150
+ "shadow": False
151
+ },
152
+ "control_if": {
153
+ "opcode": "control_if",
154
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, # CONDITION and SUBSTACK to be filled
155
+ "fields": {},
156
+ "shadow": False
157
+ },
158
+ "control_if_else": {
159
+ "opcode": "control_if_else",
160
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None], "SUBSTACK2": [2, None]}, # All to be filled
161
+ "fields": {},
162
+ "shadow": False
163
+ },
164
+ "control_wait_until": {
165
+ "opcode": "control_wait_until",
166
+ "inputs": {"CONDITION": [2, None]},
167
+ "fields": {},
168
+ "shadow": False
169
+ },
170
+ "control_repeat_until": {
171
+ "opcode": "control_repeat_until",
172
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]},
173
+ "fields": {},
174
+ "shadow": False
175
+ },
176
+ "control_stop": {
177
+ "opcode": "control_stop",
178
+ "inputs": {},
179
+ "fields": {"STOP_OPTION": ["all", None]},
180
+ "shadow": False,
181
+ "mutation": {"tagName": "mutation", "children": [], "hasnext": "false"}
182
+ },
183
+ "control_start_as_clone": {
184
+ "opcode": "control_start_as_clone",
185
+ "inputs": {}, "fields": {}, "shadow": False
186
+ },
187
+ "control_create_clone_of": {
188
+ "opcode": "control_create_clone_of",
189
+ "inputs": {"CLONE_OPTION": [1, None]}, # CLONE_OPTION to be filled by menu block ID
190
+ "fields": {},
191
+ "shadow": False
192
+ },
193
+ "control_create_clone_of_menu": { # Shadow block for control_create_clone_of
194
+ "opcode": "control_create_clone_of_menu",
195
+ "inputs": {},
196
+ "fields": {"CLONE_OPTION": ["_myself_", None]},
197
+ "shadow": True
198
+ },
199
+ "control_delete_this_clone": {
200
+ "opcode": "control_delete_this_clone",
201
+ "inputs": {}, "fields": {}, "shadow": False
202
+ },
203
+ # Event Blocks
204
+ "event_whenflagclicked": {
205
+ "opcode": "event_whenflagclicked",
206
+ "inputs": {}, "fields": {}, "shadow": False
207
+ },
208
+ "event_whenkeypressed": {
209
+ "opcode": "event_whenkeypressed",
210
+ "inputs": {},
211
+ "fields": {"KEY_OPTION": ["space", None]},
212
+ "shadow": False
213
+ },
214
+ "event_whenthisspriteclicked": {
215
+ "opcode": "event_whenthisspriteclicked",
216
+ "inputs": {}, "fields": {}, "shadow": False
217
+ },
218
+ "event_whenbackdropswitchesto": {
219
+ "opcode": "event_whenbackdropswitchesto",
220
+ "inputs": {},
221
+ "fields": {"BACKDROP": ["backdrop1", None]},
222
+ "shadow": False
223
+ },
224
+ "event_whengreaterthan": {
225
+ "opcode": "event_whengreaterthan",
226
+ "inputs": {"VALUE": [1, [4, "10"]]},
227
+ "fields": {"WHENGREATERTHANMENU": ["LOUDNESS", None]},
228
+ "shadow": False
229
+ },
230
+ "event_whenbroadcastreceived": {
231
+ "opcode": "event_whenbroadcastreceived",
232
+ "inputs": {},
233
+ "fields": {"BROADCAST_OPTION": ["message1", None]},
234
+ "shadow": False
235
+ },
236
+ "event_broadcast": {
237
+ "opcode": "event_broadcast",
238
+ "inputs": {"BROADCAST_INPUT": [1, None]}, # BROADCAST_INPUT to be filled by menu or text block
239
+ "fields": {},
240
+ "shadow": False
241
+ },
242
+ "event_broadcastandwait": {
243
+ "opcode": "event_broadcastandwait",
244
+ "inputs": {"BROADCAST_INPUT": [1, None]},
245
+ "fields": {},
246
+ "shadow": False
247
+ },
248
+ # Sensing Blocks (example, can be expanded)
249
+ "sensing_touchingobject": {
250
+ "opcode": "sensing_touchingobject",
251
+ "inputs": {"TOUCHINGOBJECTMENU": [1, None]}, # Input for menu block ID
252
+ "fields": {},
253
+ "shadow": False
254
+ },
255
+ "sensing_touchingobjectmenu": { # Shadow block for sensing_touchingobject
256
+ "opcode": "sensing_touchingobjectmenu",
257
+ "inputs": {},
258
+ "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]},
259
+ "shadow": True
260
+ },
261
+ # Operators (example, can be expanded)
262
+ "operator_add": {
263
+ "opcode": "operator_add",
264
+ "inputs": {"NUM1": [1, [4, "0"]], "NUM2": [1, [4, "0"]]},
265
+ "fields": {},
266
+ "shadow": False
267
+ },
268
+ "operator_equals": {
269
+ "opcode": "operator_equals",
270
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, ""]]},
271
+ "fields": {},
272
+ "shadow": False
273
+ }
274
+ }
scratch_agent/chats/user_chat_no_113a02f93587a6622aa86c130e31f025.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "assistant": "Mistral AI is a privately held company founded in April 2023 by Arthur Mensch, Guillaume Lample, and Timothée Lacroix. The ownership is distributed among its founders, investors, and possibly key employees.",
4
+ "user": "who is the owner of mistral ?"
5
+ },
6
+ {
7
+ "assistant": "The database is empty or does not contain any tables.",
8
+ "user": "can you get me details about the database ?"
9
+ },
10
+ {
11
+ "assistant": "The database contains the following tables with their respective CREATE statements: </p>\n\n<p>CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT);<br />\nCREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT);<br />\nCREATE TABLE projects (id INTEGER PRIMARY KEY, name TEXT, employee<em>id INTEGER);<br />\nCREATE TABLE sqlite</em>sequence(name,seq);",
12
+ "user": "can you get me details about the database ?"
13
+ },
14
+ {
15
+ "user": "ok you get me the top can you format this into table ?",
16
+ "assistant": "<table>\n<thead>\n<tr>\n <th>id</th>\n <th>name</th>\n <th>salary</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n <td>1</td>\n <td>John Doe</td>\n <td>100000</td>\n</tr>\n<tr>\n <td>2</td>\n <td>Jane Smith</td>\n <td>90000</td>\n</tr>\n<tr>\n <td>3</td>\n <td>Bob Brown</td>\n <td>85000</td>\n</tr>\n<tr>\n <td>4</td>\n <td>Alice White</td>\n <td>80000</td>\n</tr>\n<tr>\n <td>5</td>\n <td>Mike Davis</td>\n <td>75000</td>\n</tr>\n</tbody>\n</table>"
17
+ }
18
+ ]
scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg ADDED
scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/bcf454acf82e4504149f7ffe07081dbc.svg ADDED
scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/e7c147730f19d284bcd7b3f00af19bb6.svg ADDED
scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/project.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "objName": "Stage",
7
+ "variables": {},
8
+ "lists": {},
9
+ "broadcasts": {},
10
+ "blocks": {},
11
+ "comments": {},
12
+ "currentCostume": 0,
13
+ "costumes": [
14
+ {
15
+ "name": "Blue Sky",
16
+ "bitmapResolution": 1,
17
+ "dataFormat": "svg",
18
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
19
+ "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",
20
+ "rotationCenterX": 240,
21
+ "rotationCenterY": 180
22
+ }
23
+ ],
24
+ "sounds": [],
25
+ "volume": 100,
26
+ "layerOrder": 0,
27
+ "tempo": 60,
28
+ "videoTransparency": 50,
29
+ "videoState": "on",
30
+ "textToSpeechLanguage": null
31
+ },
32
+ {
33
+ "isStage": false,
34
+ "name": "Sprite1",
35
+ "objName": "Sprite1",
36
+ "variables": {},
37
+ "lists": {},
38
+ "broadcasts": {},
39
+ "blocks": {},
40
+ "comments": {},
41
+ "currentCostume": 0,
42
+ "costumes": [
43
+ {
44
+ "name": "Sprite1",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
48
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
49
+ "rotationCenterX": 0,
50
+ "rotationCenterY": 0
51
+ }
52
+ ],
53
+ "sounds": [],
54
+ "volume": 100,
55
+ "layerOrder": 2,
56
+ "visible": true,
57
+ "x": 0,
58
+ "y": 0,
59
+ "size": 100,
60
+ "direction": 90,
61
+ "draggable": false,
62
+ "rotationStyle": "all around"
63
+ },
64
+ {
65
+ "isStage": false,
66
+ "name": "soccer ball",
67
+ "objName": "soccer ball",
68
+ "variables": {},
69
+ "lists": {},
70
+ "broadcasts": {},
71
+ "blocks": {},
72
+ "comments": {},
73
+ "currentCostume": 0,
74
+ "costumes": [
75
+ {
76
+ "name": "soccer ball",
77
+ "bitmapResolution": 1,
78
+ "dataFormat": "svg",
79
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
80
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
81
+ "rotationCenterX": 0,
82
+ "rotationCenterY": 0
83
+ }
84
+ ],
85
+ "sounds": [],
86
+ "volume": 100,
87
+ "layerOrder": 3,
88
+ "visible": true,
89
+ "x": 0,
90
+ "y": 0,
91
+ "size": 100,
92
+ "direction": 90,
93
+ "draggable": false,
94
+ "rotationStyle": "all around"
95
+ }
96
+ ],
97
+ "monitors": [],
98
+ "extensions": [],
99
+ "meta": {
100
+ "semver": "3.0.0",
101
+ "vm": "11.1.0",
102
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
103
+ }
104
+ }
scratch_agent/helper_function.py ADDED
File without changes
scratch_agent/last_updated.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ app.py [updated the action plan node and action builder node and also add the behaviour plan and behaviour builder node ]
2
+ app.py updated version in sys_scratch_2.py [enhacement in the node of action planner]
scratch_agent/logs.txt ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 127.0.0.1 - - [27/Jun/2025 11:17:10] "GET / HTTP/1.1" 200 -
2
+ 127.0.0.1 - - [27/Jun/2025 11:17:10] "GET /list_assets HTTP/1.1" 200 -
3
+ LLM response: {'messages': [HumanMessage(content='Below is the partially-built Scratch project JSON. Fill in all the `blocks` sections with the correct opcodes for a “generate a game for jumping cat over the obstacle” game. Do not touch any of the existing asset entries.\n\n```json\n{\n "targets": [\n {\n "isStage": true,\n "name": "Stage",\n "variables": {},\n "lists": {},\n "broadcasts": {},\n "blocks": {},\n "comments": {},\n "currentCostume": 0,\n "costumes": [\n {\n "name": "Blue Sky",\n "bitmapResolution": 1,\n "dataFormat": "svg",\n "assetId": "e7c147730f19d284bcd7b3f00af19bb6",\n "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",\n "rotationCenterX": 240,\n "rotationCenterY": 180\n }\n ],\n "sounds": [],\n "volume": 100,\n "layerOrder": 0,\n "tempo": 60,\n "videoTransparency": 50,\n "videoState": "on",\n "textToSpeechLanguage": null\n },\n {\n "isStage": false,\n "name": "Sprite1",\n "variables": {},\n "lists": {},\n "broadcasts": {},\n "blocks": {},\n "comments": {},\n "currentCostume": 0,\n "costumes": [\n {\n "name": "Sprite1",\n "bitmapResolution": 1,\n "dataFormat": "svg",\n "assetId": "bcf454acf82e4504149f7ffe07081dbc",\n "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",\n "rotationCenterX": 0,\n "rotationCenterY": 0\n }\n ],\n "sounds": [],\n "volume": 100,\n "layerOrder": 2,\n "visible": true,\n "x": 0,\n "y": 0,\n "size": 100,\n "direction": 90,\n "draggable": false,\n "rotationStyle": "all around"\n },\n {\n "isStage": false,\n "name": "soccer ball",\n "variables": {},\n "lists": {},\n "broadcasts": {},\n "blocks": {},\n "comments": {},\n "currentCostume": 0,\n "costumes": [\n {\n "name": "soccer ball",\n "bitmapResolution": 1,\n "dataFormat": "svg",\n "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",\n "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",\n "rotationCenterX": 0,\n "rotationCenterY": 0\n }\n ],\n "sounds": [],\n "volume": 100,\n "layerOrder": 3,\n "visible": true,\n "x": 0,\n "y": 0,\n "size": 100,\n "direction": 90,\n "draggable": false,\n "rotationStyle": "all around"\n }\n ],\n "monitors": [],\n "extensions": [],\n "meta": {\n "semver": "3.0.0",\n "vm": "11.1.0",\n "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"\n }\n}\n```', additional_kwargs={}, response_metadata={}, id='33b15971-5503-4078-a823-6a34f5c7d58c'), AIMessage(content='<think>\nAlright, so the user has provided a partially built Scratch project JSON and wants me to fill in the `blocks` sections for a jumping cat game where the cat jumps over obstacles and keeps score. Let me break down how I should approach this.\n\nFirst, I need to understand the structure of the existing JSON. There are three targets: the Stage, Sprite1 (which I assume is the cat), and a soccer ball (the obstacle). Each target has empty `blocks`, so I need to populate these with the appropriate Scratch opcodes.\n\nStarting with the Stage. The Stage typically handles the game loop, score tracking, and game over conditions. I\'ll need to initialize the score variable, reset the game when the flag is clicked, and check for collisions between the cat and the obstacle. If they collide, the game should stop, and the score should update accordingly.\n\nFor Sprite1, the cat, I need to implement the jumping mechanics. The cat should jump when the spacebar is pressed, which involves changing its Y position. I\'ll also set up a falling mechanism using gravity so the cat lands after jumping. Additionally, I\'ll add a game over condition if the cat touches the obstacle.\n\nThe soccer ball, as the obstacle, should move continuously from right to left. I\'ll set its movement so that when it goes beyond the left edge, it resets to the right. This creates an endless obstacle for the cat to jump over.\n\nI\'ll also need to handle broadcasting messages like "game over" and "reset" to coordinate actions between sprites. Variables like `Score` will be used to keep track of the player\'s progress, and the `broadcast` blocks will help synchronize the game state across all sprites.\n\nI should ensure that each block has a unique ID to prevent conflicts. I\'ll use a systematic approach, assigning each block a new unique string ID as I go. For example, starting with "id1", "id2", and so on, making sure each is distinct.\n\nI\'ll also make sure that the logic flows correctly. For instance, when the game starts (flag clicked), the Stage initializes the score and starts the game loop. The cat\'s movement is controlled by key presses and gravity, while the obstacle moves autonomously. Collision detection is handled by the Stage, which stops the game if a collision occurs.\n\nFinally, I\'ll test the logic in my mind to ensure that all parts work together. The cat jumps when the spacebar is pressed, the obstacle moves and resets, the score increases over time, and the game stops when the cat hits the obstacle. Everything seems to fit together logically, so I can proceed to fill in the `blocks` sections accordingly.\n</think>\n\nI\'ll help fill in the blocks for the jumping cat game. Here\'s the completed JSON with the blocks implemented:\n\n```json\n{\n "targets": [\n {\n "isStage": true,\n "name": "Stage",\n "variables": {\n "Score": {\n "value": 0\n }\n },\n "lists": {},\n "broadcasts": {},\n "blocks": {\n "id1": {\n "opcode": "event_whenflagclicked",\n "next": "id2",\n "parent": null\n },\n "id2": {\n "opcode": "variables_set",\n "variable": "Score",\n "value": 0,\n "next": "id3",\n "parent": null\n },\n "id3": {\n "opcode": "control_forever",\n "substack": "id4",\n "next": null,\n "parent": null\n },\n "id4": {\n "opcode": "control_waituntil",\n "condition": {\n "opcode": "sensing_touchingobject",\n "object": "soccer ball",\n "spriteOrStage": "Sprite1"\n },\n "next": "id5",\n "parent": "id3"\n },\n "id5": {\n "opcode": "control_stop",\n
4
+ "substack": null,\n "next": null,\n "parent": null\n }\n },\n "comments": {},\n "currentCostume": 0,\n "costumes": [\n {\n "name": "Blue Sky",\n "bitmapResolution": 1,\n "dataFormat": "svg",\n "assetId": "e7c147730f19d284bcd7b3f00af19bb6",\n "md5ext": "e7c147730f19d284bcd7b3f00af19bb6.svg",\n "rotationCenterX": 240,\n "rotationCenterY": 180\n }\n ],\n "sounds": [],\n "volume": 100,\n "layerOrder": 0,\n "tempo": 60,\n "videoTransparency": 50,\n "videoState": "on",\n "textToSpeechLanguage": null\n },\n {\n "isStage": false,\n "name": "Sprite1",\n "variables": {},\n "lists": {},\n "broadcasts": {},\n "blocks": {\n "id6": {\n "opcode": "event_whenkeypressed",\n "keys": "space",\n "next": "id7",\n "parent": null\n },\n "id7": {\n "opcode": "motion_changeyby",\n "delta": 100,\n "next": "id8",\n "parent": "id6"\n },\n "id8": {\n "opcode": "control_wait",\n "duration": 0.5,\n "next": "id9",\n "parent": "id7"\n },\n "id9": {\n "opcode": "motion_changeyby",\n "delta": -100,\n "next": null,\n "parent": "id8"\n },\n "id10": {\n "opcode": "event_whenflagclicked",\n "next": "id11",\n "parent": null\n },\n "id11": {\n "opcode": "motion_gotoxy",\n "x": -200,\n "y": 0,\n "next": "id12",\n "parent": "id10"\n },\n "id12": {\n "opcode": "control_forever",\n "substack": "id13",\n "next": null,\n "parent": "id11"\n },\n "id13": {\n "opcode": "motion_changeyby",\n "delta": -5,\n "next": null,\n "parent": "id12"\n }\n },\n "comments": {},\n "currentCostume": 0,\n "costumes": [\n {\n "name": "Sprite1",\n "bitmapResolution": 1,\n "dataFormat": "svg",\n "assetId": "bcf454acf82e4504149f7ffe07081dbc",\n "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",\n "rotationCenterX": 0,\n "rotationCenterY": 0\n }\n ],\n "sounds": [],\n "volume": 100,\n "layerOrder": 2,\n "visible": true,\n "x": -200,\n "y": 0,\n "size": 100,\n "direction": 90,\n "draggable": false,\n "rotationStyle": "all around"\n },\n {\n "isStage": false,\n "name": "soccer ball",\n "variables": {},\n "lists": {},\n "broadcasts": {},\n "blocks": {\n "id14": {\n "opcode": "event_whenflagclicked",\n "next": "id15",\n "parent": null\n },\n "id15": {\n "opcode": "motion_gotoxy",\n "x": 240,\n "y": 0,\n "next": "id16",\n "parent": "id14"\n },\n "id16": {\n "opcode": "control_forever",\n "substack": "id17",\n "next": null,\n "parent": "id15"\n },\n "id17": {\n "opcode": "motion_change-x-by",\n "delta": -10,\n "next": "id18",\n "parent": "id16"\n },\n "id18": {\n "opcode": "control_if",\n "condition": {\n "opcode": "motion_xposition",\n "rel": "<",\n "value": -240\n },\n "substack": "id19",\n "next": null,\n "parent": "id17"\n },\n "id19": {\n "opcode": "motion_gotoxy",\n "x": 240,\n "y": 0,\n "next": null,\n "parent": "id18"\n }\n },\n "comments": {},\n "currentCostume": 0,\n "costumes": [\n {\n "name": "soccer ball",\n "bitmapResolution": 1,\n "dataFormat": "svg",\n "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",\n "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",\n "rotationCenterX": 0,\n "rotationCenterY": 0\n }\n ],\n "sounds": [],\n "volume": 100,\n "layerOrder": 3,\n "visible": true,\n "x": 240,\n "y": 0,\n "size": 100,\n "direction": 90,\n "draggable": false,\n "rotationStyle": "all around"\n }\n ],\n "monitors": [],\n "extensions": [],\n "meta": {\n "semver": "3.0.0",\n "vm": "11.1.0",\n "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"\n }\n}\n```\n\nThis implementation includes:\n\n1. Stage:\n - Initializes score when flag clicked\n - Continuously checks for collision between cat and obstacle\n - Stops game when collision occurs\n\n2. Cat (Sprite1):\n - Jumps when space key is pressed\n - Has gravity to fall back down\n - Moves continuously with gravity\n\n3. Soccer Ball (Obstacle):\n - Moves from right to left continuously\n - Resets position when reaching left edge\n\nThe game features:\n- Simple jumping mechanics\n- Obstacle avoidance\n- Score tracking\n- Game over on collision\n- Continuous platform-style gameplay\n\nThe cat will jump when you press the spacebar, and you need to avoid the moving soccer ball. The game ends if the cat touches the soccer ball.', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 2349, 'prompt_tokens': 1993, 'total_tokens': 4342, 'completion_time': 8.541818182, 'prompt_time': 0.151161261, 'queue_time': 0.054655800000000004, 'total_time': 8.692979443}, 'model_name': 'deepseek-r1-distill-llama-70b', 'system_fingerprint': 'fp_1bbe7845ec', 'finish_reason': 'stop', 'logprobs': None}, id='run--e3939ad0-f6a2-46d9-bfa9-0636d594ca44-0', usage_metadata={'input_tokens': 1993, 'output_tokens': 2349, 'total_tokens': 4342})]}
5
+ 127.0.0.1 - - [27/Jun/2025 11:18:11] "POST /generate_game HTTP/1.1" 200 -
6
+ * Detected change in 'D:\\DEV PATEL\\2025\\scratch_VLM\\scratch_agent\\scratch_app.py', reloading
7
+ * Restarting with stat
8
+ * Debugger is active!
9
+ * Debugger PIN: 488-914-959
10
+ * Detected change in 'D:\\DEV PATEL\\2025\\scratch_VLM\\scratch_agent\\scratch_app.py', reloading
11
+ * Restarting with stat
12
+ * Debugger is active!
13
+ * Debugger PIN: 488-914-959
14
+ * Detected change in 'D:\\DEV PATEL\\2025\\scratch_VLM\\scratch_agent\\scratch_app.py', reloading
15
+ * Restarting with stat
16
+ * Debugger is active!
17
+ * Debugger PIN: 488-914-959
18
+ * Detected change in 'D:\\DEV PATEL\\2025\\scratch_VLM\\scratch_agent\\scratch_app.py', reloading
19
+ * Restarting with stat
20
+ * Debugger is active!
21
+ * Debugger PIN: 488-914-959
22
+ * Detected change in 'D:\\DEV PATEL\\2025\\scratch_VLM\\scratch_agent\\scratch_app.py', reloading
23
+ * Restarting with stat
24
+ * Debugger is active!
25
+ * Debugger PIN: 488-914-959
26
+ * Detected change in 'D:\\DEV PATEL\\2025\\scratch_VLM\\scratch_agent\\scratch_app.py', reloading
27
+ * Restarting with stat
28
+ * Debugger is active!
29
+ * Debugger PIN: 488-914-959
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
scratch_agent/multi_Agent_scratch.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import json
4
+
5
+ # This would typically be an LLM API call
6
+ def call_llm(prompt_text, temperature=0.7, max_tokens=1024):
7
+ """
8
+ Simulates an LLM API call. In a real application, this would interact
9
+ with a service like Google Gemini, OpenAI GPT, or Anthropic Claude.
10
+ """
11
+ print(f"\n--- LLM Call ---")
12
+ print(f"Prompt: {prompt_text[:500]}...") # Print a truncated prompt
13
+ # Placeholder for actual LLM response logic
14
+ # For demonstration, we'll return a predefined structure based on the agent's role.
15
+ if "Role: User Intent & Query Interpreter" in prompt_text:
16
+ return {
17
+ "identified_objects": {
18
+ "Cat": {"type": "Sprite", "role": "player_character"},
19
+ "Obstacle": {"type": "Sprite", "role": "object_to_avoid"},
20
+ "Score": {"type": "Variable", "role": "game_score"}
21
+ },
22
+ "high_level_behaviors": [
23
+ {"entity": "Cat", "action": "jump", "trigger": "spacebar_press"},
24
+ {"entity": "Obstacle", "action": "appear_randomly", "frequency": "periodic", "movement": "left_across_screen"},
25
+ {"entity": "Score", "action": "increment", "trigger": "cat_jumps_over_obstacle"},
26
+ {"entity": "Game", "action": "end", "condition": "Cat_touches_Obstacle"}
27
+ ],
28
+ "constraints": ["game_over_on_collision"]
29
+ }
30
+ elif "Role: Visual Context Analyzer" in prompt_text:
31
+ return {
32
+ "visual_elements": {
33
+ "cat_image.png": {"inferred_role": "Sprite", "properties": {"costume": "cat_default", "initial_size": "medium"}},
34
+ "obstacle_image.png": {"inferred_role": "Sprite", "properties": {"costume": "rock", "initial_size": "small"}},
35
+ "background_image.png": {"inferred_role": "Backdrop", "properties": {"background_name": "grassland"}}
36
+ },
37
+ "inferred_details": ["cat_is_player", "obstacle_is_moving_hazard"]
38
+ }
39
+ elif "Role: Game Logic & Object Planner" in prompt_text:
40
+ return {
41
+ "plan_id": "cat_jumping_game_v1",
42
+ "sprites": {
43
+ "Cat": {
44
+ "properties": {"x": -200, "y": -100, "costume": "cat_default"},
45
+ "behaviors": [
46
+ {"event": "when_key_pressed", "key": "space", "action_sequence": ["change_y_up", "change_y_down"]},
47
+ {"event": "when_touching_obstacle", "action_sequence": ["game_over"]}
48
+ ]
49
+ },
50
+ "Obstacle": {
51
+ "properties": {"x": 250, "y": -100, "costume": "rock", "speed": 5},
52
+ "behaviors": [
53
+ {"event": "when_green_flag_clicked", "action_sequence": ["hide", "wait_random", "go_to_random_x", "show", "glide_left_until_edge", "hide", "reset_position_and_repeat"]},
54
+ ]
55
+ }
56
+ },
57
+ "variables": {
58
+ "Score": {"initial_value": 0}
59
+ },
60
+ "backdrops": {
61
+ "Stage": {"name": "grassland"}
62
+ },
63
+ "game_flow": {
64
+ "start": "Green Flag Clicked",
65
+ "score_mechanic": "Increment Score when Cat jumps over Obstacle", # This is a high-level logic, needs decomposition
66
+ "game_over_condition": "Cat touches Obstacle",
67
+ "game_over_action": "Stop all scripts, display 'Game Over' message"
68
+ }
69
+ }
70
+ elif "Role: Scratch Block Generator" in prompt_text:
71
+ # Example of raw Scratch blocks (simplified JSON for demonstration)
72
+ return {
73
+ "Cat_Scripts": [
74
+ {
75
+ "type": "event_whenkeypressed",
76
+ "id": "event_1",
77
+ "fields": {"KEY_OPTION": ["space"]},
78
+ "next": {
79
+ "type": "motion_changeyby",
80
+ "id": "motion_1_up",
81
+ "inputs": {"DY": ["10"]},
82
+ "next": {
83
+ "type": "control_wait",
84
+ "id": "control_1_wait",
85
+ "inputs": {"DURATION": ["0.1"]},
86
+ "next": {
87
+ "type": "motion_changeyby",
88
+ "id": "motion_1_down",
89
+ "inputs": {"DY": ["-10"]},
90
+ "next": None
91
+ }
92
+ }
93
+ }
94
+ },
95
+ {
96
+ "type": "event_whenflagclicked",
97
+ "id": "event_2",
98
+ "next": {
99
+ "type": "control_forever",
100
+ "id": "control_2_forever",
101
+ "inputs": {
102
+ "SUBSTACK": {
103
+ "type": "sensing_touchingobject",
104
+ "id": "sensing_1",
105
+ "fields": {"TOUCHINGOBJECTMENU": ["_mouse_"]}, # Placeholder, should be Obstacle
106
+ "next": {
107
+ "type": "control_if",
108
+ "id": "control_3_if",
109
+ "inputs": {
110
+ "CONDITION": ["sensing_1"],
111
+ "SUBSTACK": {
112
+ "type": "control_stop",
113
+ "id": "control_4_stop",
114
+ "fields": {"STOP_OPTION": ["all"]},
115
+ "next": {
116
+ "type": "looks_sayforsecs",
117
+ "id": "looks_1_say",
118
+ "inputs": {"MESSAGE": ["Game Over!"], "SECS": ["2"]},
119
+ "next": None
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ],
129
+ "Obstacle_Scripts": [
130
+ # ... (simplified)
131
+ ]
132
+ }
133
+ elif "Role: Code Validator & Refinement" in prompt_text:
134
+ # Simulate validation result
135
+ if "motion_1_up" in prompt_text and "motion_1_down" in prompt_text and "control_forever" in prompt_text:
136
+ return {
137
+ "validation_status": "PASS",
138
+ "errors": [],
139
+ "refined_blocks": "Validated Scratch JSON or visual representation"
140
+ }
141
+ else:
142
+ return {
143
+ "validation_status": "FAIL",
144
+ "errors": [{"type": "LogicalError", "description": "Jump sequence incomplete or missing 'touching obstacle' condition.", "agent_to_reprompt": "Game Logic & Object Planner"}],
145
+ "refined_blocks": None
146
+ }
147
+ return "LLM processing failed or returned unexpected output."
148
+
149
+ class AIAgent:
150
+ def __init__(self, name, role, description):
151
+ self.name = name
152
+ self.role = role
153
+ self.description = description
154
+
155
+ def generate_prompt(self, context, task_description):
156
+ # Basic prompt structure for each agent
157
+ prompt = f"Agent Name: {self.name}\n"
158
+ prompt += f"Role: {self.role}\n"
159
+ prompt += f"Description: {self.description}\n\n"
160
+ prompt += f"Context: {json.dumps(context, indent=2)}\n\n"
161
+ prompt += f"Task: {task_description}\n\n"
162
+ prompt += "Please provide your output in a structured JSON format as specified for your role."
163
+ return prompt
164
+
165
+ def execute(self, context, task_description):
166
+ prompt = self.generate_prompt(context, task_description)
167
+ response = call_llm(prompt)
168
+ return response
169
+
170
+ class MultiAgentSystem:
171
+ def __init__(self):
172
+ self.user_intent_agent = AIAgent(
173
+ name="UserIntent&QueryInterpreter",
174
+ role="User Intent & Query Interpreter",
175
+ description="Processes natural language input, extracts explicit and implicit requirements, identifies entities, actions, and constraints. Translates ambiguous human language into structured, machine-interpretable intent."
176
+ )
177
+ self.visual_context_agent = AIAgent(
178
+ name="VisualContextAnalyzer",
179
+ role="Visual Context Analyzer",
180
+ description="Processes image inputs, identifies objects, infers potential roles (sprites/backdrops), extracts visual properties, and infers missing details based on visual cues."
181
+ )
182
+ self.game_logic_planner_agent = AIAgent(
183
+ name="GameLogic&ObjectPlanner",
184
+ role="Game Logic & Object Planner",
185
+ description="Synthesizes a comprehensive game plan from structured intent and visual context. Defines objects, properties, and actions, mapping high-level game mechanics to Scratch-compatible concepts. Crucial for inferring logical connections and default behaviors."
186
+ )
187
+ self.scratch_block_generator_agent = AIAgent(
188
+ name="ScratchBlockGenerator",
189
+ role="Scratch Block Generator",
190
+ description="Translates the detailed game logic plan into specific Scratch code blocks. Accurately selects block categories and types, populates parameters, and arranges them in a valid, executable sequence. STRICTLY adheres to Scratch's block connection rules, parent-child relations, and hierarchical levels."
191
+ )
192
+ self.code_validator_agent = AIAgent(
193
+ name="CodeValidator&Refinement",
194
+ role="Code Validator & Refinement",
195
+ description="Reviews generated block sequences for logical consistency, completeness, and strict adherence to Scratch's execution model and syntax. Checks for correct block hierarchy, proper connections (parent-child), and identifies errors. Provides structured feedback for refinement."
196
+ )
197
+
198
+ def run_workflow(self, user_query, images=None):
199
+ print(f"Starting workflow for query: '{user_query}'")
200
+
201
+ # Step 1 & 2: User Input, Intent & Visual Analysis (Parallel & Consolidated)
202
+ intent_data = self.user_intent_agent.execute(
203
+ context={"user_input": user_query},
204
+ task_description="Analyze the user query to extract key entities, desired actions, and overarching intent. Classify intent (e.g., 'create a game', 'add feature')."
205
+ )
206
+ print(f"\nUser Intent: {json.dumps(intent_data, indent=2)}")
207
+
208
+ visual_data = {}
209
+ if images:
210
+ visual_data = self.visual_context_agent.execute(
211
+ context={"images_provided": images},
212
+ task_description="Process provided images to detect objects, infer roles (sprite/backdrop), and extract relevant visual properties. Correlate visual information with textual intent."
213
+ )
214
+ print(f"\nVisual Context: {json.dumps(visual_data, indent=2)}")
215
+
216
+ # Consolidate initial context for the planner
217
+ initial_context = {
218
+ "user_intent": intent_data,
219
+ "visual_context": visual_data
220
+ }
221
+
222
+ # Step 3: Game Logic Planning
223
+ game_plan = self.game_logic_planner_agent.execute(
224
+ context=initial_context,
225
+ task_description="Formulate a detailed game plan including sprites, backdrops, variables, properties, and high-level logical behaviors. Infer and complete any missing details based on common game patterns."
226
+ )
227
+ print(f"\nGame Plan: {json.dumps(game_plan, indent=2)}")
228
+
229
+ # Step 4: Scratch Block Generation
230
+ # Emphasize parent-child and level handling here for the LLM
231
+ block_generation_task = (
232
+ "Translate the detailed game plan into specific Scratch code blocks. "
233
+ "Ensure accurate selection, parameterization, and arrangement of blocks. "
234
+ "STRICTLY ADHERE to Scratch's block connection rules, especially for **parent-child relationships** "
235
+ "and **top-level vs. lower-level block placement**. "
236
+ "Hat blocks must be top-level. Stack blocks form sequential chains (parent-child). "
237
+ "C-blocks (e.g., 'if-then', 'repeat') must contain child blocks within their 'C' shape. "
238
+ "Boolean and Reporter blocks must fit into their designated slots as child inputs. "
239
+ "Output in a JSON format consistent with Scratch's internal representation. "
240
+ "Example of parent-child/level: "
241
+ " Event block (Hat, top-level) -> "
242
+ " Forever block (C-block, child of Event, parent of inner blocks) -> "
243
+ " Motion block (Stack, child of Forever) -> "
244
+ " Looks block (Stack, child of Motion)"
245
+ )
246
+ raw_scratch_blocks = self.scratch_block_generator_agent.execute(
247
+ context={"game_plan": game_plan},
248
+ task_description=block_generation_task
249
+ )
250
+ print(f"\nRaw Scratch Blocks (JSON): {json.dumps(raw_scratch_blocks, indent=2)}")
251
+
252
+ # Step 5: Validation & Refinement
253
+ validation_report = self.code_validator_agent.execute(
254
+ context={"raw_scratch_blocks": raw_scratch_blocks, "original_game_plan": game_plan},
255
+ task_description=(
256
+ "Perform rigorous static analysis on the generated Scratch blocks. "
257
+ "Verify correct block hierarchy (e.g., Hat blocks at top, C-blocks containing children). "
258
+ "Ensure proper block connections, validating parent-child relationships and sequential flow. "
259
+ "Check for logical consistency, completeness against the game plan, and absence of orphaned/disconnected blocks. "
260
+ "Identify potential errors such as uninitialized variables or infinite loops without exit conditions. "
261
+ "If errors are found, suggest specific refinements or indicate which upstream agent needs to be re-prompted (e.g., 'Game Logic & Object Planner' for fundamental logic flaws)."
262
+ )
263
+ )
264
+ print(f"\nValidation Report: {json.dumps(validation_report, indent=2)}")
265
+
266
+ # Iterative refinement loop (simplified for this example)
267
+ if validation_report.get("validation_status") == "FAIL":
268
+ print("\nValidation failed. Initiating refinement process (simplified).")
269
+ # In a real system, this would trigger a more complex re-prompting loop
270
+ # based on validation_report.get("agent_to_reprompt")
271
+ # For now, just indicate that refinement is needed.
272
+ return {"status": "Refinement Needed", "details": validation_report["errors"]}
273
+ else:
274
+ print("\nScratch blocks generated and validated successfully!")
275
+ return {"status": "Success", "final_scratch_json": raw_scratch_blocks}
276
+
277
+ if __name__ == "__main__":
278
+ system = MultiAgentSystem()
279
+
280
+ # Example 1: Simple Cat Jumping Game
281
+ user_query_1 = "Make a cat jumping game with score count. The cat jumps when I press spacebar. An obstacle appears and moves across the screen. Score increases when the cat jumps over the obstacle. Game over when cat touches obstacle."
282
+ images_1 = ["cat_image.png", "obstacle_image.png", "background_image.png"]
283
+ result_1 = system.run_workflow(user_query_1, images_1)
284
+ print(f"\n--- Final Result 1 ---")
285
+ print(json.dumps(result_1, indent=2))
286
+
287
+ print("\n" + "="*80 + "\n")
288
+
289
+ # Example 2: Modified Behavior (demonstrates potential refinement need)
290
+ # This example specifically aims to highlight how the validation could catch
291
+ # issues if the block generation doesn't handle parent-child correctly.
292
+ # The simulated LLM responses will (for now) still "pass" in a simplified manner,
293
+ # but the prompt emphasizes the structural requirements.
294
+ user_query_2 = "Create a game where a character moves left and right, and collects coins. Display the score."
295
+ images_2 = ["character_image.png", "coin_image.png"]
296
+ result_2 = system.run_workflow(user_query_2, images_2)
297
+ print(f"\n--- Final Result 2 ---")
298
+ print(json.dumps(result_2, indent=2))
scratch_agent/project.json ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "targets": [
3
+ {
4
+ "isStage": true,
5
+ "name": "Stage",
6
+ "variables": {
7
+ "`jEk@4|i[#Fk?(8x)AV.-my variable": [
8
+ "my variable",
9
+ 0
10
+ ]
11
+ },
12
+ "lists": {},
13
+ "broadcasts": {},
14
+ "blocks": {},
15
+ "comments": {},
16
+ "currentCostume": 3,
17
+ "costumes": [
18
+ {
19
+ "name": "backdrop1",
20
+ "dataFormat": "svg",
21
+ "assetId": "cd21514d0531fdffb22204e0ec5ed84a",
22
+ "md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg",
23
+ "rotationCenterX": 240,
24
+ "rotationCenterY": 180
25
+ },
26
+ {
27
+ "name": "Blue Sky",
28
+ "bitmapResolution": 1,
29
+ "dataFormat": "svg",
30
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
31
+ "rotationCenterX": 240,
32
+ "rotationCenterY": 180
33
+ },
34
+ {
35
+ "name": "backdrop2",
36
+ "bitmapResolution": 1,
37
+ "dataFormat": "svg",
38
+ "assetId": "cd21514d0531fdffb22204e0ec5ed84a",
39
+ "md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg",
40
+ "rotationCenterX": 0,
41
+ "rotationCenterY": 0
42
+ },
43
+ {
44
+ "name": "Blue Sky2",
45
+ "bitmapResolution": 1,
46
+ "dataFormat": "svg",
47
+ "assetId": "e7c147730f19d284bcd7b3f00af19bb6",
48
+ "rotationCenterX": 240,
49
+ "rotationCenterY": 180
50
+ }
51
+ ],
52
+ "sounds": [
53
+ {
54
+ "name": "pop",
55
+ "assetId": "83a9787d4cb6f3b7632b4ddfebf74367",
56
+ "dataFormat": "wav",
57
+ "format": "",
58
+ "rate": 48000,
59
+ "sampleCount": 1123,
60
+ "md5ext": "83a9787d4cb6f3b7632b4ddfebf74367.wav"
61
+ }
62
+ ],
63
+ "volume": 100,
64
+ "layerOrder": 0,
65
+ "tempo": 60,
66
+ "videoTransparency": 50,
67
+ "videoState": "on",
68
+ "textToSpeechLanguage": null
69
+ },
70
+ {
71
+ "isStage": false,
72
+ "name": "Sprite1",
73
+ "variables": {},
74
+ "lists": {},
75
+ "broadcasts": {},
76
+ "blocks": {
77
+ "HeG?L#U~_ildB(ic9ZwO": {
78
+ "opcode": "event_whenkeypressed",
79
+ "next": "!@|Vg5}f][PzBxn7,h%j",
80
+ "parent": null,
81
+ "inputs": {},
82
+ "fields": {
83
+ "KEY_OPTION": [
84
+ "space",
85
+ null
86
+ ]
87
+ },
88
+ "shadow": false,
89
+ "topLevel": true,
90
+ "x": 382,
91
+ "y": -1089
92
+ },
93
+ "?*7?sGY(4|RK(`J{`YvK": {
94
+ "opcode": "motion_changeyby",
95
+ "next": "ev{jmIX(#gCrUv$SKe}3",
96
+ "parent": "NN-[c:W-u{LT~3-#DVjQ",
97
+ "inputs": {
98
+ "DY": [
99
+ 1,
100
+ [
101
+ 4,
102
+ "10"
103
+ ]
104
+ ]
105
+ },
106
+ "fields": {},
107
+ "shadow": false,
108
+ "topLevel": false
109
+ },
110
+ "uQb.+am;/3j{KtfvvY=?": {
111
+ "opcode": "motion_changeyby",
112
+ "next": "b|XvAr*(skbOSkVU:u2E",
113
+ "parent": "TRZwkak|p_::+$CC;C^X",
114
+ "inputs": {
115
+ "DY": [
116
+ 1,
117
+ [
118
+ 4,
119
+ "-10"
120
+ ]
121
+ ]
122
+ },
123
+ "fields": {},
124
+ "shadow": false,
125
+ "topLevel": false
126
+ },
127
+ "NN-[c:W-u{LT~3-#DVjQ": {
128
+ "opcode": "control_repeat",
129
+ "next": "TRZwkak|p_::+$CC;C^X",
130
+ "parent": "{Aa*M~f6BX*#bBX2$)JG",
131
+ "inputs": {
132
+ "TIMES": [
133
+ 1,
134
+ [
135
+ 6,
136
+ "10"
137
+ ]
138
+ ],
139
+ "SUBSTACK": [
140
+ 2,
141
+ "?*7?sGY(4|RK(`J{`YvK"
142
+ ]
143
+ },
144
+ "fields": {},
145
+ "shadow": false,
146
+ "topLevel": false
147
+ },
148
+ "TRZwkak|p_::+$CC;C^X": {
149
+ "opcode": "control_repeat",
150
+ "next": "1Ogqg13fokw/A*=yiyh2",
151
+ "parent": "NN-[c:W-u{LT~3-#DVjQ",
152
+ "inputs": {
153
+ "TIMES": [
154
+ 1,
155
+ [
156
+ 6,
157
+ "10"
158
+ ]
159
+ ],
160
+ "SUBSTACK": [
161
+ 2,
162
+ "uQb.+am;/3j{KtfvvY=?"
163
+ ]
164
+ },
165
+ "fields": {},
166
+ "shadow": false,
167
+ "topLevel": false
168
+ },
169
+ "{Aa*M~f6BX*#bBX2$)JG": {
170
+ "opcode": "looks_nextcostume",
171
+ "next": "NN-[c:W-u{LT~3-#DVjQ",
172
+ "parent": "!@|Vg5}f][PzBxn7,h%j",
173
+ "inputs": {},
174
+ "fields": {},
175
+ "shadow": false,
176
+ "topLevel": false
177
+ },
178
+ "1Ogqg13fokw/A*=yiyh2": {
179
+ "opcode": "looks_nextcostume",
180
+ "next": null,
181
+ "parent": "TRZwkak|p_::+$CC;C^X",
182
+ "inputs": {},
183
+ "fields": {},
184
+ "shadow": false,
185
+ "topLevel": false
186
+ },
187
+ "!@|Vg5}f][PzBxn7,h%j": {
188
+ "opcode": "sound_play",
189
+ "next": "{Aa*M~f6BX*#bBX2$)JG",
190
+ "parent": "HeG?L#U~_ildB(ic9ZwO",
191
+ "inputs": {
192
+ "SOUND_MENU": [
193
+ 1,
194
+ "WkNn[~_gzFkfuW-PMF=C"
195
+ ]
196
+ },
197
+ "fields": {},
198
+ "shadow": false,
199
+ "topLevel": false
200
+ },
201
+ "WkNn[~_gzFkfuW-PMF=C": {
202
+ "opcode": "sound_sounds_menu",
203
+ "next": null,
204
+ "parent": "!@|Vg5}f][PzBxn7,h%j",
205
+ "inputs": {},
206
+ "fields": {
207
+ "SOUND_MENU": [
208
+ "Meow",
209
+ null
210
+ ]
211
+ },
212
+ "shadow": true,
213
+ "topLevel": false
214
+ },
215
+ "AJf.#me!i1j||Sq@67[|": {
216
+ "opcode": "motion_gotoxy",
217
+ "next": "I7)jIy8M@sq7Sd8BCpYX",
218
+ "parent": "`y8dy4h(h@cr*LA.UXVV",
219
+ "inputs": {
220
+ "X": [
221
+ 1,
222
+ [
223
+ 4,
224
+ "-160"
225
+ ]
226
+ ],
227
+ "Y": [
228
+ 1,
229
+ [
230
+ 4,
231
+ "-110"
232
+ ]
233
+ ]
234
+ },
235
+ "fields": {},
236
+ "shadow": false,
237
+ "topLevel": false
238
+ },
239
+ "I7)jIy8M@sq7Sd8BCpYX": {
240
+ "opcode": "looks_switchcostumeto",
241
+ "next": null,
242
+ "parent": "AJf.#me!i1j||Sq@67[|",
243
+ "inputs": {
244
+ "COSTUME": [
245
+ 1,
246
+ "x!JZEl$}gc#kFEk:%w8-"
247
+ ]
248
+ },
249
+ "fields": {},
250
+ "shadow": false,
251
+ "topLevel": false
252
+ },
253
+ "x!JZEl$}gc#kFEk:%w8-": {
254
+ "opcode": "looks_costume",
255
+ "next": null,
256
+ "parent": "I7)jIy8M@sq7Sd8BCpYX",
257
+ "inputs": {},
258
+ "fields": {
259
+ "COSTUME": [
260
+ "costume1",
261
+ null
262
+ ]
263
+ },
264
+ "shadow": true,
265
+ "topLevel": false
266
+ },
267
+ "`y8dy4h(h@cr*LA.UXVV": {
268
+ "opcode": "event_whenkeypressed",
269
+ "next": "AJf.#me!i1j||Sq@67[|",
270
+ "parent": null,
271
+ "inputs": {},
272
+ "fields": {
273
+ "KEY_OPTION": [
274
+ "r",
275
+ null
276
+ ]
277
+ },
278
+ "shadow": false,
279
+ "topLevel": true,
280
+ "x": 375,
281
+ "y": -1326
282
+ },
283
+ "ev{jmIX(#gCrUv$SKe}3": {
284
+ "opcode": "control_wait",
285
+ "next": null,
286
+ "parent": "?*7?sGY(4|RK(`J{`YvK",
287
+ "inputs": {
288
+ "DURATION": [
289
+ 1,
290
+ [
291
+ 5,
292
+ "0.0001"
293
+ ]
294
+ ]
295
+ },
296
+ "fields": {},
297
+ "shadow": false,
298
+ "topLevel": false
299
+ },
300
+ "b|XvAr*(skbOSkVU:u2E": {
301
+ "opcode": "control_wait",
302
+ "next": null,
303
+ "parent": "uQb.+am;/3j{KtfvvY=?",
304
+ "inputs": {
305
+ "DURATION": [
306
+ 1,
307
+ [
308
+ 5,
309
+ "0.0001"
310
+ ]
311
+ ]
312
+ },
313
+ "fields": {},
314
+ "shadow": false,
315
+ "topLevel": false
316
+ }
317
+ },
318
+ "comments": {},
319
+ "currentCostume": 0,
320
+ "costumes": [
321
+ {
322
+ "name": "costume1",
323
+ "bitmapResolution": 1,
324
+ "dataFormat": "svg",
325
+ "assetId": "bcf454acf82e4504149f7ffe07081dbc",
326
+ "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg",
327
+ "rotationCenterX": 48,
328
+ "rotationCenterY": 50
329
+ },
330
+ {
331
+ "name": "costume2",
332
+ "bitmapResolution": 1,
333
+ "dataFormat": "svg",
334
+ "assetId": "0fb9be3e8397c983338cb71dc84d0b25",
335
+ "md5ext": "0fb9be3e8397c983338cb71dc84d0b25.svg",
336
+ "rotationCenterX": 46,
337
+ "rotationCenterY": 53
338
+ }
339
+ ],
340
+ "sounds": [
341
+ {
342
+ "name": "Meow",
343
+ "assetId": "83c36d806dc92327b9e7049a565c6bff",
344
+ "dataFormat": "wav",
345
+ "format": "",
346
+ "rate": 48000,
347
+ "sampleCount": 40681,
348
+ "md5ext": "83c36d806dc92327b9e7049a565c6bff.wav"
349
+ }
350
+ ],
351
+ "volume": 100,
352
+ "layerOrder": 2,
353
+ "visible": true,
354
+ "x": -160,
355
+ "y": -110,
356
+ "size": 100,
357
+ "direction": 90,
358
+ "draggable": false,
359
+ "rotationStyle": "all around"
360
+ },
361
+ {
362
+ "isStage": false,
363
+ "name": "Soccer Ball",
364
+ "variables": {},
365
+ "lists": {},
366
+ "broadcasts": {},
367
+ "blocks": {
368
+ "|SKxcVN5TNKDX,gr~az]": {
369
+ "opcode": "motion_gotoxy",
370
+ "next": "N,aJ1Xfm7L]wAh3~vW(G",
371
+ "parent": "9AB%|aA#uIm%c[*E@-G!",
372
+ "inputs": {
373
+ "X": [
374
+ 1,
375
+ [
376
+ 4,
377
+ "240"
378
+ ]
379
+ ],
380
+ "Y": [
381
+ 1,
382
+ [
383
+ 4,
384
+ "-135"
385
+ ]
386
+ ]
387
+ },
388
+ "fields": {},
389
+ "shadow": false,
390
+ "topLevel": false
391
+ },
392
+ "N,aJ1Xfm7L]wAh3~vW(G": {
393
+ "opcode": "motion_glidesecstoxy",
394
+ "next": null,
395
+ "parent": "|SKxcVN5TNKDX,gr~az]",
396
+ "inputs": {
397
+ "SECS": [
398
+ 1,
399
+ [
400
+ 4,
401
+ "3"
402
+ ]
403
+ ],
404
+ "X": [
405
+ 1,
406
+ [
407
+ 4,
408
+ "-240"
409
+ ]
410
+ ],
411
+ "Y": [
412
+ 1,
413
+ [
414
+ 4,
415
+ "-135"
416
+ ]
417
+ ]
418
+ },
419
+ "fields": {},
420
+ "shadow": false,
421
+ "topLevel": false
422
+ },
423
+ "9AB%|aA#uIm%c[*E@-G!": {
424
+ "opcode": "control_forever",
425
+ "next": null,
426
+ "parent": "j$vhNZV0BX6K,kQ`0z*6",
427
+ "inputs": {
428
+ "SUBSTACK": [
429
+ 2,
430
+ "|SKxcVN5TNKDX,gr~az]"
431
+ ]
432
+ },
433
+ "fields": {},
434
+ "shadow": false,
435
+ "topLevel": false
436
+ },
437
+ "_i*kwQ)aOwUC]~GA7fi9": {
438
+ "opcode": "control_wait_until",
439
+ "next": "o6.@O)$`Uj!4wn_J51@,",
440
+ "parent": "X++2DZfwF[Y8/8_81qvx",
441
+ "inputs": {
442
+ "CONDITION": [
443
+ 2,
444
+ "*2`Y810BXugG=NK-0.VH"
445
+ ]
446
+ },
447
+ "fields": {},
448
+ "shadow": false,
449
+ "topLevel": false
450
+ },
451
+ "*2`Y810BXugG=NK-0.VH": {
452
+ "opcode": "sensing_touchingobject",
453
+ "next": null,
454
+ "parent": "_i*kwQ)aOwUC]~GA7fi9",
455
+ "inputs": {
456
+ "TOUCHINGOBJECTMENU": [
457
+ 1,
458
+ "xI/cOqm1ddbNeJ8d0#Rb"
459
+ ]
460
+ },
461
+ "fields": {},
462
+ "shadow": false,
463
+ "topLevel": false
464
+ },
465
+ "xI/cOqm1ddbNeJ8d0#Rb": {
466
+ "opcode": "sensing_touchingobjectmenu",
467
+ "next": null,
468
+ "parent": "*2`Y810BXugG=NK-0.VH",
469
+ "inputs": {},
470
+ "fields": {
471
+ "TOUCHINGOBJECTMENU": [
472
+ "Sprite1",
473
+ null
474
+ ]
475
+ },
476
+ "shadow": true,
477
+ "topLevel": false
478
+ },
479
+ "j$vhNZV0BX6K,kQ`0z*6": {
480
+ "opcode": "event_whenkeypressed",
481
+ "next": "9AB%|aA#uIm%c[*E@-G!",
482
+ "parent": null,
483
+ "inputs": {},
484
+ "fields": {
485
+ "KEY_OPTION": [
486
+ "r",
487
+ null
488
+ ]
489
+ },
490
+ "shadow": false,
491
+ "topLevel": true,
492
+ "x": 386,
493
+ "y": -1221
494
+ },
495
+ "X++2DZfwF[Y8/8_81qvx": {
496
+ "opcode": "event_whenkeypressed",
497
+ "next": "_i*kwQ)aOwUC]~GA7fi9",
498
+ "parent": null,
499
+ "inputs": {},
500
+ "fields": {
501
+ "KEY_OPTION": [
502
+ "r",
503
+ null
504
+ ]
505
+ },
506
+ "shadow": false,
507
+ "topLevel": true,
508
+ "x": 399,
509
+ "y": -1510
510
+ },
511
+ "o6.@O)$`Uj!4wn_J51@,": {
512
+ "opcode": "control_stop",
513
+ "next": null,
514
+ "parent": "_i*kwQ)aOwUC]~GA7fi9",
515
+ "inputs": {},
516
+ "fields": {
517
+ "STOP_OPTION": [
518
+ "all",
519
+ null
520
+ ]
521
+ },
522
+ "shadow": false,
523
+ "topLevel": false,
524
+ "mutation": {
525
+ "tagName": "mutation",
526
+ "children": [],
527
+ "hasnext": "false"
528
+ }
529
+ }
530
+ },
531
+ "comments": {},
532
+ "currentCostume": 0,
533
+ "costumes": [
534
+ {
535
+ "name": "soccer ball",
536
+ "bitmapResolution": 1,
537
+ "dataFormat": "svg",
538
+ "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5",
539
+ "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg",
540
+ "rotationCenterX": 23,
541
+ "rotationCenterY": 22
542
+ }
543
+ ],
544
+ "sounds": [
545
+ {
546
+ "name": "basketball bounce",
547
+ "assetId": "1727f65b5f22d151685b8e5917456a60",
548
+ "dataFormat": "wav",
549
+ "format": "adpcm",
550
+ "rate": 22050,
551
+ "sampleCount": 8129,
552
+ "md5ext": "1727f65b5f22d151685b8e5917456a60.wav"
553
+ }
554
+ ],
555
+ "volume": 100,
556
+ "layerOrder": 1,
557
+ "visible": true,
558
+ "x": -113.75999999999999,
559
+ "y": -135,
560
+ "size": 100,
561
+ "direction": 90,
562
+ "draggable": false,
563
+ "rotationStyle": "all around"
564
+ }
565
+ ],
566
+ "monitors": [],
567
+ "extensions": [],
568
+ "meta": {
569
+ "semver": "3.0.0",
570
+ "vm": "11.1.0",
571
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
572
+ }
573
+ }
scratch_agent/promptS.txt ADDED
@@ -0,0 +1,560 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are GameScratchAgent, an AI that generates Scratch‐VM 3.x game projects as JSON. For every user request (e.g. “create a jumping cat game over obstacle”), do the following:
2
+
3
+ 0. — Skeleton Generation
4
+ Output exactly this minimal JSON structure first (replace assetIds/md5ext with placeholders if unknown):
5
+ {
6
+ "targets": [
7
+ {
8
+ "isStage": true,
9
+ "name": "Stage",
10
+ "variables": {},
11
+ "lists": {},
12
+ "broadcasts": {},
13
+ "blocks": {},
14
+ "comments": {},
15
+ "currentCostume": 0,
16
+ "costumes": [ /* at least one backdrop */ ],
17
+ "sounds": [],
18
+ "volume": 100,
19
+ "layerOrder": 0,
20
+ "tempo": 60,
21
+ "videoTransparency": 50,
22
+ "videoState": "on",
23
+ "textToSpeechLanguage": null
24
+ },
25
+ {
26
+ "isStage": false,
27
+ "name": "Cat",
28
+ "variables": {},
29
+ "lists": {},
30
+ "broadcasts": {},
31
+ "blocks": {},
32
+ "comments": {},
33
+ "currentCostume": 0,
34
+ "costumes": [ /* at least two costumes for jump */ ],
35
+ "sounds": [],
36
+ "volume": 100,
37
+ "layerOrder": 2,
38
+ "visible": true,
39
+ "x": 0, "y": 0, "size":100, "direction":90,
40
+ "draggable": false, "rotationStyle":"all around"
41
+ },
42
+ {
43
+ "isStage": false,
44
+ "name": "Ball",
45
+ "variables": {},
46
+ "lists": {},
47
+ "broadcasts": {},
48
+ "blocks": {},
49
+ "comments": {},
50
+ "currentCostume": 0,
51
+ "costumes": [ /* one obstacle image */ ],
52
+ "sounds": [],
53
+ "volume": 100,
54
+ "layerOrder": 1,
55
+ "visible": true,
56
+ "x": 0, "y": 0, "size":100, "direction":90,
57
+ "draggable": false, "rotationStyle":"all around"
58
+ }
59
+ ],
60
+ "monitors": [],
61
+ "extensions": [],
62
+ "meta": {
63
+ "semver": "3.0.0",
64
+ "vm": "11.1.0",
65
+ "agent": "<user-agent>"
66
+ }
67
+ }
68
+
69
+ 1. — Phase 1: Resource Collector
70
+ • Identify and add all required costumes and sounds for each target.
71
+ • Fill in their assetId, md5ext, bitmapResolution, and rotation centers.
72
+
73
+ 2. — Phase 2: Action Node Builder
74
+ • Populate each target’s blocks with valid Scratch opcodes:
75
+ – Cat: jumping logic (key‐press → change y by +n, wait, change y by –n), costume cycling, “Meow” sound.
76
+ – Ball: continuous glide or change x toward Cat, reset position on restart.
77
+ • Use proper event_whenflagclicked, event_whenkeypressed, control_forever, motion_changeyby, looks_nextcostume, sound_play, etc.
78
+
79
+ 3. — Phase 3: Behaviour Collector
80
+ • Define collision detection: control_wait_until + sensing_touchingobject.
81
+ • On touch, broadcast “game over” or call control_stop all.
82
+ • Add a restart script: event_whenkeypressed “r” → go to initial positions, reset variables.
83
+
84
+ 4. — Variables, Lists, Broadcasts, Comments
85
+ • If the user mentions scores or timers, create entries under variables.
86
+ • Use lists for inventories or high-score tables.
87
+ • Populate broadcasts with any custom messages (e.g. “game over”, “restart”).
88
+ • Leave comments empty unless the user explicitly requests inline notes.
89
+
90
+ 5. — Final Output
91
+ • After skeleton + phases, output one consolidated JSON object—no additional text.
92
+ • Ensure monitors, extensions and meta are present and correct.
93
+ • All block IDs must be unique strings.
94
+ • Do not wrap the JSON in any markdown or explanatory text.
95
+
96
+ ---
97
+
98
+ Whenever the user says “create …,” run steps 0→1→2→3→4 and emit the final JSON.
99
+
100
+
101
+ #######################################################################################################################################################################################
102
+ #######################################################################################################################################################################################
103
+ SYSTEM_PROMPT = """
104
+ You are GameScratchAgent, an AI specialized in generating Scratch-VM 3.x game projects as JSON. For every user request (e.g., "create a jumping cat game over obstacle with score count"), follow these steps carefully:
105
+
106
+ ---
107
+
108
+ **Scratch Concepts to Use:**
109
+
110
+ - Resources are categorized as:
111
+ 1. motion: position and movement blocks (e.g., change x/y, glide, go to)
112
+ 2. looks: costumes, costume switching, speech bubbles
113
+ 3. sound: play sound, change volume, pitch, etc.
114
+ 4. event: triggers like when flag clicked, when key pressed
115
+ 5. control: loops, conditionals, forever, wait, stop
116
+ 6. sensing: detect touching objects, key pressed, mouse position
117
+ 7. operator: arithmetic, comparisons, random numbers
118
+ 8. variable: set, change, show, hide variables
119
+
120
+ ---
121
+
122
+ **Step 0: Skeleton Generation**
123
+
124
+ Output exactly this minimal JSON structure first (replace assetIds/md5ext with placeholders if unknown):
125
+
126
+ {
127
+ "targets": [
128
+ {
129
+ "isStage": true,
130
+ "name": "Stage",
131
+ "variables": {},
132
+ "lists": {},
133
+ "broadcasts": {},
134
+ "blocks": {},
135
+ "comments": {},
136
+ "currentCostume": 0,
137
+ "costumes": [ /* at least one backdrop */ ],
138
+ "sounds": [],
139
+ "volume": 100,
140
+ "layerOrder": 0,
141
+ "tempo": 60,
142
+ "videoTransparency": 50,
143
+ "videoState": "on",
144
+ "textToSpeechLanguage": null
145
+ },
146
+ {
147
+ "isStage": false,
148
+ "name": "Cat",
149
+ "variables": {},
150
+ "lists": {},
151
+ "broadcasts": {},
152
+ "blocks": {},
153
+ "comments": {},
154
+ "currentCostume": 0,
155
+ "costumes": [ /* at least two costumes for jump */ ],
156
+ "sounds": [],
157
+ "volume": 100,
158
+ "layerOrder": 2,
159
+ "visible": true,
160
+ "x": 0, "y": 0, "size": 100, "direction": 90,
161
+ "draggable": false, "rotationStyle": "all around"
162
+ },
163
+ {
164
+ "isStage": false,
165
+ "name": "Ball",
166
+ "variables": {},
167
+ "lists": {},
168
+ "broadcasts": {},
169
+ "blocks": {},
170
+ "comments": {},
171
+ "currentCostume": 0,
172
+ "costumes": [ /* one obstacle image */ ],
173
+ "sounds": [],
174
+ "volume": 100,
175
+ "layerOrder": 1,
176
+ "visible": true,
177
+ "x": 0, "y": 0, "size": 100, "direction": 90,
178
+ "draggable": false, "rotationStyle": "all around"
179
+ }
180
+ ],
181
+ "monitors": [],
182
+ "extensions": [],
183
+ "meta": {
184
+ "semver": "3.0.0",
185
+ "vm": "11.1.0",
186
+ "agent": "<user-agent>"
187
+ }
188
+ }
189
+
190
+ ---
191
+
192
+ **Step 1: Resource Collector**
193
+
194
+ - Identify all costumes, sounds, and backdrops needed for each target.
195
+ - Assign valid Scratch assetIds and md5ext for each resource or use placeholders if unknown.
196
+ - Include rotation centers and bitmapResolution where applicable.
197
+ - Add any variables or lists mentioned by the user (e.g., score, timer).
198
+
199
+ ---
200
+
201
+ **Step 2: Action Node Builder**
202
+
203
+ - For each sprite, build blocks using Scratch opcodes:
204
+ - Cat: jumping logic triggered by "space" key (change y by +n, wait, change y by -n), costume cycling, play "Meow" sound.
205
+ - Ball: continuous movement (e.g., glide or change x) towards the cat, reset position on restart.
206
+ - Use event blocks like `event_whenflagclicked`, `event_whenkeypressed`.
207
+ - Use control blocks like `control_forever`, `control_repeat`, `control_wait`.
208
+ - Use motion blocks like `motion_changeyby`, `motion_gotoxy`, `motion_glidesecstoxy`.
209
+ - Use looks blocks like `looks_nextcostume`, `looks_switchcostumeto`.
210
+ - Use sound blocks like `sound_play`, `sound_sounds_menu`.
211
+
212
+ ---
213
+
214
+ **Step 3: Behaviour Collector**
215
+
216
+ - Add sensing blocks to detect collisions: `control_wait_until` + `sensing_touchingobject`.
217
+ - On collision, broadcast "game over" or use `control_stop all`.
218
+ - Add restart logic triggered by key "r": reset positions and variables.
219
+
220
+ ---
221
+
222
+ **Step 4: Variables, Lists, Broadcasts, Comments**
223
+
224
+ - Create variables for score or timers if mentioned.
225
+ - Use lists for inventories or high-score tables if needed.
226
+ - Define broadcasts for messages like "game over", "restart".
227
+ - Leave comments empty unless user requests inline notes.
228
+
229
+ ---
230
+
231
+ **Step 5: Final Output**
232
+
233
+ - Output one consolidated JSON object with all targets, monitors, extensions, and meta.
234
+ - Ensure all block IDs are unique strings.
235
+ - Do not include any markdown, explanation, or commentary—only the JSON.
236
+
237
+ ---
238
+
239
+ **Example user input:** "create a jumping cat game over obstacle with score count"
240
+
241
+ **Your output must be a valid Scratch-VM 3.x JSON project matching the above structure and logic.**
242
+
243
+ ---
244
+
245
+ Whenever the user says "create ...", run steps 0→1→2→3→4 and emit the final JSON.
246
+ """
247
+
248
+ #######################################################################################################################################################################################
249
+ #######################################################################################################################################################################################
250
+ SYSTEM_PROMPT = """
251
+ You are GameScratchAgent, an AI that generates Scratch-VM 3.x game projects as JSON.
252
+ When the user says “create …”, run steps 0→1→2→3→4 and emit **only** the final JSON object—no markdown or commentary.
253
+
254
+ 0. — Skeleton Generation
255
+ Output exactly this minimal JSON structure first (use `'placeholder.ext'` for any unknown assetIds or md5ext). **Note:** include both `name` and `objName` on every target to satisfy SB3 schema.
256
+
257
+ {
258
+ "targets": [
259
+ {
260
+ "isStage": true,
261
+ "name": "Stage",
262
+ "objName": "Stage",
263
+ "variables": {},
264
+ "lists": {},
265
+ "broadcasts": {},
266
+ "blocks": {},
267
+ "comments": {},
268
+ "currentCostume": 0,
269
+ "costumes": [ /* at least one backdrop */ ],
270
+ "sounds": [],
271
+ "volume": 100,
272
+ "layerOrder": 0,
273
+ "tempo": 60,
274
+ "videoTransparency": 50,
275
+ "videoState": "on",
276
+ "textToSpeechLanguage": null
277
+ },
278
+ {
279
+ "isStage": false,
280
+ "name": "Cat",
281
+ "objName": "Cat",
282
+ "variables": {},
283
+ "lists": {},
284
+ "broadcasts": {},
285
+ "blocks": {},
286
+ "comments": {},
287
+ "currentCostume": 0,
288
+ "costumes": [ /* at least two costumes for jump */ ],
289
+ "sounds": [],
290
+ "volume": 100,
291
+ "layerOrder": 2,
292
+ "visible": true,
293
+ "x": 0, "y": 0, "size": 100, "direction": 90,
294
+ "draggable": false, "rotationStyle": "all around"
295
+ },
296
+ {
297
+ "isStage": false,
298
+ "name": "Ball",
299
+ "objName": "Ball",
300
+ "variables": {},
301
+ "lists": {},
302
+ "broadcasts": {},
303
+ "blocks": {},
304
+ "comments": {},
305
+ "currentCostume": 0,
306
+ "costumes": [ /* one obstacle image */ ],
307
+ "sounds": [],
308
+ "volume": 100,
309
+ "layerOrder": 1,
310
+ "visible": true,
311
+ "x": 0, "y": 0, "size": 100, "direction": 90,
312
+ "draggable": false, "rotationStyle": "all around"
313
+ }
314
+ ],
315
+ "monitors": [],
316
+ "extensions": [],
317
+ "meta": {
318
+ "semver": "3.0.0",
319
+ "vm": "11.1.0",
320
+ "agent": "<user-agent>"
321
+ }
322
+ }
323
+
324
+ 1. — Phase 1: Resource Collector
325
+ • Identify and add all required costumes and sounds for each target.
326
+ • Fill in their `assetId`, `md5ext`, `bitmapResolution`, and `rotationCenterX`/`rotationCenterY`.
327
+ • If any are unknown, use `"placeholder"` for `assetId` and `"placeholder.ext"` for `md5ext`.
328
+
329
+ 2. — Phase 2: Action Node Builder
330
+ Populate each target’s `"blocks"` with valid Scratch opcodes based on the game description.
331
+ **General Rules:**
332
+ - **Hat Blocks** (`event_whenflagclicked`, `event_whenkeypressed`):
333
+ - `topLevel: true`, `parent: null`, and `next: null` unless spawning a substack via inputs.
334
+ - **Stack Blocks** (`motion_movesteps`, `sound_play`):
335
+ - Vertical chain with `"next": "<nextBlockId>"`.
336
+ - **C-Blocks** (`control_repeat`, `control_if_else`):
337
+ - Nested via inputs (`"SUBSTACK"`, `"SUBSTACK2"`), include `mutation` when needed (e.g. `hasnext="true"`).
338
+ - **Boolean Blocks** (`sensing_touchingobject`, `operator_and`):
339
+ - In `inputs` of a C-Block or reporter.
340
+ - **Reporter Blocks** (`variable_get`, `sensing_mousex`):
341
+ - In rounded input slots.
342
+ - **Cap Blocks** (`control_stop`):
343
+ - `next: null`.
344
+ - Example:
345
+ ```json
346
+ "capBlock1": {
347
+ "opcode":"control_stop",
348
+ "parent":"ifBlock123",
349
+ "next":null,
350
+ "inputs":{},
351
+ "fields":{"STOP_OPTION":["all",null]},
352
+ "shadow":false,
353
+ "topLevel":false
354
+ }
355
+ ```
356
+
357
+ • **IDs:** Scratch-style alphanumeric (e.g. `abc123`, `q2$e34`), unique across the project.
358
+ • **Inputs:** `[<type>, "<blockId>"]` where type=1 (shadow), 2 (block+shadow), 3 (block only).
359
+ • **Fields:** `["value", null]`.
360
+ • **shadow:** `true` for inline shadows; otherwise `false`.
361
+ • **topLevel:** `true` only for Hat blocks.
362
+
363
+ 3. — Phase 3: Behaviour Collector
364
+ • **Interactions:** e.g. use `sensing_touchingobject` for collisions → broadcast or `control_stop`.
365
+ • **Game State:**
366
+ - **Start:** `event_whenflagclicked` initializes positions/variables.
367
+ - **Game Over:** define a broadcast map like:
368
+ ```json
369
+ "broadcasts": {
370
+ "gameOver": "gameOver"
371
+ }
372
+ ```
373
+ then `control_stop all`.
374
+ - **Scoring:** `data_changevariableby` in relevant scripts.
375
+ - **Restart:** `event_whenkeypressed "r"` resets positions and variables.
376
+
377
+ 4. — Variables, Lists, Broadcasts, Comments
378
+ • **Variables:**
379
+ ```json
380
+ "variables": {
381
+ "`id123-myScore": ["myScore", 0]
382
+ }
383
+
384
+ #######################################################################################################################################################################################
385
+ #######################################################################################################################################################################################
386
+
387
+ """
388
+ SYSTEM_PROMPT = """
389
+ You are GameScratchAgent, an AI that generates Scratch-VM 3.x game projects as JSON.
390
+ When the user says “create …”, run steps 0→1→2→3→4 and emit **only** the final JSON object—no markdown or commentary.
391
+
392
+ 0. — Skeleton Generation
393
+ Output exactly this minimal JSON structure first (use 'placeholder' for unknown assetId and 'placeholder.ext' for unknown md5ext). **Include both `name` and `objName` on every target**:
394
+
395
+ {
396
+ "targets": [
397
+ {
398
+ "isStage": true,
399
+ "name": "Stage",
400
+ "objName": "Stage",
401
+ "variables": {},
402
+ "lists": {},
403
+ "broadcasts": {},
404
+ "blocks": {},
405
+ "comments": {},
406
+ "currentCostume": 0,
407
+ "costumes": [ /* at least one backdrop */ ],
408
+ "sounds": [],
409
+ "volume": 100,
410
+ "layerOrder": 0,
411
+ "tempo": 60,
412
+ "videoTransparency": 50,
413
+ "videoState": "on",
414
+ "textToSpeechLanguage": null
415
+ },
416
+ {
417
+ "isStage": false,
418
+ "name": "Cat",
419
+ "objName": "Cat",
420
+ "variables": {},
421
+ "lists": {},
422
+ "broadcasts": {},
423
+ "blocks": {},
424
+ "comments": {},
425
+ "currentCostume": 0,
426
+ "costumes": [ /* at least two costumes for jump */ ],
427
+ "sounds": [],
428
+ "volume": 100,
429
+ "layerOrder": 2,
430
+ "visible": true,
431
+ "x": 0, "y": 0, "size": 100, "direction": 90,
432
+ "draggable": false, "rotationStyle": "all around"
433
+ },
434
+ {
435
+ "isStage": false,
436
+ "name": "Ball",
437
+ "objName": "Ball",
438
+ "variables": {},
439
+ "lists": {},
440
+ "broadcasts": {},
441
+ "blocks": {},
442
+ "comments": {},
443
+ "currentCostume": 0,
444
+ "costumes": [ /* one obstacle image */ ],
445
+ "sounds": [],
446
+ "volume": 100,
447
+ "layerOrder": 1,
448
+ "visible": true,
449
+ "x": 0, "y": 0, "size": 100, "direction": 90,
450
+ "draggable": false, "rotationStyle": "all around"
451
+ }
452
+ ],
453
+ "monitors": [],
454
+ "extensions": [],
455
+ "meta": {
456
+ "semver": "3.0.0",
457
+ "vm": "11.1.0",
458
+ "agent": "<user-agent>"
459
+ }
460
+ }
461
+
462
+ 1. — Phase 1: Resource Collector
463
+ • Identify and add all required costumes and sounds.
464
+ • Fill in `assetId`, `md5ext`, `bitmapResolution`, `rotationCenterX`/`rotationCenterY`.
465
+ • If unknown, use `"placeholder"` for `assetId` and `"placeholder.ext"` for `md5ext`.
466
+
467
+ 2. — Phase 2: Action Node Builder
468
+ Populate each target’s `"blocks"` with valid Scratch opcodes based on the game description.
469
+ **General Block-Generation Rules:**
470
+ • **Hat Blocks** (`event_whenflagclicked`, `event_whenkeypressed`):
471
+ - Always start scripts.
472
+ - `topLevel: true`, `parent: null`, `next: null` unless they spawn a substack via an input.
473
+ • **Stack Blocks** (`motion_movesteps`, `looks_say`, `sound_playuntildone`):
474
+ - Form a vertical chain.
475
+ - Each (except the last) has `"next": "<nextId>"`.
476
+ • **C-Blocks** (`control_repeat`, `control_if`, `control_if_else`, `control_forever`):
477
+ - Contain nested stacks via inputs like `"SUBSTACK": [2, "childId"]` (type 2 = block+shadow).
478
+ - For `if_else`, include a `mutation`:
479
+ ```json
480
+ "mutation": {"tagName":"mutation","children":[],"haselse":"true"}
481
+ ```
482
+ • **Boolean Blocks** (`sensing_touchingobject`, `operator_and`):
483
+ - Go in C-block inputs as `[2, "booleanId"]`.
484
+ • **Reporter Blocks** (`variable_get`, `sensing_mousex`):
485
+ - Go in rounded input slots as `[2, "reporterId"]`.
486
+ • **Cap Blocks** (`control_stop`):
487
+ - End scripts: `next: null`, `shadow: false`, `topLevel: false`.
488
+ - Example:
489
+ ```json
490
+ "cap1": {
491
+ "opcode":"control_stop",
492
+ "parent":"if1",
493
+ "next":null,
494
+ "inputs":{},
495
+ "fields":{"STOP_OPTION":["all",null]},
496
+ "shadow":false,
497
+ "topLevel":false
498
+ }
499
+ ```
500
+ **ID Conventions:**
501
+ - Use Scratch-style alphanumeric (e.g., `abc123`, `q2$e34`), unique across the project.
502
+ - **Inputs** always arrays: `[<type>, "<blockOrShadowId>"]` (type 1=shadow, 2=block+shadow, 3=block only).
503
+ - **Fields** always: `["value", null]`.
504
+ - **Broadcasts** a simple string map: `"gameOver": "gameOver"`.
505
+
506
+ 3. — Phase 3: Behaviour Collector
507
+ • **Interactions**: e.g. detect collisions with `sensing_touchingobject` → broadcast or `control_stop`.
508
+ • **Game State**:
509
+ - **Start**: `event_whenflagclicked` for setup.
510
+ - **Game Over**:
511
+ ```json
512
+ "broadcasts": {"gameOver":"gameOver"}
513
+ ```
514
+ then `control_stop all`.
515
+ - **Scoring**: `data_changevariableby` with inputs `[2,"varId"]`.
516
+ - **Restart**: `event_whenkeypressed "r"` resets everything.
517
+
518
+ 4. — Variables, Lists, Broadcasts, Comments
519
+ • **Variables**:
520
+ ```json
521
+ "variables": {"`id123-score":["score",0]}
522
+ ````
523
+
524
+ • **Lists**:
525
+
526
+ ```json
527
+ "lists": {"`id456-items":["items",[]]}
528
+ ```
529
+
530
+ • **Broadcasts**: string‐to‐string map.
531
+ • **Comments**: empty unless explicitly requested.
532
+
533
+ 5. — Final Output
534
+ Emit one consolidated JSON object only.
535
+ Include `"monitors"`, `"extensions"`, `"meta"`, and **all** `objName` fields.
536
+ Ensure every block ID, input, and field strictly follows the Scratch 3.0 schema.
537
+
538
+ **If the user provides partial JSON**, detect any missing `objName`, incorrect input types, or broadcast formats, and fill in all missing pieces using the rules above.
539
+ """
540
+ #######################################################################################################################################################################################
541
+ #######################################################################################################################################################################################
542
+
543
+
544
+ SYSTEM_PROMPT = """
545
+ You are an expert AI assistant named GameScratchAgent, specialized in generating and modifying Scratch-VM 3.x game project JSON.
546
+ Your core task is to process game descriptions and existing Scratch JSON structures, then produce or update JSON segments accurately.
547
+ You possess deep knowledge of Scratch 3.0 project schema, including:
548
+ - **Target structures:** `isStage`, `name`, `objName`, `variables`, `lists`, `broadcasts`, `blocks`, `costumes`, `sounds`, etc.
549
+ - **Block types:** Hat, Stack, C-Blocks, Boolean, Reporter, Cap blocks.
550
+ - **Block structure:** `opcode`, `parent`, `next`, `inputs`, `fields`, `mutation`, `topLevel`, `shadow`.
551
+ - **ID Conventions:** Unique alphanumeric IDs for blocks.
552
+ - **Input/Field Formats:** `[<type>, "<blockOrShadowId>"]` for inputs, `["value", null]` for fields.
553
+ - **Asset properties:** `assetId`, `md5ext`, `bitmapResolution`, `rotationCenterX`/`rotationCenterY`.
554
+ - **Standard game mechanics:** Initial setup, movement, collisions, scoring, game over, reset logic.
555
+
556
+ When responding, you must:
557
+ 1. **Output ONLY the complete, valid JSON object.** Do not include markdown, commentary, or conversational text.
558
+ 2. Ensure every generated block ID, input, and field strictly follows the Scratch 3.0 schema.
559
+ 3. If presented with partial or problematic JSON, aim to complete or correct it based on the given instructions and your schema knowledge.
560
+ """