Spaces:
No application file
No application file
Upload 76 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +1 -0
- scratch_agent/.env +5 -0
- scratch_agent/README.md +89 -0
- scratch_agent/__pycache__/agent.cpython-312.pyc +0 -0
- scratch_agent/__pycache__/retry_groq.cpython-312.pyc +0 -0
- scratch_agent/action_node.py +452 -0
- scratch_agent/agent.py +1647 -0
- scratch_agent/agent2.py +259 -0
- scratch_agent/app.py +997 -0
- scratch_agent/blocks_content.json +1367 -0
- scratch_agent/blocks_defination.py +274 -0
- scratch_agent/chats/user_chat_no_113a02f93587a6622aa86c130e31f025.json +18 -0
- scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/076ea82f-eaa5-4908-96e9-273c9e67c4d0/project.json +104 -0
- scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/6897def8-8527-4e08-a7f4-e2cac52c52eb/project.json +104 -0
- scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/6c5cf6c7-cb4b-4b44-a79f-37bf8396317e/project.json +104 -0
- scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/73a33e04-d24d-4108-b30b-228c6bfc37fa/project.json +104 -0
- scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/80529764-5067-4068-abb8-c26f066a28ab/project.json +104 -0
- scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/80b7ffa8-11cb-4943-85b2-130b7b153914/project.json +104 -0
- scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/870080f4-050a-4c1d-86ee-5c32c1b18666/project.json +104 -0
- scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg +29 -0
- scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/bcf454acf82e4504149f7ffe07081dbc.svg +42 -0
- scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/e7c147730f19d284bcd7b3f00af19bb6.svg +19 -0
- scratch_agent/generated_projects/ab68c9da-1e1a-4708-9f21-6b7a37da2711/project.json +104 -0
- scratch_agent/helper_function.py +0 -0
- scratch_agent/last_updated.txt +2 -0
- scratch_agent/logs.txt +51 -0
- scratch_agent/multi_Agent_scratch.py +298 -0
- scratch_agent/project.json +573 -0
- 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"
|
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 |
+
"""
|