WebashalarForML commited on
Commit
330b6e4
·
verified ·
1 Parent(s): 9d17af7

Upload 178 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +76 -0
  2. .env.example +44 -0
  3. .gitattributes +3 -0
  4. AI Code Block Generation_.docx +3 -0
  5. DEPLOYMENT_README.md +403 -0
  6. Dockerfile +51 -0
  7. Dockerfile.dev +31 -0
  8. FRONTEND_README.md +160 -0
  9. Makefile +171 -0
  10. README.md +91 -12
  11. REDIS_SETUP.md +237 -0
  12. Refine Scratch Block Builders_.docx +3 -0
  13. app.py +395 -0
  14. blocks/blocks.json +2221 -0
  15. blocks/boolean_blocks.json +281 -0
  16. blocks/c_blocks.json +105 -0
  17. blocks/cap_blocks.json +53 -0
  18. blocks/classwise_blocks/control_block.json +168 -0
  19. blocks/classwise_blocks/data_block.json +328 -0
  20. blocks/classwise_blocks/event_block.json +136 -0
  21. blocks/classwise_blocks/look_block.json +365 -0
  22. blocks/classwise_blocks/motion_block.json +370 -0
  23. blocks/classwise_blocks/operator_block.json +409 -0
  24. blocks/classwise_blocks/sensing_block.json +292 -0
  25. blocks/classwise_blocks/sound_block.json +167 -0
  26. blocks/hat_blocks.json +217 -0
  27. blocks/reporter_blocks.json +709 -0
  28. blocks/stack_blocks.json +1321 -0
  29. chat_agent/__init__.py +1 -0
  30. chat_agent/__pycache__/__init__.cpython-312.pyc +0 -0
  31. chat_agent/api/README.md +898 -0
  32. chat_agent/api/__init__.py +12 -0
  33. chat_agent/api/__pycache__/__init__.cpython-312.pyc +0 -0
  34. chat_agent/api/__pycache__/chat_routes.cpython-312.pyc +0 -0
  35. chat_agent/api/__pycache__/health.cpython-312.pyc +0 -0
  36. chat_agent/api/__pycache__/middleware.cpython-312.pyc +0 -0
  37. chat_agent/api/__pycache__/performance_routes.cpython-312.pyc +0 -0
  38. chat_agent/api/chat_routes.py +642 -0
  39. chat_agent/api/health.py +243 -0
  40. chat_agent/api/middleware.py +402 -0
  41. chat_agent/api/performance_routes.py +441 -0
  42. chat_agent/models/__init__.py +15 -0
  43. chat_agent/models/__pycache__/__init__.cpython-312.pyc +0 -0
  44. chat_agent/models/__pycache__/base.cpython-312.pyc +0 -0
  45. chat_agent/models/__pycache__/chat_session.cpython-312.pyc +0 -0
  46. chat_agent/models/__pycache__/language_context.cpython-312.pyc +0 -0
  47. chat_agent/models/__pycache__/message.cpython-312.pyc +0 -0
  48. chat_agent/models/base.py +61 -0
  49. chat_agent/models/chat_session.py +118 -0
  50. chat_agent/models/language_context.py +304 -0
.dockerignore ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git
2
+ .git
3
+ .gitignore
4
+
5
+ # Python
6
+ __pycache__
7
+ *.pyc
8
+ *.pyo
9
+ *.pyd
10
+ .Python
11
+ env
12
+ pip-log.txt
13
+ pip-delete-this-directory.txt
14
+ .tox
15
+ .coverage
16
+ .coverage.*
17
+ .cache
18
+ nosetests.xml
19
+ coverage.xml
20
+ *.cover
21
+ *.log
22
+ .git
23
+ .mypy_cache
24
+ .pytest_cache
25
+ .hypothesis
26
+
27
+ # Virtual environments
28
+ venv/
29
+ env/
30
+ ENV/
31
+
32
+ # IDE
33
+ .vscode/
34
+ .idea/
35
+ *.swp
36
+ *.swo
37
+ *~
38
+
39
+ # OS
40
+ .DS_Store
41
+ .DS_Store?
42
+ ._*
43
+ .Spotlight-V100
44
+ .Trashes
45
+ ehthumbs.db
46
+ Thumbs.db
47
+
48
+ # Project specific
49
+ logs/
50
+ *.log
51
+ instance/
52
+ flask_session/
53
+ backups/
54
+ ssl/
55
+
56
+ # Documentation
57
+ docs/
58
+ *.md
59
+ !README.md
60
+
61
+ # Development files
62
+ .env.example
63
+ config/development.env
64
+ config/testing.env
65
+ docker-compose.dev.yml
66
+ Dockerfile.dev
67
+
68
+ # Test files
69
+ tests/
70
+ pytest.ini
71
+ .coverage
72
+
73
+ # Temporary files
74
+ tmp/
75
+ temp/
76
+ *.tmp
.env.example ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Groq API Configuration
2
+ GROQ_API_KEY=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
3
+ GROQ_MODEL=meta-llama/llama-4-scout-17b-16e-instruct
4
+
5
+ # Database Configuration
6
+ DATABASE_URL=postgresql://username:password@localhost:5432/chat_agent_db
7
+ DB_HOST=localhost
8
+ DB_PORT=5432
9
+ DB_NAME=chat_agent_db
10
+ DB_USER=admin
11
+ DB_PASSWORD=admin
12
+
13
+ # Redis Configuration
14
+ REDIS_URL=redis://localhost:6379/0
15
+ REDIS_HOST=localhost
16
+ REDIS_PORT=6379
17
+ REDIS_DB=0
18
+ REDIS_PASSWORD=Key@123
19
+
20
+ # Flask Configuration
21
+ FLASK_ENV=development
22
+ FLASK_DEBUG=True
23
+ SECRET_KEY=your_secret_key_here
24
+
25
+ # Session Configuration
26
+ SESSION_TYPE=redis
27
+ SESSION_PERMANENT=False
28
+ SESSION_USE_SIGNER=True
29
+ SESSION_KEY_PREFIX=chat_agent:
30
+
31
+ # Chat Agent Configuration
32
+ DEFAULT_LANGUAGE=python
33
+ MAX_CHAT_HISTORY=20
34
+ CONTEXT_WINDOW_SIZE=10
35
+ SESSION_TIMEOUT=3600
36
+
37
+ # API Configuration
38
+ MAX_TOKENS=2048
39
+ TEMPERATURE=0.7
40
+ STREAM_RESPONSES=True
41
+
42
+ # Logging Configuration
43
+ LOG_LEVEL=INFO
44
+ LOG_FILE=chat_agent.log
.gitattributes CHANGED
@@ -33,3 +33,6 @@ 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
+ AI[[:space:]]Code[[:space:]]Block[[:space:]]Generation_.docx filter=lfs diff=lfs merge=lfs -text
37
+ instance/chat_agent.db filter=lfs diff=lfs merge=lfs -text
38
+ Refine[[:space:]]Scratch[[:space:]]Block[[:space:]]Builders_.docx filter=lfs diff=lfs merge=lfs -text
AI Code Block Generation_.docx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc9d14e1a3ef23a5581cf3a000d6d0275f5a85778bf10b08f90891da3997b467
3
+ size 6226103
DEPLOYMENT_README.md ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-Language Chat Agent - Deployment Setup
2
+
3
+ This document provides a comprehensive overview of the deployment configuration and setup for the Multi-Language Chat Agent application.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ### Docker Deployment (Recommended)
8
+
9
+ ```bash
10
+ # Clone repository
11
+ git clone <repository-url>
12
+ cd chat-agent
13
+
14
+ # Set up environment
15
+ cp config/production.env .env
16
+ # Edit .env with your actual values
17
+
18
+ # Start with Docker Compose
19
+ docker-compose up -d
20
+
21
+ # Check health
22
+ curl http://localhost:5000/health/
23
+ ```
24
+
25
+ ### Manual Deployment
26
+
27
+ ```bash
28
+ # Set up environment
29
+ python scripts/setup_environment.py --environment production
30
+
31
+ # Start application
32
+ make run-prod
33
+ ```
34
+
35
+ ## 📁 Configuration Files Overview
36
+
37
+ ### Docker Configuration
38
+
39
+ | File | Purpose | Environment |
40
+ |------|---------|-------------|
41
+ | `Dockerfile` | Production container build | Production |
42
+ | `Dockerfile.dev` | Development container with hot reload | Development |
43
+ | `docker-compose.yml` | Production services orchestration | Production |
44
+ | `docker-compose.dev.yml` | Development services with volumes | Development |
45
+ | `nginx.conf` | Reverse proxy configuration | Production |
46
+ | `.dockerignore` | Docker build optimization | All |
47
+
48
+ ### Environment Configuration
49
+
50
+ | File | Purpose | Usage |
51
+ |------|---------|-------|
52
+ | `config/development.env` | Development environment variables | Development |
53
+ | `config/production.env` | Production environment template | Production |
54
+ | `config/testing.env` | Testing environment configuration | Testing |
55
+ | `.env.example` | Environment template with examples | Reference |
56
+
57
+ ### Database Configuration
58
+
59
+ | File | Purpose | Usage |
60
+ |------|---------|-------|
61
+ | `migrations/001_initial_schema.sql` | Database schema definition | All environments |
62
+ | `migrations/migrate.py` | Migration management script | All environments |
63
+ | `scripts/init_db.py` | Database initialization utility | Setup |
64
+
65
+ ### Deployment Scripts
66
+
67
+ | File | Purpose | Usage |
68
+ |------|---------|-------|
69
+ | `scripts/setup_environment.py` | Automated environment setup | Setup |
70
+ | `scripts/init_db.py` | Database management | Setup/Maintenance |
71
+ | `Makefile` | Common development tasks | Development |
72
+
73
+ ## 🏗️ Architecture Overview
74
+
75
+ ```
76
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
77
+ │ Nginx │ │ Chat Agent │ │ PostgreSQL │
78
+ │ (Reverse Proxy)│────│ Application │────│ Database │
79
+ │ │ │ │ │ │
80
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
81
+
82
+
83
+ ┌─────────────────┐
84
+ │ Redis │
85
+ │ (Cache/ │
86
+ │ Sessions) │
87
+ └─────────────────┘
88
+ ```
89
+
90
+ ## 🔧 Configuration Management
91
+
92
+ ### Environment-Specific Settings
93
+
94
+ The application supports three environments with different configurations:
95
+
96
+ #### Development
97
+ - Debug mode enabled
98
+ - Detailed logging
99
+ - Local database
100
+ - Hot reload
101
+ - Development tools
102
+
103
+ #### Production
104
+ - Optimized for performance
105
+ - Security hardened
106
+ - Production database
107
+ - Monitoring enabled
108
+ - Error handling
109
+
110
+ #### Testing
111
+ - In-memory database
112
+ - Isolated test data
113
+ - Minimal logging
114
+ - Fast execution
115
+
116
+ ### Configuration Hierarchy
117
+
118
+ 1. **Environment Variables** (highest priority)
119
+ 2. **`.env` file**
120
+ 3. **Config class defaults**
121
+ 4. **Application defaults** (lowest priority)
122
+
123
+ ## 🐳 Docker Deployment
124
+
125
+ ### Production Deployment
126
+
127
+ ```bash
128
+ # Build and start all services
129
+ docker-compose up -d
130
+
131
+ # With nginx reverse proxy
132
+ docker-compose --profile production up -d
133
+
134
+ # Scale application instances
135
+ docker-compose up -d --scale chat-agent=3
136
+ ```
137
+
138
+ ### Development Deployment
139
+
140
+ ```bash
141
+ # Start development environment
142
+ docker-compose -f docker-compose.dev.yml up -d
143
+
144
+ # View logs
145
+ docker-compose -f docker-compose.dev.yml logs -f
146
+ ```
147
+
148
+ ### Docker Services
149
+
150
+ | Service | Purpose | Ports | Dependencies |
151
+ |---------|---------|-------|--------------|
152
+ | `chat-agent` | Main application | 5000 | postgres, redis |
153
+ | `postgres` | Database | 5432 | - |
154
+ | `redis` | Cache/Sessions | 6379 | - |
155
+ | `nginx` | Reverse proxy | 80, 443 | chat-agent |
156
+
157
+ ## 🗄️ Database Management
158
+
159
+ ### Initialization
160
+
161
+ ```bash
162
+ # Initialize database with schema
163
+ python scripts/init_db.py init --config production
164
+
165
+ # Check migration status
166
+ python scripts/init_db.py status --config production
167
+
168
+ # Seed with sample data
169
+ python scripts/init_db.py seed --config production
170
+ ```
171
+
172
+ ### Migrations
173
+
174
+ ```bash
175
+ # Run migrations manually
176
+ python migrations/migrate.py migrate --config production
177
+
178
+ # Check migration status
179
+ python migrations/migrate.py status --config production
180
+ ```
181
+
182
+ ### Backup and Restore
183
+
184
+ ```bash
185
+ # Create backup
186
+ make backup-db
187
+
188
+ # Restore from backup
189
+ make restore-db
190
+ ```
191
+
192
+ ## 🔍 Health Monitoring
193
+
194
+ ### Health Check Endpoints
195
+
196
+ | Endpoint | Purpose | Use Case |
197
+ |----------|---------|----------|
198
+ | `/health/` | Basic health check | Load balancer |
199
+ | `/health/detailed` | Component status | Monitoring |
200
+ | `/health/ready` | Readiness probe | Kubernetes |
201
+ | `/health/live` | Liveness probe | Kubernetes |
202
+ | `/health/metrics` | Application metrics | Monitoring |
203
+
204
+ ### Example Health Check
205
+
206
+ ```bash
207
+ # Basic health check
208
+ curl http://localhost:5000/health/
209
+
210
+ # Detailed status
211
+ curl http://localhost:5000/health/detailed | jq
212
+ ```
213
+
214
+ ### Monitoring Integration
215
+
216
+ The health endpoints are designed to work with:
217
+ - **Load Balancers**: Nginx, HAProxy, AWS ALB
218
+ - **Container Orchestration**: Kubernetes, Docker Swarm
219
+ - **Monitoring Systems**: Prometheus, Grafana, DataDog
220
+
221
+ ## 🔒 Security Configuration
222
+
223
+ ### Production Security Checklist
224
+
225
+ - [ ] **HTTPS**: SSL/TLS certificates configured
226
+ - [ ] **API Keys**: Stored securely, not in code
227
+ - [ ] **Database**: Connection encryption enabled
228
+ - [ ] **Firewall**: Proper rules configured
229
+ - [ ] **Headers**: Security headers set in Nginx
230
+ - [ ] **Rate Limiting**: Configured for API endpoints
231
+ - [ ] **Input Validation**: All inputs sanitized
232
+ - [ ] **Logging**: Security events logged
233
+
234
+ ### Environment Variables Security
235
+
236
+ ```bash
237
+ # Use secure secret generation
238
+ python -c "import secrets; print(secrets.token_urlsafe(32))"
239
+
240
+ # Store in secure location
241
+ export SECRET_KEY="your_secure_key"
242
+ export GROQ_API_KEY="your_api_key"
243
+ ```
244
+
245
+ ## 📊 Performance Optimization
246
+
247
+ ### Application Performance
248
+
249
+ - **Connection Pooling**: Database and Redis connections
250
+ - **Caching**: Redis for sessions and frequent data
251
+ - **Async Processing**: WebSocket for real-time communication
252
+ - **Load Balancing**: Multiple application instances
253
+
254
+ ### Database Performance
255
+
256
+ ```sql
257
+ -- Recommended indexes
258
+ CREATE INDEX CONCURRENTLY idx_messages_session_timestamp
259
+ ON messages(session_id, timestamp);
260
+
261
+ CREATE INDEX CONCURRENTLY idx_sessions_user_active
262
+ ON chat_sessions(user_id, is_active);
263
+ ```
264
+
265
+ ### Redis Configuration
266
+
267
+ ```bash
268
+ # Production Redis settings
269
+ maxmemory 512mb
270
+ maxmemory-policy allkeys-lru
271
+ save 900 1
272
+ save 300 10
273
+ ```
274
+
275
+ ## 🚨 Troubleshooting
276
+
277
+ ### Common Issues
278
+
279
+ #### 1. Container Won't Start
280
+
281
+ ```bash
282
+ # Check logs
283
+ docker-compose logs chat-agent
284
+
285
+ # Check health
286
+ docker-compose exec chat-agent curl localhost:5000/health/
287
+ ```
288
+
289
+ #### 2. Database Connection Failed
290
+
291
+ ```bash
292
+ # Test database connectivity
293
+ docker-compose exec postgres psql -U chatuser -d chat_agent_db -c "SELECT 1;"
294
+
295
+ # Check environment variables
296
+ docker-compose exec chat-agent env | grep DATABASE
297
+ ```
298
+
299
+ #### 3. Redis Connection Issues
300
+
301
+ ```bash
302
+ # Test Redis connectivity
303
+ docker-compose exec redis redis-cli ping
304
+
305
+ # Check Redis logs
306
+ docker-compose logs redis
307
+ ```
308
+
309
+ ### Log Analysis
310
+
311
+ ```bash
312
+ # Application logs
313
+ docker-compose logs -f chat-agent
314
+
315
+ # Database logs
316
+ docker-compose logs -f postgres
317
+
318
+ # All services
319
+ docker-compose logs -f
320
+ ```
321
+
322
+ ## 📈 Scaling
323
+
324
+ ### Horizontal Scaling
325
+
326
+ ```bash
327
+ # Scale application instances
328
+ docker-compose up -d --scale chat-agent=3
329
+
330
+ # Update nginx upstream configuration
331
+ # Add multiple server entries in nginx.conf
332
+ ```
333
+
334
+ ### Load Balancing
335
+
336
+ ```nginx
337
+ upstream chat_agent {
338
+ server chat-agent_1:5000;
339
+ server chat-agent_2:5000;
340
+ server chat-agent_3:5000;
341
+ }
342
+ ```
343
+
344
+ ### Database Scaling
345
+
346
+ - **Read Replicas**: For read-heavy workloads
347
+ - **Connection Pooling**: PgBouncer for connection management
348
+ - **Partitioning**: For large message tables
349
+
350
+ ## 🔄 CI/CD Integration
351
+
352
+ ### GitHub Actions Example
353
+
354
+ ```yaml
355
+ name: Deploy to Production
356
+
357
+ on:
358
+ push:
359
+ branches: [main]
360
+
361
+ jobs:
362
+ deploy:
363
+ runs-on: ubuntu-latest
364
+ steps:
365
+ - uses: actions/checkout@v2
366
+
367
+ - name: Build and Deploy
368
+ run: |
369
+ docker-compose build
370
+ docker-compose up -d
371
+
372
+ - name: Health Check
373
+ run: |
374
+ sleep 30
375
+ curl -f http://localhost:5000/health/ || exit 1
376
+ ```
377
+
378
+ ## 📚 Additional Resources
379
+
380
+ - **[Deployment Guide](docs/DEPLOYMENT.md)**: Detailed deployment instructions
381
+ - **[Environment Setup](docs/ENVIRONMENT_SETUP.md)**: Manual setup guide
382
+ - **[API Documentation](chat_agent/api/README.md)**: API reference
383
+ - **[Feature Specifications](.kiro/specs/multi-language-chat-agent/)**: Requirements and design
384
+
385
+ ## 🆘 Support
386
+
387
+ For deployment issues:
388
+
389
+ 1. Check the [troubleshooting section](#-troubleshooting)
390
+ 2. Review application logs
391
+ 3. Verify configuration settings
392
+ 4. Test health endpoints
393
+ 5. Check system resources
394
+
395
+ ## 📝 Changelog
396
+
397
+ ### Version 1.0.0
398
+ - Initial deployment configuration
399
+ - Docker containerization
400
+ - Health monitoring
401
+ - Environment management
402
+ - Database migrations
403
+ - Security hardening
Dockerfile ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-stage build for production optimization
2
+ FROM python:3.11-slim as builder
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies for building Python packages
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ g++ \
11
+ libpq-dev \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copy requirements and install Python dependencies
15
+ COPY requirements.txt .
16
+ RUN pip install --no-cache-dir --user -r requirements.txt
17
+
18
+ # Production stage
19
+ FROM python:3.11-slim
20
+
21
+ # Set working directory
22
+ WORKDIR /app
23
+
24
+ # Install runtime dependencies
25
+ RUN apt-get update && apt-get install -y \
26
+ libpq5 \
27
+ && rm -rf /var/lib/apt/lists/*
28
+
29
+ # Copy Python packages from builder stage
30
+ COPY --from=builder /root/.local /root/.local
31
+
32
+ # Make sure scripts in .local are usable
33
+ ENV PATH=/root/.local/bin:$PATH
34
+
35
+ # Copy application code
36
+ COPY . .
37
+
38
+ # Create non-root user for security
39
+ RUN groupadd -r chatuser && useradd -r -g chatuser chatuser
40
+ RUN chown -R chatuser:chatuser /app
41
+ USER chatuser
42
+
43
+ # Expose port
44
+ EXPOSE 5000
45
+
46
+ # Health check
47
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
48
+ CMD python -c "import requests; requests.get('http://localhost:5000/health')" || exit 1
49
+
50
+ # Default command
51
+ CMD ["python", "app.py"]
Dockerfile.dev ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Development Dockerfile with hot reload
2
+ FROM python:3.11-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ g++ \
11
+ libpq-dev \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copy requirements and install Python dependencies
15
+ COPY requirements.txt .
16
+ RUN pip install --no-cache-dir -r requirements.txt
17
+
18
+ # Install development dependencies
19
+ RUN pip install --no-cache-dir \
20
+ watchdog \
21
+ flask-debugtoolbar \
22
+ ipdb
23
+
24
+ # Copy application code
25
+ COPY . .
26
+
27
+ # Expose port
28
+ EXPOSE 5000
29
+
30
+ # Development command with hot reload
31
+ CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=5000", "--reload"]
FRONTEND_README.md ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chat Interface Frontend
2
+
3
+ This document describes the frontend chat interface implementation for the multi-language chat agent.
4
+
5
+ ## Features Implemented
6
+
7
+ ### ✅ Responsive Chat UI
8
+ - Clean, modern interface with gradient header
9
+ - Responsive design that works on desktop and mobile
10
+ - Dark mode support via CSS media queries
11
+ - Smooth animations and transitions
12
+
13
+ ### ✅ WebSocket Client
14
+ - Real-time communication with backend via Socket.IO
15
+ - Automatic reconnection handling
16
+ - Connection status indicators
17
+ - Error handling and user feedback
18
+
19
+ ### ✅ Language Selection
20
+ - Dropdown with 8 supported programming languages:
21
+ - Python (default)
22
+ - JavaScript
23
+ - Java
24
+ - C++
25
+ - C#
26
+ - Go
27
+ - Rust
28
+ - TypeScript
29
+ - Real-time language switching
30
+ - Visual feedback when language changes
31
+
32
+ ### ✅ Syntax Highlighting
33
+ - Prism.js integration for code block highlighting
34
+ - Automatic language detection
35
+ - Support for inline code and code blocks
36
+ - Click-to-copy functionality for code blocks
37
+
38
+ ### ✅ Typing Indicators
39
+ - Visual typing animation while assistant responds
40
+ - Streaming response support with real-time updates
41
+ - Processing status indicators
42
+
43
+ ### ✅ Connection Status
44
+ - Visual connection status indicator (connected/disconnected/connecting)
45
+ - Automatic reconnection attempts
46
+ - Connection health monitoring
47
+
48
+ ### ✅ Error Message Display
49
+ - User-friendly error messages
50
+ - Auto-dismissing error notifications
51
+ - Toast notifications for actions like copying code
52
+ - Character count with warnings
53
+
54
+ ## Files Created
55
+
56
+ ### HTML Template
57
+ - `templates/chat.html` - Main chat interface template
58
+
59
+ ### CSS Styles
60
+ - `static/css/chat.css` - Complete responsive styling with:
61
+ - Modern gradient design
62
+ - Responsive breakpoints
63
+ - Dark mode support
64
+ - Smooth animations
65
+ - Code block styling
66
+
67
+ ### JavaScript Client
68
+ - `static/js/chat.js` - WebSocket client with:
69
+ - ChatClient class for managing connections
70
+ - Real-time message handling
71
+ - Language switching
72
+ - Syntax highlighting integration
73
+ - Error handling and notifications
74
+
75
+ ## How to Use
76
+
77
+ 1. **Start the Flask Application**
78
+ ```bash
79
+ python app.py
80
+ ```
81
+
82
+ 2. **Open Browser**
83
+ - Navigate to `http://localhost:5000`
84
+ - The chat interface will load automatically
85
+
86
+ 3. **Chat Features**
87
+ - Type messages in the input field
88
+ - Press Enter to send (Shift+Enter for new lines)
89
+ - Select different programming languages from dropdown
90
+ - Click code blocks to copy them
91
+ - Monitor connection status in header
92
+
93
+ ## Demo Mode
94
+
95
+ The current implementation includes a demo WebSocket handler that:
96
+ - Simulates streaming responses
97
+ - Handles language switching
98
+ - Provides helpful demo messages
99
+ - Shows all UI features working
100
+
101
+ ## Integration with Backend
102
+
103
+ The frontend is designed to work with the full chat agent backend. Key integration points:
104
+
105
+ ### WebSocket Events
106
+ - `connect` - Establish connection with auth
107
+ - `message` - Send chat messages
108
+ - `language_switch` - Change programming language
109
+ - `disconnect` - Clean disconnection
110
+
111
+ ### Expected Backend Events
112
+ - `connection_status` - Connection confirmation
113
+ - `response_start` - Begin streaming response
114
+ - `response_chunk` - Stream response content
115
+ - `response_complete` - End streaming response
116
+ - `language_switched` - Language change confirmation
117
+ - `error` - Error notifications
118
+
119
+ ## Browser Compatibility
120
+
121
+ - Modern browsers with WebSocket support
122
+ - Chrome, Firefox, Safari, Edge
123
+ - Mobile browsers (iOS Safari, Chrome Mobile)
124
+ - Fallback to polling for older browsers
125
+
126
+ ## Performance Features
127
+
128
+ - Efficient DOM updates
129
+ - Smooth scrolling to new messages
130
+ - Optimized syntax highlighting
131
+ - Minimal memory footprint
132
+ - Automatic cleanup on disconnect
133
+
134
+ ## Security Considerations
135
+
136
+ - Input sanitization for XSS prevention
137
+ - Message length limits (2000 characters)
138
+ - Rate limiting ready (handled by backend)
139
+ - Secure WebSocket connections (WSS in production)
140
+
141
+ ## Next Steps
142
+
143
+ The frontend is ready for integration with:
144
+ 1. Full chat agent backend (Task 11)
145
+ 2. User authentication system
146
+ 3. Session persistence
147
+ 4. Chat history loading
148
+ 5. File upload capabilities
149
+ 6. Advanced code execution features
150
+
151
+ ## Testing
152
+
153
+ The interface has been tested with:
154
+ - WebSocket connection establishment
155
+ - Message sending and receiving
156
+ - Language switching
157
+ - Error handling
158
+ - Responsive design
159
+ - Code copying functionality
160
+ - Real-time streaming responses
Makefile ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Makefile for Chat Agent Application
2
+
3
+ .PHONY: help install setup test clean run docker-build docker-run docker-dev migrate seed
4
+
5
+ # Default target
6
+ help:
7
+ @echo "Available commands:"
8
+ @echo " install - Install dependencies"
9
+ @echo " setup - Set up development environment"
10
+ @echo " setup-prod - Set up production environment"
11
+ @echo " test - Run tests"
12
+ @echo " test-cov - Run tests with coverage"
13
+ @echo " clean - Clean up temporary files"
14
+ @echo " run - Run development server"
15
+ @echo " run-prod - Run production server"
16
+ @echo " migrate - Run database migrations"
17
+ @echo " seed - Seed database with sample data"
18
+ @echo " reset-db - Reset database (development only)"
19
+ @echo " docker-build - Build Docker image"
20
+ @echo " docker-run - Run with Docker Compose"
21
+ @echo " docker-dev - Run development environment with Docker"
22
+ @echo " docker-stop - Stop Docker containers"
23
+ @echo " lint - Run code linting"
24
+ @echo " format - Format code"
25
+
26
+ # Installation and setup
27
+ install:
28
+ pip install --upgrade pip
29
+ pip install -r requirements.txt
30
+
31
+ setup:
32
+ python scripts/setup_environment.py --environment development
33
+
34
+ setup-prod:
35
+ python scripts/setup_environment.py --environment production
36
+
37
+ setup-test:
38
+ python scripts/setup_environment.py --environment testing
39
+
40
+ # Testing
41
+ test:
42
+ python -m pytest tests/ -v
43
+
44
+ test-cov:
45
+ python -m pytest tests/ --cov=chat_agent --cov-report=html --cov-report=term
46
+
47
+ test-integration:
48
+ python -m pytest tests/integration/ -v
49
+
50
+ test-unit:
51
+ python -m pytest tests/unit/ -v
52
+
53
+ # Database operations
54
+ migrate:
55
+ python scripts/init_db.py init --config development
56
+
57
+ migrate-prod:
58
+ python scripts/init_db.py init --config production
59
+
60
+ seed:
61
+ python scripts/init_db.py seed --config development
62
+
63
+ reset-db:
64
+ python scripts/init_db.py reset --config development
65
+
66
+ db-status:
67
+ python scripts/init_db.py status --config development
68
+
69
+ # Application running
70
+ run:
71
+ python app.py
72
+
73
+ run-prod:
74
+ gunicorn --bind 0.0.0.0:5000 --workers 4 --worker-class eventlet app:app
75
+
76
+ run-debug:
77
+ FLASK_DEBUG=True python app.py
78
+
79
+ # Docker operations
80
+ docker-build:
81
+ docker build -t chat-agent .
82
+
83
+ docker-run:
84
+ docker-compose up -d
85
+
86
+ docker-dev:
87
+ docker-compose -f docker-compose.dev.yml up -d
88
+
89
+ docker-stop:
90
+ docker-compose down
91
+ docker-compose -f docker-compose.dev.yml down
92
+
93
+ docker-logs:
94
+ docker-compose logs -f chat-agent
95
+
96
+ docker-clean:
97
+ docker-compose down -v
98
+ docker system prune -f
99
+
100
+ # Code quality
101
+ lint:
102
+ flake8 chat_agent/ --max-line-length=100 --ignore=E203,W503
103
+ flake8 tests/ --max-line-length=100 --ignore=E203,W503
104
+
105
+ format:
106
+ black chat_agent/ tests/ --line-length=100
107
+ isort chat_agent/ tests/
108
+
109
+ type-check:
110
+ mypy chat_agent/ --ignore-missing-imports
111
+
112
+ # Cleanup
113
+ clean:
114
+ find . -type f -name "*.pyc" -delete
115
+ find . -type d -name "__pycache__" -delete
116
+ find . -type d -name "*.egg-info" -exec rm -rf {} +
117
+ rm -rf .coverage htmlcov/ .pytest_cache/ .mypy_cache/
118
+ rm -rf logs/*.log
119
+
120
+ clean-all: clean
121
+ rm -rf venv/
122
+ rm -rf instance/
123
+ rm -rf flask_session/
124
+
125
+ # Health checks
126
+ health:
127
+ curl -s http://localhost:5000/health/ | python -m json.tool
128
+
129
+ health-detailed:
130
+ curl -s http://localhost:5000/health/detailed | python -m json.tool
131
+
132
+ # Development helpers
133
+ dev-install:
134
+ pip install -r requirements.txt
135
+ pip install flask-debugtoolbar ipdb watchdog black flake8 isort mypy
136
+
137
+ logs:
138
+ tail -f logs/chat_agent.log
139
+
140
+ logs-error:
141
+ tail -f logs/chat_agent.log | grep ERROR
142
+
143
+ # Production deployment helpers
144
+ deploy-check:
145
+ @echo "Pre-deployment checklist:"
146
+ @echo "- [ ] Environment variables configured"
147
+ @echo "- [ ] Database migrations applied"
148
+ @echo "- [ ] SSL certificates installed"
149
+ @echo "- [ ] Firewall configured"
150
+ @echo "- [ ] Monitoring set up"
151
+ @echo "- [ ] Backups configured"
152
+
153
+ backup-db:
154
+ pg_dump $(DATABASE_URL) > backups/backup_$(shell date +%Y%m%d_%H%M%S).sql
155
+
156
+ restore-db:
157
+ @echo "Warning: This will overwrite the current database!"
158
+ @read -p "Enter backup file path: " backup_file; \
159
+ psql $(DATABASE_URL) < $$backup_file
160
+
161
+ # Monitoring
162
+ monitor:
163
+ watch -n 5 'curl -s http://localhost:5000/health/detailed | python -m json.tool'
164
+
165
+ # Documentation
166
+ docs:
167
+ @echo "Documentation available:"
168
+ @echo "- README.md - Project overview"
169
+ @echo "- docs/DEPLOYMENT.md - Deployment guide"
170
+ @echo "- docs/ENVIRONMENT_SETUP.md - Environment setup"
171
+ @echo "- .kiro/specs/multi-language-chat-agent/ - Feature specifications"
README.md CHANGED
@@ -1,12 +1,91 @@
1
- ---
2
- title: Scratch Chat
3
- emoji: 📉
4
- colorFrom: purple
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: other
9
- short_description: chat assisstance
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-Language Chat Agent
2
+
3
+ A Flask-based chat agent that supports multiple programming languages with Python as the default. The agent maintains proper chat history, integrates with Groq LangChain API for LLM capabilities, and provides language-specific assistance to students learning to code.
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ chat_agent/
9
+ ├── chat_agent/ # Main application package
10
+ │ ├── __init__.py
11
+ │ ├── api/ # REST API endpoints
12
+ │ ├── models/ # Data models
13
+ │ ├── services/ # Business logic services
14
+ │ ├── utils/ # Utility functions
15
+ │ └── websocket/ # WebSocket handlers
16
+ ├── static/ # Static assets (CSS, JS)
17
+ │ ├── css/
18
+ │ └── js/
19
+ ├── templates/ # HTML templates
20
+ ├── tests/ # Test suite
21
+ │ ├── unit/ # Unit tests
22
+ │ └── integration/ # Integration tests
23
+ ├── app.py # Application entry point
24
+ ├── config.py # Configuration management
25
+ ├── requirements.txt # Python dependencies
26
+ └── .env.example # Environment variables template
27
+ ```
28
+
29
+ ## Setup Instructions
30
+
31
+ 1. **Clone and navigate to the project directory**
32
+
33
+ 2. **Create a virtual environment**
34
+ ```bash
35
+ python -m venv venv
36
+ source venv/bin/activate # On Windows: venv\Scripts\activate
37
+ ```
38
+
39
+ 3. **Install dependencies**
40
+ ```bash
41
+ pip install -r requirements.txt
42
+ ```
43
+
44
+ 4. **Set up environment variables**
45
+ ```bash
46
+ cp .env.example .env
47
+ # Edit .env with your actual configuration values
48
+ ```
49
+
50
+ 5. **Set up databases**
51
+ - Install and start PostgreSQL
52
+ - Install and start Redis
53
+ - Update database connection strings in .env
54
+
55
+ 6. **Run the application**
56
+ ```bash
57
+ python app.py
58
+ ```
59
+
60
+ ## Environment Variables
61
+
62
+ Copy `.env.example` to `.env` and configure the following:
63
+
64
+ - `GROQ_API_KEY`: Your Groq API key for LLM integration
65
+ - `DATABASE_URL`: PostgreSQL connection string
66
+ - `REDIS_URL`: Redis connection string
67
+ - `SECRET_KEY`: Flask secret key for sessions
68
+
69
+ ## Features
70
+
71
+ - Multi-language programming support (Python, JavaScript, Java, C++, etc.)
72
+ - Real-time chat with WebSocket communication
73
+ - Persistent chat history with Redis caching
74
+ - Groq LangChain API integration
75
+ - Session management for concurrent users
76
+ - Language context switching mid-conversation
77
+
78
+ ## Development
79
+
80
+ This project follows the spec-driven development methodology. See the implementation tasks in `.kiro/specs/multi-language-chat-agent/tasks.md` for detailed development steps.
81
+
82
+ ## Testing
83
+
84
+ Run tests with:
85
+ ```bash
86
+ pytest tests/
87
+ ```
88
+
89
+ ## License
90
+
91
+ MIT License
REDIS_SETUP.md ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Redis Setup Guide for Chat Agent
2
+
3
+ ## Why Redis is Used
4
+
5
+ The chat agent uses Redis for several performance and scalability benefits:
6
+
7
+ 1. **Session Caching** - Fast access to frequently used session data
8
+ 2. **Chat History Caching** - Quick retrieval of recent messages
9
+ 3. **Rate Limiting** - Distributed rate limiting across multiple app instances
10
+ 4. **User Session Tracking** - Managing multiple sessions per user
11
+
12
+ ## Error 10061 Explanation
13
+
14
+ **Error 10061 connecting to localhost:6379** means:
15
+ - Port 6379 is Redis's default port
16
+ - No Redis server is running on your machine
17
+ - The application can't connect to Redis for caching
18
+
19
+ ## Installation Options
20
+
21
+ ### Option 1: Windows Native Installation
22
+
23
+ 1. **Download Redis for Windows:**
24
+ - Visit: https://github.com/microsoftarchive/redis/releases
25
+ - Download the latest `.msi` installer
26
+ - Run the installer and follow the setup wizard
27
+
28
+ 2. **Start Redis:**
29
+ ```cmd
30
+ # Redis should start automatically after installation
31
+ # Or manually start it:
32
+ redis-server
33
+ ```
34
+
35
+ 3. **Verify Installation:**
36
+ ```cmd
37
+ redis-cli ping
38
+ # Should return: PONG
39
+ ```
40
+
41
+ ### Option 2: Using Chocolatey (Windows)
42
+
43
+ ```cmd
44
+ # Install Chocolatey first if you don't have it
45
+ # Then install Redis:
46
+ choco install redis-64
47
+
48
+ # Start Redis
49
+ redis-server
50
+
51
+ # Test connection
52
+ redis-cli ping
53
+ ```
54
+
55
+ ### Option 3: Using Docker (Recommended)
56
+
57
+ ```bash
58
+ # Pull and run Redis container
59
+ docker run -d -p 6379:6379 --name redis redis:alpine
60
+
61
+ # Verify it's running
62
+ docker ps
63
+
64
+ # Test connection
65
+ docker exec -it redis redis-cli ping
66
+ ```
67
+
68
+ ### Option 4: WSL/Linux
69
+
70
+ ```bash
71
+ # Update package list
72
+ sudo apt update
73
+
74
+ # Install Redis
75
+ sudo apt install redis-server
76
+
77
+ # Start Redis service
78
+ sudo systemctl start redis-server
79
+
80
+ # Enable auto-start on boot
81
+ sudo systemctl enable redis-server
82
+
83
+ # Test connection
84
+ redis-cli ping
85
+ ```
86
+
87
+ ## Configuration
88
+
89
+ ### Environment Variables
90
+
91
+ Create a `.env` file in your project root:
92
+
93
+ ```env
94
+ # Redis Configuration
95
+ REDIS_URL=redis://localhost:6379/0
96
+ REDIS_HOST=localhost
97
+ REDIS_PORT=6379
98
+ REDIS_DB=0
99
+ REDIS_PASSWORD=
100
+
101
+ # For production with password:
102
+ # REDIS_URL=redis://:password@localhost:6379/0
103
+ # REDIS_PASSWORD=your-secure-password
104
+ ```
105
+
106
+ ### Testing Configuration
107
+
108
+ For testing without Redis, set:
109
+
110
+ ```env
111
+ REDIS_URL=None
112
+ ```
113
+
114
+ Or use the testing configuration which automatically disables Redis.
115
+
116
+ ## Running Without Redis
117
+
118
+ The application is designed to work without Redis, but with reduced performance:
119
+
120
+ ### What Works Without Redis:
121
+ - ✅ All API endpoints function normally
122
+ - ✅ Session management (database-only)
123
+ - ✅ Chat history (database-only)
124
+ - ✅ Language context management
125
+ - ✅ Authentication and authorization
126
+
127
+ ### What's Affected Without Redis:
128
+ - ⚠️ **Performance**: Slower session and message retrieval
129
+ - ⚠️ **Rate Limiting**: Uses in-memory storage (not distributed)
130
+ - ⚠️ **Caching**: No caching layer, all requests hit the database
131
+ - ⚠️ **Scalability**: Cannot scale across multiple app instances
132
+
133
+ ### Performance Impact:
134
+ - Session retrieval: ~50-100ms slower per request
135
+ - Chat history: ~100-200ms slower for large histories
136
+ - Rate limiting: Resets when app restarts
137
+
138
+ ## Production Recommendations
139
+
140
+ ### Redis Configuration for Production:
141
+
142
+ 1. **Use a dedicated Redis instance**
143
+ 2. **Enable password authentication**
144
+ 3. **Configure persistence** (RDB + AOF)
145
+ 4. **Set up monitoring**
146
+ 5. **Use Redis Cluster** for high availability
147
+
148
+ ### Example Production Config:
149
+
150
+ ```env
151
+ # Production Redis with authentication
152
+ REDIS_URL=redis://:your-secure-password@redis-server:6379/0
153
+ REDIS_PASSWORD=your-secure-password
154
+
155
+ # Connection pool settings
156
+ REDIS_MAX_CONNECTIONS=20
157
+ REDIS_SOCKET_TIMEOUT=5
158
+ REDIS_SOCKET_CONNECT_TIMEOUT=5
159
+ ```
160
+
161
+ ## Troubleshooting
162
+
163
+ ### Common Issues:
164
+
165
+ 1. **"Connection refused" (Error 10061)**
166
+ - Redis server is not running
167
+ - Wrong host/port configuration
168
+ - Firewall blocking the connection
169
+
170
+ 2. **"Authentication failed"**
171
+ - Wrong password in REDIS_URL
172
+ - Redis configured with auth but no password provided
173
+
174
+ 3. **"Connection timeout"**
175
+ - Network issues
176
+ - Redis server overloaded
177
+ - Wrong host address
178
+
179
+ ### Debug Commands:
180
+
181
+ ```bash
182
+ # Check if Redis is running
183
+ redis-cli ping
184
+
185
+ # Check Redis info
186
+ redis-cli info
187
+
188
+ # Monitor Redis commands
189
+ redis-cli monitor
190
+
191
+ # Check specific database
192
+ redis-cli -n 0 keys "*"
193
+ ```
194
+
195
+ ## Development vs Production
196
+
197
+ ### Development (Local):
198
+ ```env
199
+ REDIS_URL=redis://localhost:6379/0
200
+ ```
201
+
202
+ ### Testing (No Redis):
203
+ ```env
204
+ REDIS_URL=None
205
+ ```
206
+
207
+ ### Production (Managed Redis):
208
+ ```env
209
+ REDIS_URL=redis://:password@your-redis-host:6379/0
210
+ ```
211
+
212
+ ## Cloud Redis Services
213
+
214
+ For production, consider managed Redis services:
215
+
216
+ - **AWS ElastiCache**
217
+ - **Google Cloud Memorystore**
218
+ - **Azure Cache for Redis**
219
+ - **Redis Cloud**
220
+ - **DigitalOcean Managed Redis**
221
+
222
+ These provide:
223
+ - Automatic backups
224
+ - High availability
225
+ - Monitoring and alerts
226
+ - Security and compliance
227
+ - Automatic scaling
228
+
229
+ ## Summary
230
+
231
+ The **Error 10061** occurs because Redis is not installed or running. You have three options:
232
+
233
+ 1. **Install Redis** (recommended for development/production)
234
+ 2. **Use Docker** to run Redis (easiest setup)
235
+ 3. **Run without Redis** (works but with performance impact)
236
+
237
+ The application gracefully handles Redis being unavailable and will continue to function using database-only operations.
Refine Scratch Block Builders_.docx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:40b2b72cc62775bc1e412b0c6985185d36c49e4239f333d3d64f861cc93bb01e
3
+ size 6226907
app.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Main application entry point for the multi-language chat agent."""
2
+
3
+ import os
4
+ from flask import Flask
5
+ from flask_socketio import SocketIO
6
+ from flask_session import Session
7
+ import redis
8
+
9
+ from config import config
10
+ from chat_agent.models.base import db
11
+ from chat_agent.utils.logging_config import setup_logging
12
+ from chat_agent.utils.error_handler import set_error_handler, ErrorHandler
13
+ from chat_agent.utils.connection_pool import initialize_connection_pools, get_connection_pool_manager
14
+ from chat_agent.services.cache_service import initialize_cache_service, get_cache_service
15
+ from chat_agent.utils.response_optimization import ResponseMiddleware
16
+
17
+ # Initialize extensions
18
+ socketio = SocketIO()
19
+ session = Session()
20
+
21
+
22
+ def create_app(config_name=None):
23
+ """Application factory pattern."""
24
+ if config_name is None:
25
+ config_name = os.getenv('FLASK_ENV', 'development')
26
+
27
+ app = Flask(__name__)
28
+ app.config.from_object(config[config_name])
29
+
30
+ # Setup comprehensive logging
31
+ loggers = setup_logging("chat_agent", app.config.get('LOG_LEVEL', 'INFO'))
32
+ app.logger = loggers['main']
33
+
34
+ # Setup global error handler
35
+ error_handler = ErrorHandler(loggers['error'])
36
+ set_error_handler(error_handler)
37
+
38
+ app.logger.info("Chat agent application starting", extra={
39
+ 'config': config_name,
40
+ 'debug': app.config.get('DEBUG', False),
41
+ 'logging_level': app.config.get('LOG_LEVEL', 'INFO')
42
+ })
43
+
44
+ # Initialize connection pools for performance optimization
45
+ database_url = app.config.get('SQLALCHEMY_DATABASE_URI')
46
+ redis_url = app.config.get('REDIS_URL')
47
+
48
+ connection_pool_manager = initialize_connection_pools(database_url, redis_url)
49
+
50
+ # Configure SQLAlchemy to use connection pool
51
+ if connection_pool_manager:
52
+ app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
53
+ 'pool_size': int(os.getenv('DB_POOL_SIZE', '10')),
54
+ 'max_overflow': int(os.getenv('DB_MAX_OVERFLOW', '20')),
55
+ 'pool_recycle': int(os.getenv('DB_POOL_RECYCLE', '3600')),
56
+ 'pool_pre_ping': True,
57
+ 'pool_timeout': int(os.getenv('DB_POOL_TIMEOUT', '30'))
58
+ }
59
+
60
+ # Initialize extensions with app
61
+ db.init_app(app)
62
+ socketio.init_app(app, cors_allowed_origins="*")
63
+
64
+ # Initialize response optimization middleware
65
+ ResponseMiddleware(app)
66
+
67
+ # Configure Redis for sessions and caching (if available)
68
+ redis_client = None
69
+ if redis_url and redis_url != 'None':
70
+ try:
71
+ # Use connection pool manager's Redis client if available
72
+ if connection_pool_manager:
73
+ redis_client = connection_pool_manager.get_redis_client()
74
+ else:
75
+ redis_client = redis.from_url(redis_url)
76
+
77
+ if redis_client:
78
+ redis_client.ping() # Test connection
79
+ app.config['SESSION_REDIS'] = redis_client
80
+ session.init_app(app)
81
+ app.logger.info("Redis connection established for sessions and caching")
82
+ else:
83
+ raise Exception("Redis client not available")
84
+
85
+ except Exception as e:
86
+ app.logger.warning(f"Redis connection failed: {e}. Sessions will use filesystem.")
87
+ app.config['SESSION_TYPE'] = 'filesystem'
88
+ session.init_app(app)
89
+ redis_client = None
90
+ else:
91
+ app.logger.info("Redis disabled. Using filesystem sessions.")
92
+ app.config['SESSION_TYPE'] = 'filesystem'
93
+ session.init_app(app)
94
+
95
+ # Initialize cache service with Redis client
96
+ cache_service = initialize_cache_service(redis_client)
97
+ app.logger.info(f"Cache service initialized", extra={
98
+ 'redis_enabled': bool(redis_client)
99
+ })
100
+
101
+ # Register API blueprints
102
+ from chat_agent.api import chat_bp, create_limiter, setup_error_handlers, RequestLoggingMiddleware
103
+ from chat_agent.api.health import health_bp
104
+ from chat_agent.api.performance_routes import performance_bp
105
+ app.register_blueprint(chat_bp)
106
+ app.register_blueprint(health_bp)
107
+ app.register_blueprint(performance_bp)
108
+
109
+ # Configure rate limiting
110
+ limiter = create_limiter(app)
111
+ if redis_url and redis_url != 'None':
112
+ limiter.storage_uri = redis_url
113
+ # If no Redis, limiter will use in-memory storage (with warning)
114
+
115
+ # Setup error handlers
116
+ setup_error_handlers(app)
117
+
118
+ # Setup request logging middleware
119
+ RequestLoggingMiddleware(app)
120
+
121
+ # Add chat interface route
122
+ @app.route('/')
123
+ @app.route('/chat')
124
+ def chat_interface():
125
+ """Serve the chat interface."""
126
+ from flask import render_template
127
+ return render_template('chat.html')
128
+
129
+ # Initialize real chat agent services
130
+ from chat_agent.services.groq_client import GroqClient
131
+ from chat_agent.services.language_context import LanguageContextManager
132
+ from chat_agent.services.session_manager import SessionManager
133
+ from chat_agent.services.chat_history import ChatHistoryManager
134
+ from chat_agent.services.chat_agent import ChatAgent
135
+ from chat_agent.services.programming_assistance import ProgrammingAssistanceService
136
+
137
+ # Initialize services
138
+ try:
139
+ # Initialize Redis client
140
+ redis_url = app.config.get('REDIS_URL', 'redis://localhost:6379/0')
141
+ redis_client = redis.from_url(redis_url)
142
+
143
+ # Test Redis connection
144
+ redis_client.ping()
145
+ print("✅ Redis connection successful")
146
+
147
+ groq_client = GroqClient()
148
+ language_context_manager = LanguageContextManager()
149
+ session_manager = SessionManager(redis_client)
150
+ chat_history_manager = ChatHistoryManager(redis_client)
151
+ programming_assistance_service = ProgrammingAssistanceService()
152
+
153
+ # Initialize main chat agent
154
+ chat_agent = ChatAgent(
155
+ groq_client=groq_client,
156
+ language_context_manager=language_context_manager,
157
+ session_manager=session_manager,
158
+ chat_history_manager=chat_history_manager,
159
+ programming_assistance_service=programming_assistance_service
160
+ )
161
+
162
+ print("✅ Chat agent services initialized successfully")
163
+
164
+ except Exception as e:
165
+ print(f"⚠️ Error initializing chat agent services: {e}")
166
+ print("🔄 Falling back to demo mode")
167
+ chat_agent = None
168
+
169
+ # Store session mapping for WebSocket connections
170
+ websocket_sessions = {}
171
+
172
+ # Initialize WebSocket handlers for chat interface
173
+ @socketio.on('connect')
174
+ def handle_connect(auth=None):
175
+ """Handle WebSocket connection for chat interface."""
176
+ from flask_socketio import emit
177
+ from flask import request
178
+ import uuid
179
+
180
+ try:
181
+ # Create a new session for this connection
182
+ user_id = f"user_{request.sid}" # Use socket ID as user ID for demo
183
+
184
+ if chat_agent and session_manager:
185
+ session = session_manager.create_session(user_id, language='python')
186
+ websocket_sessions[request.sid] = session.id
187
+
188
+ emit('connection_status', {
189
+ 'status': 'connected',
190
+ 'session_id': session.id,
191
+ 'language': session.language,
192
+ 'message_count': session.message_count,
193
+ 'timestamp': datetime.now().isoformat()
194
+ })
195
+
196
+ print(f"WebSocket connected: session={session.id}, user={user_id}")
197
+ else:
198
+ # Fallback to demo mode
199
+ session_id = str(uuid.uuid4())
200
+ websocket_sessions[request.sid] = session_id
201
+
202
+ emit('connection_status', {
203
+ 'status': 'connected',
204
+ 'session_id': session_id,
205
+ 'language': 'python',
206
+ 'message_count': 0,
207
+ 'timestamp': datetime.now().isoformat()
208
+ })
209
+
210
+ print(f"WebSocket connected (demo mode): session={session_id}, user={user_id}")
211
+
212
+ except Exception as e:
213
+ print(f"Error connecting WebSocket: {e}")
214
+ emit('error', {'message': 'Connection failed', 'code': 'CONNECTION_ERROR'})
215
+
216
+ @socketio.on('disconnect')
217
+ def handle_disconnect(reason=None):
218
+ """Handle WebSocket disconnection."""
219
+ from flask import request
220
+
221
+ # Clean up session mapping
222
+ if request.sid in websocket_sessions:
223
+ session_id = websocket_sessions[request.sid]
224
+ del websocket_sessions[request.sid]
225
+ print(f"WebSocket disconnected: session={session_id}")
226
+ else:
227
+ print("WebSocket disconnected")
228
+
229
+ @socketio.on('message')
230
+ def handle_message(data):
231
+ """Handle chat messages using real chat agent."""
232
+ from flask_socketio import emit
233
+ from flask import request
234
+
235
+ try:
236
+ print(f"Received message: {data}")
237
+
238
+ # Get session ID for this connection
239
+ if request.sid not in websocket_sessions:
240
+ emit('error', {'message': 'No active session', 'code': 'NO_SESSION'})
241
+ return
242
+
243
+ session_id = websocket_sessions[request.sid]
244
+ content = data.get('content', '').strip()
245
+ language = data.get('language', 'python')
246
+
247
+ if not content:
248
+ emit('error', {'message': 'Empty message received', 'code': 'EMPTY_MESSAGE'})
249
+ return
250
+
251
+ # Process message with real chat agent
252
+ emit('response_start', {
253
+ 'session_id': session_id,
254
+ 'language': language,
255
+ 'timestamp': datetime.now().isoformat()
256
+ })
257
+
258
+ try:
259
+ if chat_agent:
260
+ # Use the real chat agent to process the message
261
+ print(f"🤖 Processing message with chat agent: '{content}' (language: {language})")
262
+ result = chat_agent.process_message(session_id, content, language)
263
+
264
+ # Extract response content from the result dictionary
265
+ if isinstance(result, dict) and 'response' in result:
266
+ response = result['response']
267
+ print(f"✅ Chat agent response: {response[:100]}..." if len(response) > 100 else f"✅ Chat agent response: {response}")
268
+ else:
269
+ print(f"✅ Unexpected response format: {type(result)}, value: {result}")
270
+ response = str(result)
271
+ else:
272
+ # Fallback response if chat agent is not available
273
+ response = f"I understand you're asking about: '{content}'. I'm currently in demo mode, but I can help you with {language} programming concepts, debugging, and best practices. The full AI-powered assistant will provide more detailed responses."
274
+
275
+ # Ensure response is a string before processing
276
+ if not isinstance(response, str):
277
+ response = str(response)
278
+
279
+ # Send response in chunks to simulate streaming
280
+ words = response.split()
281
+ chunk_size = 5
282
+ total_chunks = (len(words) + chunk_size - 1) // chunk_size
283
+
284
+ for i in range(0, len(words), chunk_size):
285
+ chunk = ' '.join(words[i:i+chunk_size]) + ' '
286
+ emit('response_chunk', {
287
+ 'content': chunk,
288
+ 'timestamp': datetime.now().isoformat()
289
+ })
290
+ socketio.sleep(0.02) # Small delay for streaming effect
291
+
292
+ emit('response_complete', {
293
+ 'message_id': str(uuid.uuid4()),
294
+ 'total_chunks': total_chunks,
295
+ 'processing_time': 1.0,
296
+ 'timestamp': datetime.now().isoformat()
297
+ })
298
+
299
+ except Exception as e:
300
+ print(f"❌ Error processing message with chat agent: {e}")
301
+ # Fallback to demo response if chat agent fails
302
+ demo_response = f"I apologize, but I'm having trouble processing your request right now. You asked about: '{content}'. Please try again in a moment, or check that the Groq API key is properly configured."
303
+
304
+ emit('response_chunk', {
305
+ 'content': demo_response,
306
+ 'timestamp': datetime.now().isoformat()
307
+ })
308
+
309
+ emit('response_complete', {
310
+ 'message_id': str(uuid.uuid4()),
311
+ 'total_chunks': 1,
312
+ 'processing_time': 0.1,
313
+ 'timestamp': datetime.now().isoformat()
314
+ })
315
+
316
+ except Exception as e:
317
+ print(f"Error handling message: {e}")
318
+ emit('error', {'message': 'Failed to process message', 'code': 'PROCESSING_ERROR'})
319
+
320
+ @socketio.on('language_switch')
321
+ def handle_language_switch(data):
322
+ """Handle language switching using real chat agent."""
323
+ from flask_socketio import emit
324
+ from flask import request
325
+
326
+ try:
327
+ # Get session ID for this connection
328
+ if request.sid not in websocket_sessions:
329
+ emit('error', {'message': 'No active session', 'code': 'NO_SESSION'})
330
+ return
331
+
332
+ session_id = websocket_sessions[request.sid]
333
+ new_language = data.get('language', 'python')
334
+
335
+ language_names = {
336
+ 'python': 'Python',
337
+ 'javascript': 'JavaScript',
338
+ 'java': 'Java',
339
+ 'cpp': 'C++',
340
+ 'csharp': 'C#',
341
+ 'go': 'Go',
342
+ 'rust': 'Rust',
343
+ 'typescript': 'TypeScript'
344
+ }
345
+
346
+ try:
347
+ if chat_agent:
348
+ # Use real chat agent to switch language
349
+ result = chat_agent.switch_language(session_id, new_language)
350
+
351
+ emit('language_switched', {
352
+ 'previous_language': result.get('previous_language', 'python'),
353
+ 'new_language': result.get('new_language', new_language),
354
+ 'message': result.get('message', f'Language switched to {language_names.get(new_language, new_language)}'),
355
+ 'timestamp': datetime.now().isoformat()
356
+ })
357
+
358
+ print(f"🔄 Language switched to: {new_language} for session {session_id}")
359
+ else:
360
+ # Fallback for demo mode
361
+ emit('language_switched', {
362
+ 'previous_language': 'python',
363
+ 'new_language': new_language,
364
+ 'message': f"Switched to {language_names.get(new_language, new_language)}. I'm now ready to help you with {language_names.get(new_language, new_language)} programming!",
365
+ 'timestamp': datetime.now().isoformat()
366
+ })
367
+
368
+ print(f"🔄 Language switched to: {new_language} (demo mode)")
369
+
370
+ except Exception as e:
371
+ print(f"❌ Error switching language: {e}")
372
+ emit('error', {'message': 'Failed to switch language', 'code': 'LANGUAGE_SWITCH_ERROR'})
373
+
374
+ except Exception as e:
375
+ print(f"Error handling language switch: {e}")
376
+ emit('error', {'message': 'Failed to switch language', 'code': 'LANGUAGE_SWITCH_ERROR'})
377
+
378
+ # Add error handlers for WebSocket
379
+ @socketio.on_error_default
380
+ def default_error_handler(e):
381
+ """Handle WebSocket errors."""
382
+ print(f"WebSocket error: {e}")
383
+ from flask_socketio import emit
384
+ emit('error', {'message': 'Connection error occurred', 'code': 'WEBSOCKET_ERROR'})
385
+
386
+ # Import datetime for timestamps
387
+ from datetime import datetime
388
+ import uuid
389
+
390
+ return app
391
+
392
+
393
+ if __name__ == '__main__':
394
+ app = create_app()
395
+ socketio.run(app, debug=True, host='0.0.0.0', port=5000)
blocks/blocks.json ADDED
@@ -0,0 +1,2221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "data_setvariableto": {
1368
+ "opcode": "data_setvariableto",
1369
+ "next": null,
1370
+ "parent": null,
1371
+ "inputs": {
1372
+ "VALUE": [
1373
+ 1,
1374
+ [
1375
+ 10,
1376
+ "0"
1377
+ ]
1378
+ ]
1379
+ },
1380
+ "fields": {
1381
+ "VARIABLE": [
1382
+ "my variable",
1383
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
1384
+ ]
1385
+ },
1386
+ "shadow": false,
1387
+ "topLevel": true,
1388
+ "x": 348,
1389
+ "y": 241
1390
+ },
1391
+ "data_changevariableby": {
1392
+ "opcode": "data_changevariableby",
1393
+ "next": null,
1394
+ "parent": null,
1395
+ "inputs": {
1396
+ "VALUE": [
1397
+ 1,
1398
+ [
1399
+ 4,
1400
+ "1"
1401
+ ]
1402
+ ]
1403
+ },
1404
+ "fields": {
1405
+ "VARIABLE": [
1406
+ "my variable",
1407
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
1408
+ ]
1409
+ },
1410
+ "shadow": false,
1411
+ "topLevel": true,
1412
+ "x": 313,
1413
+ "y": 363
1414
+ },
1415
+ "data_showvariable": {
1416
+ "opcode": "data_showvariable",
1417
+ "next": null,
1418
+ "parent": null,
1419
+ "inputs": {},
1420
+ "fields": {
1421
+ "VARIABLE": [
1422
+ "my variable",
1423
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
1424
+ ]
1425
+ },
1426
+ "shadow": false,
1427
+ "topLevel": true,
1428
+ "x": 415,
1429
+ "y": 473
1430
+ },
1431
+ "data_hidevariable": {
1432
+ "opcode": "data_hidevariable",
1433
+ "next": null,
1434
+ "parent": null,
1435
+ "inputs": {},
1436
+ "fields": {
1437
+ "VARIABLE": [
1438
+ "my variable",
1439
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
1440
+ ]
1441
+ },
1442
+ "shadow": false,
1443
+ "topLevel": true,
1444
+ "x": 319,
1445
+ "y": 587
1446
+ },
1447
+ "data_addtolist": {
1448
+ "opcode": "data_addtolist",
1449
+ "next": null,
1450
+ "parent": null,
1451
+ "inputs": {
1452
+ "ITEM": [
1453
+ 1,
1454
+ [
1455
+ 10,
1456
+ "thing"
1457
+ ]
1458
+ ]
1459
+ },
1460
+ "fields": {
1461
+ "LIST": [
1462
+ "MY_LIST",
1463
+ "o6`kIhtT{xWH+rX(5d,A"
1464
+ ]
1465
+ },
1466
+ "shadow": false,
1467
+ "topLevel": true,
1468
+ "x": 385,
1469
+ "y": 109
1470
+ },
1471
+ "data_deleteoflist": {
1472
+ "opcode": "data_deleteoflist",
1473
+ "next": null,
1474
+ "parent": null,
1475
+ "inputs": {
1476
+ "INDEX": [
1477
+ 1,
1478
+ [
1479
+ 7,
1480
+ "1"
1481
+ ]
1482
+ ]
1483
+ },
1484
+ "fields": {
1485
+ "LIST": [
1486
+ "MY_LIST",
1487
+ "o6`kIhtT{xWH+rX(5d,A"
1488
+ ]
1489
+ },
1490
+ "shadow": false,
1491
+ "topLevel": true,
1492
+ "x": 384,
1493
+ "y": 244
1494
+ },
1495
+ "data_deletealloflist": {
1496
+ "opcode": "data_deletealloflist",
1497
+ "next": null,
1498
+ "parent": null,
1499
+ "inputs": {},
1500
+ "fields": {
1501
+ "LIST": [
1502
+ "MY_LIST",
1503
+ "o6`kIhtT{xWH+rX(5d,A"
1504
+ ]
1505
+ },
1506
+ "shadow": false,
1507
+ "topLevel": true,
1508
+ "x": 387,
1509
+ "y": 374
1510
+ },
1511
+ "data_insertatlist": {
1512
+ "opcode": "data_insertatlist",
1513
+ "next": null,
1514
+ "parent": null,
1515
+ "inputs": {
1516
+ "ITEM": [
1517
+ 1,
1518
+ [
1519
+ 10,
1520
+ "thing"
1521
+ ]
1522
+ ],
1523
+ "INDEX": [
1524
+ 1,
1525
+ [
1526
+ 7,
1527
+ "1"
1528
+ ]
1529
+ ]
1530
+ },
1531
+ "fields": {
1532
+ "LIST": [
1533
+ "MY_LIST",
1534
+ "o6`kIhtT{xWH+rX(5d,A"
1535
+ ]
1536
+ },
1537
+ "shadow": false,
1538
+ "topLevel": true,
1539
+ "x": 366,
1540
+ "y": 527
1541
+ },
1542
+ "data_replaceitemoflist": {
1543
+ "opcode": "data_replaceitemoflist",
1544
+ "next": null,
1545
+ "parent": null,
1546
+ "inputs": {
1547
+ "INDEX": [
1548
+ 1,
1549
+ [
1550
+ 7,
1551
+ "1"
1552
+ ]
1553
+ ],
1554
+ "ITEM": [
1555
+ 1,
1556
+ [
1557
+ 10,
1558
+ "thing"
1559
+ ]
1560
+ ]
1561
+ },
1562
+ "fields": {
1563
+ "LIST": [
1564
+ "MY_LIST",
1565
+ "o6`kIhtT{xWH+rX(5d,A"
1566
+ ]
1567
+ },
1568
+ "shadow": false,
1569
+ "topLevel": true,
1570
+ "x": 365,
1571
+ "y": 657
1572
+ },
1573
+ "data_itemoflist": {
1574
+ "opcode": "data_itemoflist",
1575
+ "next": null,
1576
+ "parent": null,
1577
+ "inputs": {
1578
+ "INDEX": [
1579
+ 1,
1580
+ [
1581
+ 7,
1582
+ "1"
1583
+ ]
1584
+ ]
1585
+ },
1586
+ "fields": {
1587
+ "LIST": [
1588
+ "MY_LIST",
1589
+ "o6`kIhtT{xWH+rX(5d,A"
1590
+ ]
1591
+ },
1592
+ "shadow": false,
1593
+ "topLevel": true,
1594
+ "x": 862,
1595
+ "y": 117
1596
+ },
1597
+ "data_itemnumoflist": {
1598
+ "opcode": "data_itemnumoflist",
1599
+ "next": null,
1600
+ "parent": null,
1601
+ "inputs": {
1602
+ "ITEM": [
1603
+ 1,
1604
+ [
1605
+ 10,
1606
+ "thing"
1607
+ ]
1608
+ ]
1609
+ },
1610
+ "fields": {
1611
+ "LIST": [
1612
+ "MY_LIST",
1613
+ "o6`kIhtT{xWH+rX(5d,A"
1614
+ ]
1615
+ },
1616
+ "shadow": false,
1617
+ "topLevel": true,
1618
+ "x": 883,
1619
+ "y": 238
1620
+ },
1621
+ "data_lengthoflist": {
1622
+ "opcode": "data_lengthoflist",
1623
+ "next": null,
1624
+ "parent": null,
1625
+ "inputs": {},
1626
+ "fields": {
1627
+ "LIST": [
1628
+ "MY_LIST",
1629
+ "o6`kIhtT{xWH+rX(5d,A"
1630
+ ]
1631
+ },
1632
+ "shadow": false,
1633
+ "topLevel": true,
1634
+ "x": 876,
1635
+ "y": 342
1636
+ },
1637
+ "data_listcontainsitem": {
1638
+ "opcode": "data_listcontainsitem",
1639
+ "next": null,
1640
+ "parent": null,
1641
+ "inputs": {
1642
+ "ITEM": [
1643
+ 1,
1644
+ [
1645
+ 10,
1646
+ "thing"
1647
+ ]
1648
+ ]
1649
+ },
1650
+ "fields": {
1651
+ "LIST": [
1652
+ "MY_LIST",
1653
+ "o6`kIhtT{xWH+rX(5d,A"
1654
+ ]
1655
+ },
1656
+ "shadow": false,
1657
+ "topLevel": true,
1658
+ "x": 871,
1659
+ "y": 463
1660
+ },
1661
+ "data_showlist": {
1662
+ "opcode": "data_showlist",
1663
+ "next": null,
1664
+ "parent": null,
1665
+ "inputs": {},
1666
+ "fields": {
1667
+ "LIST": [
1668
+ "MY_LIST",
1669
+ "o6`kIhtT{xWH+rX(5d,A"
1670
+ ]
1671
+ },
1672
+ "shadow": false,
1673
+ "topLevel": true,
1674
+ "x": 931,
1675
+ "y": 563
1676
+ },
1677
+ "data_hidelist": {
1678
+ "opcode": "data_hidelist",
1679
+ "next": null,
1680
+ "parent": null,
1681
+ "inputs": {},
1682
+ "fields": {
1683
+ "LIST": [
1684
+ "MY_LIST",
1685
+ "o6`kIhtT{xWH+rX(5d,A"
1686
+ ]
1687
+ },
1688
+ "shadow": false,
1689
+ "topLevel": true,
1690
+ "x": 962,
1691
+ "y": 716
1692
+ },
1693
+ "sound_playuntildone": {
1694
+ "opcode": "sound_playuntildone",
1695
+ "next": null,
1696
+ "parent": null,
1697
+ "inputs": {
1698
+ "SOUND_MENU": [
1699
+ 1,
1700
+ "4w%pR8G.yD%g-BwCj=uK"
1701
+ ]
1702
+ },
1703
+ "fields": {},
1704
+ "shadow": false,
1705
+ "topLevel": true,
1706
+ "x": 253,
1707
+ "y": 17
1708
+ },
1709
+ "sound_sounds_menu": {
1710
+ "opcode": "sound_sounds_menu",
1711
+ "next": null,
1712
+ "parent": "Pdc$U;s8e_uUfTX`}jOo",
1713
+ "inputs": {},
1714
+ "fields": {
1715
+ "SOUND_MENU": [
1716
+ "Meow",
1717
+ null
1718
+ ]
1719
+ },
1720
+ "shadow": true,
1721
+ "topLevel": false
1722
+ },
1723
+ "sound_play": {
1724
+ "opcode": "sound_play",
1725
+ "next": null,
1726
+ "parent": null,
1727
+ "inputs": {
1728
+ "SOUND_MENU": [
1729
+ 1,
1730
+ "i1U{^VHb*2`9?l}=:L)/"
1731
+ ]
1732
+ },
1733
+ "fields": {},
1734
+ "shadow": false,
1735
+ "topLevel": true,
1736
+ "x": 245,
1737
+ "y": 122
1738
+ },
1739
+ "sound_stopallsounds": {
1740
+ "opcode": "sound_stopallsounds",
1741
+ "next": null,
1742
+ "parent": null,
1743
+ "inputs": {},
1744
+ "fields": {},
1745
+ "shadow": false,
1746
+ "topLevel": true,
1747
+ "x": 253,
1748
+ "y": 245
1749
+ },
1750
+ "sound_changeeffectby": {
1751
+ "opcode": "sound_changeeffectby",
1752
+ "next": null,
1753
+ "parent": null,
1754
+ "inputs": {
1755
+ "VALUE": [
1756
+ 1,
1757
+ [
1758
+ 4,
1759
+ "10"
1760
+ ]
1761
+ ]
1762
+ },
1763
+ "fields": {
1764
+ "EFFECT": [
1765
+ "PITCH",
1766
+ null
1767
+ ]
1768
+ },
1769
+ "shadow": false,
1770
+ "topLevel": true,
1771
+ "x": 653,
1772
+ "y": 14
1773
+ },
1774
+ "sound_seteffectto": {
1775
+ "opcode": "sound_seteffectto",
1776
+ "next": null,
1777
+ "parent": null,
1778
+ "inputs": {
1779
+ "VALUE": [
1780
+ 1,
1781
+ [
1782
+ 4,
1783
+ "100"
1784
+ ]
1785
+ ]
1786
+ },
1787
+ "fields": {
1788
+ "EFFECT": [
1789
+ "PITCH",
1790
+ null
1791
+ ]
1792
+ },
1793
+ "shadow": false,
1794
+ "topLevel": true,
1795
+ "x": 653,
1796
+ "y": 139
1797
+ },
1798
+ "sound_cleareffects": {
1799
+ "opcode": "sound_cleareffects",
1800
+ "next": null,
1801
+ "parent": null,
1802
+ "inputs": {},
1803
+ "fields": {},
1804
+ "shadow": false,
1805
+ "topLevel": true,
1806
+ "x": 651,
1807
+ "y": 242
1808
+ },
1809
+ "sound_changevolumeby": {
1810
+ "opcode": "sound_changevolumeby",
1811
+ "next": null,
1812
+ "parent": null,
1813
+ "inputs": {
1814
+ "VOLUME": [
1815
+ 1,
1816
+ [
1817
+ 4,
1818
+ "-10"
1819
+ ]
1820
+ ]
1821
+ },
1822
+ "fields": {},
1823
+ "shadow": false,
1824
+ "topLevel": true,
1825
+ "x": 645,
1826
+ "y": 353
1827
+ },
1828
+ "sound_setvolumeto": {
1829
+ "opcode": "sound_setvolumeto",
1830
+ "next": null,
1831
+ "parent": null,
1832
+ "inputs": {
1833
+ "VOLUME": [
1834
+ 1,
1835
+ [
1836
+ 4,
1837
+ "100"
1838
+ ]
1839
+ ]
1840
+ },
1841
+ "fields": {},
1842
+ "shadow": false,
1843
+ "topLevel": true,
1844
+ "x": 1108,
1845
+ "y": 5
1846
+ },
1847
+ "sound_volume": {
1848
+ "opcode": "sound_volume",
1849
+ "next": null,
1850
+ "parent": null,
1851
+ "inputs": {},
1852
+ "fields": {},
1853
+ "shadow": false,
1854
+ "topLevel": true,
1855
+ "x": 1136,
1856
+ "y": 123
1857
+ },
1858
+ "looks_sayforsecs": {
1859
+ "opcode": "looks_sayforsecs",
1860
+ "next": null,
1861
+ "parent": null,
1862
+ "inputs": {
1863
+ "MESSAGE": [
1864
+ 1,
1865
+ [
1866
+ 10,
1867
+ "Hello!"
1868
+ ]
1869
+ ],
1870
+ "SECS": [
1871
+ 1,
1872
+ [
1873
+ 4,
1874
+ "2"
1875
+ ]
1876
+ ]
1877
+ },
1878
+ "fields": {},
1879
+ "shadow": false,
1880
+ "topLevel": true,
1881
+ "x": 408,
1882
+ "y": 91
1883
+ },
1884
+ "looks_say": {
1885
+ "opcode": "looks_say",
1886
+ "next": null,
1887
+ "parent": null,
1888
+ "inputs": {
1889
+ "MESSAGE": [
1890
+ 1,
1891
+ [
1892
+ 10,
1893
+ "Hello!"
1894
+ ]
1895
+ ]
1896
+ },
1897
+ "fields": {},
1898
+ "shadow": false,
1899
+ "topLevel": true,
1900
+ "x": 413,
1901
+ "y": 213
1902
+ },
1903
+ "looks_thinkforsecs": {
1904
+ "opcode": "looks_thinkforsecs",
1905
+ "next": null,
1906
+ "parent": null,
1907
+ "inputs": {
1908
+ "MESSAGE": [
1909
+ 1,
1910
+ [
1911
+ 10,
1912
+ "Hmm..."
1913
+ ]
1914
+ ],
1915
+ "SECS": [
1916
+ 1,
1917
+ [
1918
+ 4,
1919
+ "2"
1920
+ ]
1921
+ ]
1922
+ },
1923
+ "fields": {},
1924
+ "shadow": false,
1925
+ "topLevel": true,
1926
+ "x": 413,
1927
+ "y": 317
1928
+ },
1929
+ "looks_think": {
1930
+ "opcode": "looks_think",
1931
+ "next": null,
1932
+ "parent": null,
1933
+ "inputs": {
1934
+ "MESSAGE": [
1935
+ 1,
1936
+ [
1937
+ 10,
1938
+ "Hmm..."
1939
+ ]
1940
+ ]
1941
+ },
1942
+ "fields": {},
1943
+ "shadow": false,
1944
+ "topLevel": true,
1945
+ "x": 412,
1946
+ "y": 432
1947
+ },
1948
+ "looks_switchcostumeto": {
1949
+ "opcode": "looks_switchcostumeto",
1950
+ "next": null,
1951
+ "parent": null,
1952
+ "inputs": {
1953
+ "COSTUME": [
1954
+ 1,
1955
+ "8;bti4wv(iH9nkOacCJ|"
1956
+ ]
1957
+ },
1958
+ "fields": {},
1959
+ "shadow": false,
1960
+ "topLevel": true,
1961
+ "x": 411,
1962
+ "y": 555
1963
+ },
1964
+ "looks_costume": {
1965
+ "opcode": "looks_costume",
1966
+ "next": null,
1967
+ "parent": "Q#a,6LPWHqo9-0Nu*[SV",
1968
+ "inputs": {},
1969
+ "fields": {
1970
+ "COSTUME": [
1971
+ "costume2",
1972
+ null
1973
+ ]
1974
+ },
1975
+ "shadow": true,
1976
+ "topLevel": false
1977
+ },
1978
+ "looks_nextcostume": {
1979
+ "opcode": "looks_nextcostume",
1980
+ "next": null,
1981
+ "parent": null,
1982
+ "inputs": {},
1983
+ "fields": {},
1984
+ "shadow": false,
1985
+ "topLevel": true,
1986
+ "x": 419,
1987
+ "y": 687
1988
+ },
1989
+ "looks_switchbackdropto": {
1990
+ "opcode": "looks_switchbackdropto",
1991
+ "next": null,
1992
+ "parent": null,
1993
+ "inputs": {
1994
+ "BACKDROP": [
1995
+ 1,
1996
+ "-?yeX}29V*wd6W:unW0i"
1997
+ ]
1998
+ },
1999
+ "fields": {},
2000
+ "shadow": false,
2001
+ "topLevel": true,
2002
+ "x": 901,
2003
+ "y": 91
2004
+ },
2005
+ "looks_backdrops": {
2006
+ "opcode": "looks_backdrops",
2007
+ "next": null,
2008
+ "parent": "`Wm^p~l[(IWzc1|wNv*.",
2009
+ "inputs": {},
2010
+ "fields": {
2011
+ "BACKDROP": [
2012
+ "backdrop1",
2013
+ null
2014
+ ]
2015
+ },
2016
+ "shadow": true,
2017
+ "topLevel": false
2018
+ },
2019
+ "looks_changesizeby": {
2020
+ "opcode": "looks_changesizeby",
2021
+ "next": null,
2022
+ "parent": null,
2023
+ "inputs": {
2024
+ "CHANGE": [
2025
+ 1,
2026
+ [
2027
+ 4,
2028
+ "10"
2029
+ ]
2030
+ ]
2031
+ },
2032
+ "fields": {},
2033
+ "shadow": false,
2034
+ "topLevel": true,
2035
+ "x": 895,
2036
+ "y": 192
2037
+ },
2038
+ "looks_setsizeto": {
2039
+ "opcode": "looks_setsizeto",
2040
+ "next": null,
2041
+ "parent": null,
2042
+ "inputs": {
2043
+ "SIZE": [
2044
+ 1,
2045
+ [
2046
+ 4,
2047
+ "100"
2048
+ ]
2049
+ ]
2050
+ },
2051
+ "fields": {},
2052
+ "shadow": false,
2053
+ "topLevel": true,
2054
+ "x": 896,
2055
+ "y": 303
2056
+ },
2057
+ "looks_changeeffectby": {
2058
+ "opcode": "looks_changeeffectby",
2059
+ "next": null,
2060
+ "parent": null,
2061
+ "inputs": {
2062
+ "CHANGE": [
2063
+ 1,
2064
+ [
2065
+ 4,
2066
+ "25"
2067
+ ]
2068
+ ]
2069
+ },
2070
+ "fields": {
2071
+ "EFFECT": [
2072
+ "COLOR",
2073
+ null
2074
+ ]
2075
+ },
2076
+ "shadow": false,
2077
+ "topLevel": true,
2078
+ "x": 892,
2079
+ "y": 416
2080
+ },
2081
+ "looks_seteffectto": {
2082
+ "opcode": "looks_seteffectto",
2083
+ "next": null,
2084
+ "parent": null,
2085
+ "inputs": {
2086
+ "VALUE": [
2087
+ 1,
2088
+ [
2089
+ 4,
2090
+ "0"
2091
+ ]
2092
+ ]
2093
+ },
2094
+ "fields": {
2095
+ "EFFECT": [
2096
+ "COLOR",
2097
+ null
2098
+ ]
2099
+ },
2100
+ "shadow": false,
2101
+ "topLevel": true,
2102
+ "x": 902,
2103
+ "y": 527
2104
+ },
2105
+ "looks_cleargraphiceffects": {
2106
+ "opcode": "looks_cleargraphiceffects",
2107
+ "next": null,
2108
+ "parent": null,
2109
+ "inputs": {},
2110
+ "fields": {},
2111
+ "shadow": false,
2112
+ "topLevel": true,
2113
+ "x": 902,
2114
+ "y": 638
2115
+ },
2116
+ "looks_show": {
2117
+ "opcode": "looks_show",
2118
+ "next": null,
2119
+ "parent": null,
2120
+ "inputs": {},
2121
+ "fields": {},
2122
+ "shadow": false,
2123
+ "topLevel": true,
2124
+ "x": 908,
2125
+ "y": 758
2126
+ },
2127
+ "looks_hide": {
2128
+ "opcode": "looks_hide",
2129
+ "next": null,
2130
+ "parent": null,
2131
+ "inputs": {},
2132
+ "fields": {},
2133
+ "shadow": false,
2134
+ "topLevel": true,
2135
+ "x": 455,
2136
+ "y": 861
2137
+ },
2138
+ "looks_gotofrontback": {
2139
+ "opcode": "looks_gotofrontback",
2140
+ "next": null,
2141
+ "parent": null,
2142
+ "inputs": {},
2143
+ "fields": {
2144
+ "FRONT_BACK": [
2145
+ "front",
2146
+ null
2147
+ ]
2148
+ },
2149
+ "shadow": false,
2150
+ "topLevel": true,
2151
+ "x": 853,
2152
+ "y": 878
2153
+ },
2154
+ "looks_goforwardbackwardlayers": {
2155
+ "opcode": "looks_goforwardbackwardlayers",
2156
+ "next": null,
2157
+ "parent": null,
2158
+ "inputs": {
2159
+ "NUM": [
2160
+ 1,
2161
+ [
2162
+ 7,
2163
+ "1"
2164
+ ]
2165
+ ]
2166
+ },
2167
+ "fields": {
2168
+ "FORWARD_BACKWARD": [
2169
+ "forward",
2170
+ null
2171
+ ]
2172
+ },
2173
+ "shadow": false,
2174
+ "topLevel": true,
2175
+ "x": 851,
2176
+ "y": 999
2177
+ },
2178
+ "looks_costumenumbername": {
2179
+ "opcode": "looks_costumenumbername",
2180
+ "next": null,
2181
+ "parent": null,
2182
+ "inputs": {},
2183
+ "fields": {
2184
+ "NUMBER_NAME": [
2185
+ "number",
2186
+ null
2187
+ ]
2188
+ },
2189
+ "shadow": false,
2190
+ "topLevel": true,
2191
+ "x": 458,
2192
+ "y": 1007
2193
+ },
2194
+ "looks_backdropnumbername": {
2195
+ "opcode": "looks_backdropnumbername",
2196
+ "next": null,
2197
+ "parent": null,
2198
+ "inputs": {},
2199
+ "fields": {
2200
+ "NUMBER_NAME": [
2201
+ "number",
2202
+ null
2203
+ ]
2204
+ },
2205
+ "shadow": false,
2206
+ "topLevel": true,
2207
+ "x": 1242,
2208
+ "y": 753
2209
+ },
2210
+ "looks_size": {
2211
+ "opcode": "looks_size",
2212
+ "next": null,
2213
+ "parent": null,
2214
+ "inputs": {},
2215
+ "fields": {},
2216
+ "shadow": false,
2217
+ "topLevel": true,
2218
+ "x": 1249,
2219
+ "y": 876
2220
+ }
2221
+ }
blocks/boolean_blocks.json ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "block_category": "Boolean Blocks",
3
+ "description": "Boolean blocks are hexagonal in shape. They represent conditions that evaluate to either 'true' or 'false' and are typically used as inputs for control flow blocks.",
4
+ "blocks": [
5
+ {
6
+ "block_name": "<() < ()>",
7
+ "block_type": "operator",
8
+ "op_code": "operator_lt",
9
+ "block_shape": "Boolean Block",
10
+ "functionality": "Checks if the first value is less than the second.",
11
+ "inputs": [
12
+ {"name": "OPERAND1", "type": "any"},
13
+ {"name": "OPERAND2", "type": "any"}
14
+ ],
15
+ "example_standalone": "<(score) < (10)>",
16
+ "example_with_other_blocks": [
17
+ {
18
+ "script": "if <(score) < (10)> then\n say [Keep trying!] \nend",
19
+ "explanation": "This script causes the sprite to say 'Keep trying!' if the 'score' variable is less than 10."
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "block_name": "<() = ()>",
25
+ "block_type": "operator",
26
+ "op_code": "operator_equals",
27
+ "block_shape": "Boolean Block",
28
+ "functionality": "Checks if two values are equal.",
29
+ "inputs": [
30
+ {"name": "OPERAND1", "type": "any"},
31
+ {"name": "OPERAND2", "type": "any"}
32
+ ],
33
+ "example_standalone": "<(answer) = (5)>",
34
+ "example_with_other_blocks": [
35
+ {
36
+ "script": "if <(answer) = (5)> then\n say [Correct!] \nend",
37
+ "explanation": "This script makes the sprite say 'Correct!' if the value of the 'answer' variable is exactly 5."
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "block_name": "<() > ()>",
43
+ "block_type": "operator",
44
+ "op_code": "operator_gt",
45
+ "block_shape": "Boolean Block",
46
+ "functionality": "Checks if the first value is greater than the second.",
47
+ "inputs": [
48
+ {"name": "OPERAND1", "type": "any"},
49
+ {"name": "OPERAND2", "type": "any"}
50
+ ],
51
+ "example_standalone": "<([health v]) > (0)>",
52
+ "example_with_other_blocks": [
53
+ {
54
+ "script": "if <([health v]) > (0)> then\n move (10) steps\nelse\n stop [all v]\nend",
55
+ "explanation": "This script moves the sprite if its 'health' is greater than 0; otherwise, it stops all scripts."
56
+ }
57
+ ]
58
+ },
59
+ {
60
+ "block_name": "<<> and <>>",
61
+ "block_type": "operator",
62
+ "op_code": "operator_and",
63
+ "block_shape": "Boolean Block",
64
+ "functionality": "Returns 'true' if both provided Boolean conditions are 'true'.",
65
+ "inputs": [
66
+ {"name": "OPERAND1", "type": "boolean"},
67
+ {"name": "OPERAND2", "type": "boolean"}
68
+ ],
69
+ "example_standalone": "<<mouse down?> and <touching [mouse-pointer]?> >",
70
+ "example_with_other_blocks": [
71
+ {
72
+ "script": "if <<mouse down?> and <touching [mouse-pointer]?> > then\n say [You're clicking me!]\nend",
73
+ "explanation": "This script makes the sprite say 'You're clicking me!' only if the mouse button is pressed AND the mouse pointer is touching the sprite."
74
+ }
75
+ ]
76
+ },
77
+ {
78
+ "block_name": "<<> or <>>",
79
+ "block_type": "operator",
80
+ "op_code": "operator_or",
81
+ "block_shape": "Boolean Block",
82
+ "functionality": "Returns 'true' if at least one of the provided Boolean conditions is 'true'.",
83
+ "inputs": [
84
+ {"name": "OPERAND1", "type": "boolean"},
85
+ {"name": "OPERAND2", "type": "boolean"}
86
+ ],
87
+ "example_standalone": "<<key [left arrow v] pressed?> or <key [a v] pressed?>>",
88
+ "example_with_other_blocks": [
89
+ {
90
+ "script": "if <<key [left arrow v] pressed?> or <key [a v] pressed?>> then\n change x by (-10)\nend",
91
+ "explanation": "This script moves the sprite left if either the left arrow key OR the 'a' key is pressed."
92
+ }
93
+ ]
94
+ },
95
+ {
96
+ "block_name": "<not <>>",
97
+ "block_type": "operator",
98
+ "op_code": "operator_not",
99
+ "block_shape": "Boolean Block",
100
+ "functionality": "Returns 'true' if the provided Boolean condition is 'false', and 'false' if it is 'true'.",
101
+ "inputs": [
102
+ {"name": "OPERAND", "type": "boolean"}
103
+ ],
104
+ "example_standalone": "<not <mouse down?>>",
105
+ "example_with_other_blocks": [
106
+ {
107
+ "script": "if <not <touching [Sprite2 v]?>> then\n say [I'm safe!]\nend",
108
+ "explanation": "This script makes the sprite say 'I'm safe!' if it is NOT touching 'Sprite2'."
109
+ }
110
+ ]
111
+ },
112
+ {
113
+ "block_name": "<() contains ()?>",
114
+ "block_type": "operator",
115
+ "op_code": "operator_contains",
116
+ "block_shape": "Boolean Block",
117
+ "functionality": "Checks if one string contains another string.",
118
+ "inputs": [
119
+ {"name": "STRING1", "type": "string"},
120
+ {"name": "STRING2", "type": "string"}
121
+ ],
122
+ "example_standalone": "<[apple v] contains [a v]?>",
123
+ "example_with_other_blocks": [
124
+ {
125
+ "script": "if <[answer] contains [yes]?> then\n say [Great!]\nend",
126
+ "explanation": "This script makes the sprite say 'Great!' if the 'answer' variable contains the substring 'yes'."
127
+ }
128
+ ]
129
+ },
130
+ {
131
+ "block_name": "<touching [edge v]?>",
132
+ "block_type": "Sensing",
133
+ "op_code": "sensing_touchingobject",
134
+ "block_shape": "Boolean Block",
135
+ "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
136
+ "inputs": [
137
+ {"name": "TOUCHINGOBJECTMENU", "type": "dropdown", "options": ["mouse-pointer", "edge", "Sprite1", "..." ]}
138
+ ],
139
+ "example_standalone": "<touching [edge v]?>",
140
+ "example_with_other_blocks": [
141
+ {
142
+ "script": "if <touching [Sprite v]?> then\n broadcast [Game Over v] \nend",
143
+ "explanation": "This script makes the broadcast message 'Game Over' in script if it comes into contact with the sprite."
144
+ },
145
+ {
146
+ "script": "if <touching [edge v]?> then\n bounce off edge\nend",
147
+ "explanation": "This script makes the sprite reverse direction if it comes into contact with the edge of the stage."
148
+ }
149
+ ]
150
+ },
151
+ {
152
+ "block_name": "<touching color ()?>",
153
+ "block_type": "Sensing",
154
+ "op_code": "sensing_touchingcolor",
155
+ "block_shape": "Boolean Block",
156
+ "functionality": "Checks whether its sprite is touching a specified color.",
157
+ "inputs": [
158
+ {"name": "COLOR", "type": "color"}
159
+ ],
160
+ "example_standalone": "<touching color [#FF0000]?>",
161
+ "example_with_other_blocks": [
162
+ {
163
+ "script": "if <touching color [#FF0000]?> then\n change [health v] by (-1)\nend",
164
+ "explanation": "This script decreases the 'health' variable by 1 if the sprite touches any red color on the stage."
165
+ }
166
+ ]
167
+ },
168
+ {
169
+ "block_name": "<color () is touching ()?>",
170
+ "block_type": "Sensing",
171
+ "op_code": "sensing_coloristouchingcolor",
172
+ "block_shape": "Boolean Block",
173
+ "functionality": "Checks whether a specific color on its sprite is touching another specified color on the stage or another sprite.",
174
+ "inputs": [
175
+ {"name": "COLOR1", "type": "color"},
176
+ {"name": "COLOR2", "type": "color"}
177
+ ],
178
+ "example_standalone": "<color [#00FF00] is touching [#FF0000]?>",
179
+ "example_with_other_blocks": [
180
+ {
181
+ "script": "if <color [#00FF00] is touching [#FF0000]?> then\n say [Collision!]\nend",
182
+ "explanation": "This script makes the sprite say 'Collision!' if a green part of the sprite touches a red color elsewhere in the project."
183
+ }
184
+ ]
185
+ },
186
+ {
187
+ "block_name": "<key () pressed?>",
188
+ "block_type": "Sensing",
189
+ "op_code": "sensing_keypressed",
190
+ "block_shape": "Boolean Block",
191
+ "functionality": "Checks if a specified keyboard key is currently being pressed.",
192
+ "inputs": [
193
+ {"name": "KEY_OPTION", "type": "dropdown",
194
+ "options": [
195
+ "space",
196
+ "up arrow",
197
+ "down arrow",
198
+ "right arrow",
199
+ "left arrow",
200
+ "any",
201
+ "a",
202
+ "b",
203
+ "c",
204
+ "d",
205
+ "e",
206
+ "f",
207
+ "g",
208
+ "h",
209
+ "i",
210
+ "j",
211
+ "k",
212
+ "l",
213
+ "m",
214
+ "n",
215
+ "o",
216
+ "p",
217
+ "q",
218
+ "r",
219
+ "s",
220
+ "t",
221
+ "u",
222
+ "v",
223
+ "w",
224
+ "x",
225
+ "y",
226
+ "z",
227
+ "0",
228
+ "1",
229
+ "2",
230
+ "3",
231
+ "4",
232
+ "5",
233
+ "6",
234
+ "7",
235
+ "8",
236
+ "9"
237
+ ]}
238
+ ],
239
+ "example_standalone": "<key [space v] pressed?>",
240
+ "example_with_other_blocks": [
241
+ {
242
+ "script": "forever\n if <key [space v] pressed?> then\n broadcast [shoot v]\n end\nend",
243
+ "explanation": "This script continuously checks if the space key is pressed and, if so, sends a 'shoot' broadcast."
244
+ }
245
+ ]
246
+ },
247
+ {
248
+ "block_name": "<mouse down?>",
249
+ "block_type": "Sensing",
250
+ "op_code": "sensing_mousedown",
251
+ "block_shape": "Boolean Block",
252
+ "functionality": "Checks if the computer mouse's primary button is being clicked while the cursor is over the stage.",
253
+ "inputs": null,
254
+ "example_standalone": "<mouse down?>",
255
+ "example_with_other_blocks": [
256
+ {
257
+ "script": "if <mouse down?> then\n go to mouse-pointer\nend",
258
+ "explanation": "This script makes the sprite follow the mouse pointer only when the mouse button is held down."
259
+ }
260
+ ]
261
+ },
262
+ {
263
+ "block_name": "<[my list v] contains ()?>",
264
+ "block_type": "Data",
265
+ "op_code": "data_listcontainsitem",
266
+ "block_shape": "Boolean Block",
267
+ "functionality": "Checks if a list includes a specific item.",
268
+ "inputs": [
269
+ {"name": "LIST", "type": "dropdown"},
270
+ {"name": "ITEM", "type": "any"}
271
+ ],
272
+ "example_standalone": "<[inventory v] contains [key]?>",
273
+ "example_with_other_blocks": [
274
+ {
275
+ "script": "if <[inventory v] contains [key]?> then\n say [You have the key!]\nend",
276
+ "explanation": "This script makes the sprite say 'You have the key!' if the 'inventory' list contains the item 'key'."
277
+ }
278
+ ]
279
+ }
280
+ ]
281
+ }
blocks/c_blocks.json ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "block_category": "C Blocks",
3
+ "description": "C blocks are shaped like the letter 'C'. They are used to loop or conditionally execute blocks that are placed within their opening, managing the flow of scripts.",
4
+ "blocks": [
5
+ {
6
+ "block_name": "repeat ()",
7
+ "block_type": "Control",
8
+ "block_shape": "C-Block",
9
+ "op_code": "control_repeat",
10
+ "functionality": "Repeats the blocks inside it a specified number of times.",
11
+ "inputs": [
12
+ {
13
+ "name": "times",
14
+ "type": "number"
15
+ }
16
+ ],
17
+ "example_standalone": "repeat (10)",
18
+ "example_with_other_blocks": [
19
+ {
20
+ "script": "when [space v] key pressed\n repeat (10)\n move (10) steps\n wait (0.1) seconds\n end",
21
+ "explanation": "This script makes the sprite move 10 steps Ten times, with a short pause after each movement on spacebar pressed."
22
+ },
23
+ {
24
+ "script": "when [up arrow v] key pressed\n repeat (10)\n change y by (10)\n wait (0.1) seconds\n change y by (10)\n end",
25
+ "explanation": "This script makes the sprite jump, with a short pause after each movement on up arrow pressed."
26
+ }
27
+ ]
28
+ },
29
+ {
30
+ "block_name": "forever",
31
+ "block_type": "Control",
32
+ "block_shape": "C-Block",
33
+ "op_code": "control_forever",
34
+ "functionality": "Continuously runs the blocks inside it.",
35
+ "inputs": null,
36
+ "example_standalone": "forever",
37
+ "example_with_other_blocks": [
38
+ {
39
+ "script": "when green flag clicked\n forever\n move (5) steps\n if on edge, bounce\n end",
40
+ "explanation": "This script makes the sprite move endlessly and bounce off the edges of the stage, creating continuous motion."
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ "block_name": "if <> then",
46
+ "block_type": "Control",
47
+ "block_shape": "C-Block",
48
+ "op_code": "control_if",
49
+ "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
50
+ "inputs": [
51
+ {
52
+ "name": "condition",
53
+ "type": "boolean"
54
+ }
55
+ ],
56
+ "example_standalone": "if <touching [mouse-pointer v]?> then",
57
+ "example_with_other_blocks": [
58
+ {
59
+ "script": "forever\n if <touching [color (red) v]?> then\n stop [this script v]\n end",
60
+ "explanation": "This script continuously checks if the sprite is touching a red color, and if so, it stops the current script."
61
+ }
62
+ ]
63
+ },
64
+ {
65
+ "block_name": "if <> then else",
66
+ "block_type": "Control",
67
+ "block_shape": "C-Block",
68
+ "op_code": "control_if_else",
69
+ "functionality": "Executes one set of blocks if the specified boolean condition is true, and a different set of blocks if the condition is false. [NOTE: it takes boolean blocks as input]",
70
+ "inputs": [
71
+ {
72
+ "name": "condition",
73
+ "type": "boolean"
74
+ }
75
+ ],
76
+ "example_standalone": "if <score > (10)> then else",
77
+ "example_with_other_blocks": [
78
+ {
79
+ "script": "if <(score) > (10)> then\n say [You win!] for (2) seconds\nelse\n say [Keep trying!] for (2) seconds\nend",
80
+ "explanation": "This script checks the 'score'. If the score is greater than 10, it says 'You win!'; otherwise, it says 'Keep trying!'."
81
+ }
82
+ ]
83
+ },
84
+ {
85
+ "block_name": "repeat until <>",
86
+ "block_type": "Control",
87
+ "block_shape": "C-Block",
88
+ "op_code": "control_repeat_until",
89
+ "functionality": "Repeats the blocks inside it until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
90
+ "inputs": [
91
+ {
92
+ "name": "condition",
93
+ "type": "boolean"
94
+ }
95
+ ],
96
+ "example_standalone": "repeat until <touching [edge v]?>",
97
+ "example_with_other_blocks": [
98
+ {
99
+ "script": "repeat until <touching [edge v]?>\n move (5) steps\nend",
100
+ "explanation": "This script makes the sprite move 5 steps repeatedly until it touches the edge of the stage."
101
+ }
102
+ ]
103
+ }
104
+ ]
105
+ }
blocks/cap_blocks.json ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "block_category": "Cap Blocks",
3
+ "description": "Cap blocks have a notch at the top and a flat bottom. They signify the end of a script, preventing any further blocks from being placed below them, and are used to terminate scripts or specific actions.",
4
+ "blocks": [
5
+ {
6
+ "block_name": "stop [v]",
7
+ "block_type": "Control",
8
+ "block_shape": "Cap Block (dynamic: can be Stack)",
9
+ "op_code": "control_stop",
10
+ "functionality": "Halts all scripts, only the current script, or other scripts within the same sprite. Its shape can dynamically change based on the selected option.",
11
+ "inputs": [
12
+ {"name": "option", "type": "dropdown", "options": ["all"]}
13
+ ],
14
+ "example_standalone": "stop [all v]",
15
+ "example_with_other_blocks": [
16
+ {
17
+ "script": "if <(health) = (0)> then\n stop [all v]\nend",
18
+ "explanation": "This script stops all running scripts in the project if the 'health' variable reaches 0, typically signifying a game over condition. [9, 15]"
19
+ }
20
+ ]
21
+ },
22
+ {
23
+ "block_name": "delete this clone",
24
+ "block_type": "Control",
25
+ "block_shape": "Cap Block",
26
+ "op_code": "control_delete_this_clone",
27
+ "functionality": "Removes the clone that is executing it from the stage.",
28
+ "inputs":null,
29
+ "example_standalone": "delete this clone",
30
+ "example_with_other_blocks": [
31
+ {
32
+ "script": "when I start as a clone\n wait until <touching [edge v]?>\n delete this clone\nend",
33
+ "explanation": "This script, run by a clone, causes the clone to disappear from the stage once it touches the edge. [1]"
34
+ }
35
+ ]
36
+ },
37
+ {
38
+ "block_name": "forever",
39
+ "block_type": "Control",
40
+ "block_shape": "Cap Block",
41
+ "op_code": "control_forever",
42
+ "functionality": "Continuously runs the blocks inside it.",
43
+ "inputs": null,
44
+ "example_standalone": "forever",
45
+ "example_with_other_blocks": [
46
+ {
47
+ "script": "when green flag clicked\n forever\n move (5) steps\n if on edge, bounce\n end",
48
+ "explanation": "This script makes the sprite move endlessly and bounce off the edges of the stage, creating continuous motion."
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }
blocks/classwise_blocks/control_block.json ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "control_wait": {
3
+ "opcode": "control_wait",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "DURATION": [
8
+ 1,
9
+ [
10
+ 5,
11
+ "1"
12
+ ]
13
+ ]
14
+ },
15
+ "fields": {},
16
+ "shadow": false,
17
+ "topLevel": true,
18
+ "x": 337,
19
+ "y": 129
20
+ },
21
+ "control_repeat": {
22
+ "opcode": "control_repeat",
23
+ "next": null,
24
+ "parent": null,
25
+ "inputs": {
26
+ "TIMES": [
27
+ 1,
28
+ [
29
+ 6,
30
+ "10"
31
+ ]
32
+ ]
33
+ },
34
+ "fields": {},
35
+ "shadow": false,
36
+ "topLevel": true,
37
+ "x": 348,
38
+ "y": 265
39
+ },
40
+ "control_forever": {
41
+ "opcode": "control_forever",
42
+ "next": null,
43
+ "parent": null,
44
+ "inputs": {},
45
+ "fields": {},
46
+ "shadow": false,
47
+ "topLevel": true,
48
+ "x": 334,
49
+ "y": 439
50
+ },
51
+ "control_if": {
52
+ "opcode": "control_if",
53
+ "next": null,
54
+ "parent": null,
55
+ "inputs": {},
56
+ "fields": {},
57
+ "shadow": false,
58
+ "topLevel": true,
59
+ "x": 331,
60
+ "y": 597
61
+ },
62
+ "control_if_else": {
63
+ "opcode": "control_if_else",
64
+ "next": null,
65
+ "parent": null,
66
+ "inputs": {},
67
+ "fields": {},
68
+ "shadow": false,
69
+ "topLevel": true,
70
+ "x": 335,
71
+ "y": 779
72
+ },
73
+ "control_wait_until": {
74
+ "opcode": "control_wait_until",
75
+ "next": null,
76
+ "parent": null,
77
+ "inputs": {},
78
+ "fields": {},
79
+ "shadow": false,
80
+ "topLevel": true,
81
+ "x": 676,
82
+ "y": 285
83
+ },
84
+ "control_repeat_until": {
85
+ "opcode": "control_repeat_until",
86
+ "next": null,
87
+ "parent": null,
88
+ "inputs": {},
89
+ "fields": {},
90
+ "shadow": false,
91
+ "topLevel": true,
92
+ "x": 692,
93
+ "y": 381
94
+ },
95
+ "control_stop": {
96
+ "opcode": "control_stop",
97
+ "next": null,
98
+ "parent": null,
99
+ "inputs": {},
100
+ "fields": {
101
+ "STOP_OPTION": [
102
+ "all",
103
+ null
104
+ ]
105
+ },
106
+ "shadow": false,
107
+ "topLevel": true,
108
+ "x": 708,
109
+ "y": 545,
110
+ "mutation": {
111
+ "tagName": "mutation",
112
+ "children": [],
113
+ "hasnext": "false"
114
+ }
115
+ },
116
+ "control_start_as_clone": {
117
+ "opcode": "control_start_as_clone",
118
+ "next": null,
119
+ "parent": null,
120
+ "inputs": {},
121
+ "fields": {},
122
+ "shadow": false,
123
+ "topLevel": true,
124
+ "x": 665,
125
+ "y": 672
126
+ },
127
+ "control_create_clone_of": {
128
+ "opcode": "control_create_clone_of",
129
+ "next": null,
130
+ "parent": null,
131
+ "inputs": {
132
+ "CLONE_OPTION": [
133
+ 1,
134
+ "control_create_clone_of_menu"
135
+ ]
136
+ },
137
+ "fields": {},
138
+ "shadow": false,
139
+ "topLevel": true,
140
+ "x": 648,
141
+ "y": 797
142
+ },
143
+ "control_create_clone_of_menu": {
144
+ "opcode": "control_create_clone_of_menu",
145
+ "next": null,
146
+ "parent": "control_create_clone_of",
147
+ "inputs": {},
148
+ "fields": {
149
+ "CLONE_OPTION": [
150
+ "_myself_",
151
+ null
152
+ ]
153
+ },
154
+ "shadow": true,
155
+ "topLevel": false
156
+ },
157
+ "control_delete_this_clone": {
158
+ "opcode": "control_delete_this_clone",
159
+ "next": null,
160
+ "parent": null,
161
+ "inputs": {},
162
+ "fields": {},
163
+ "shadow": false,
164
+ "topLevel": true,
165
+ "x": 642,
166
+ "y": 914
167
+ }
168
+ }
blocks/classwise_blocks/data_block.json ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "data_setvariableto": {
3
+ "opcode": "data_setvariableto",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "VALUE": [
8
+ 1,
9
+ [
10
+ 10,
11
+ "0"
12
+ ]
13
+ ]
14
+ },
15
+ "fields": {
16
+ "VARIABLE": [
17
+ "my variable",
18
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
19
+ ]
20
+ },
21
+ "shadow": false,
22
+ "topLevel": true,
23
+ "x": 348,
24
+ "y": 241
25
+ },
26
+ "data_changevariableby": {
27
+ "opcode": "data_changevariableby",
28
+ "next": null,
29
+ "parent": null,
30
+ "inputs": {
31
+ "VALUE": [
32
+ 1,
33
+ [
34
+ 4,
35
+ "1"
36
+ ]
37
+ ]
38
+ },
39
+ "fields": {
40
+ "VARIABLE": [
41
+ "my variable",
42
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
43
+ ]
44
+ },
45
+ "shadow": false,
46
+ "topLevel": true,
47
+ "x": 313,
48
+ "y": 363
49
+ },
50
+ "data_showvariable": {
51
+ "opcode": "data_showvariable",
52
+ "next": null,
53
+ "parent": null,
54
+ "inputs": {},
55
+ "fields": {
56
+ "VARIABLE": [
57
+ "my variable",
58
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
59
+ ]
60
+ },
61
+ "shadow": false,
62
+ "topLevel": true,
63
+ "x": 415,
64
+ "y": 473
65
+ },
66
+ "data_hidevariable": {
67
+ "opcode": "data_hidevariable",
68
+ "next": null,
69
+ "parent": null,
70
+ "inputs": {},
71
+ "fields": {
72
+ "VARIABLE": [
73
+ "my variable",
74
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
75
+ ]
76
+ },
77
+ "shadow": false,
78
+ "topLevel": true,
79
+ "x": 319,
80
+ "y": 587
81
+ },
82
+ "data_addtolist": {
83
+ "opcode": "data_addtolist",
84
+ "next": null,
85
+ "parent": null,
86
+ "inputs": {
87
+ "ITEM": [
88
+ 1,
89
+ [
90
+ 10,
91
+ "thing"
92
+ ]
93
+ ]
94
+ },
95
+ "fields": {
96
+ "LIST": [
97
+ "MY_LIST",
98
+ "o6`kIhtT{xWH+rX(5d,A"
99
+ ]
100
+ },
101
+ "shadow": false,
102
+ "topLevel": true,
103
+ "x": 385,
104
+ "y": 109
105
+ },
106
+ "data_deleteoflist": {
107
+ "opcode": "data_deleteoflist",
108
+ "next": null,
109
+ "parent": null,
110
+ "inputs": {
111
+ "INDEX": [
112
+ 1,
113
+ [
114
+ 7,
115
+ "1"
116
+ ]
117
+ ]
118
+ },
119
+ "fields": {
120
+ "LIST": [
121
+ "MY_LIST",
122
+ "o6`kIhtT{xWH+rX(5d,A"
123
+ ]
124
+ },
125
+ "shadow": false,
126
+ "topLevel": true,
127
+ "x": 384,
128
+ "y": 244
129
+ },
130
+ "data_deletealloflist": {
131
+ "opcode": "data_deletealloflist",
132
+ "next": null,
133
+ "parent": null,
134
+ "inputs": {},
135
+ "fields": {
136
+ "LIST": [
137
+ "MY_LIST",
138
+ "o6`kIhtT{xWH+rX(5d,A"
139
+ ]
140
+ },
141
+ "shadow": false,
142
+ "topLevel": true,
143
+ "x": 387,
144
+ "y": 374
145
+ },
146
+ "data_insertatlist": {
147
+ "opcode": "data_insertatlist",
148
+ "next": null,
149
+ "parent": null,
150
+ "inputs": {
151
+ "ITEM": [
152
+ 1,
153
+ [
154
+ 10,
155
+ "thing"
156
+ ]
157
+ ],
158
+ "INDEX": [
159
+ 1,
160
+ [
161
+ 7,
162
+ "1"
163
+ ]
164
+ ]
165
+ },
166
+ "fields": {
167
+ "LIST": [
168
+ "MY_LIST",
169
+ "o6`kIhtT{xWH+rX(5d,A"
170
+ ]
171
+ },
172
+ "shadow": false,
173
+ "topLevel": true,
174
+ "x": 366,
175
+ "y": 527
176
+ },
177
+ "data_replaceitemoflist": {
178
+ "opcode": "data_replaceitemoflist",
179
+ "next": null,
180
+ "parent": null,
181
+ "inputs": {
182
+ "INDEX": [
183
+ 1,
184
+ [
185
+ 7,
186
+ "1"
187
+ ]
188
+ ],
189
+ "ITEM": [
190
+ 1,
191
+ [
192
+ 10,
193
+ "thing"
194
+ ]
195
+ ]
196
+ },
197
+ "fields": {
198
+ "LIST": [
199
+ "MY_LIST",
200
+ "o6`kIhtT{xWH+rX(5d,A"
201
+ ]
202
+ },
203
+ "shadow": false,
204
+ "topLevel": true,
205
+ "x": 365,
206
+ "y": 657
207
+ },
208
+ "data_itemoflist": {
209
+ "opcode": "data_itemoflist",
210
+ "next": null,
211
+ "parent": null,
212
+ "inputs": {
213
+ "INDEX": [
214
+ 1,
215
+ [
216
+ 7,
217
+ "1"
218
+ ]
219
+ ]
220
+ },
221
+ "fields": {
222
+ "LIST": [
223
+ "MY_LIST",
224
+ "o6`kIhtT{xWH+rX(5d,A"
225
+ ]
226
+ },
227
+ "shadow": false,
228
+ "topLevel": true,
229
+ "x": 862,
230
+ "y": 117
231
+ },
232
+ "data_itemnumoflist": {
233
+ "opcode": "data_itemnumoflist",
234
+ "next": null,
235
+ "parent": null,
236
+ "inputs": {
237
+ "ITEM": [
238
+ 1,
239
+ [
240
+ 10,
241
+ "thing"
242
+ ]
243
+ ]
244
+ },
245
+ "fields": {
246
+ "LIST": [
247
+ "MY_LIST",
248
+ "o6`kIhtT{xWH+rX(5d,A"
249
+ ]
250
+ },
251
+ "shadow": false,
252
+ "topLevel": true,
253
+ "x": 883,
254
+ "y": 238
255
+ },
256
+ "data_lengthoflist": {
257
+ "opcode": "data_lengthoflist",
258
+ "next": null,
259
+ "parent": null,
260
+ "inputs": {},
261
+ "fields": {
262
+ "LIST": [
263
+ "MY_LIST",
264
+ "o6`kIhtT{xWH+rX(5d,A"
265
+ ]
266
+ },
267
+ "shadow": false,
268
+ "topLevel": true,
269
+ "x": 876,
270
+ "y": 342
271
+ },
272
+ "data_listcontainsitem": {
273
+ "opcode": "data_listcontainsitem",
274
+ "next": null,
275
+ "parent": null,
276
+ "inputs": {
277
+ "ITEM": [
278
+ 1,
279
+ [
280
+ 10,
281
+ "thing"
282
+ ]
283
+ ]
284
+ },
285
+ "fields": {
286
+ "LIST": [
287
+ "MY_LIST",
288
+ "o6`kIhtT{xWH+rX(5d,A"
289
+ ]
290
+ },
291
+ "shadow": false,
292
+ "topLevel": true,
293
+ "x": 871,
294
+ "y": 463
295
+ },
296
+ "data_showlist": {
297
+ "opcode": "data_showlist",
298
+ "next": null,
299
+ "parent": null,
300
+ "inputs": {},
301
+ "fields": {
302
+ "LIST": [
303
+ "MY_LIST",
304
+ "o6`kIhtT{xWH+rX(5d,A"
305
+ ]
306
+ },
307
+ "shadow": false,
308
+ "topLevel": true,
309
+ "x": 931,
310
+ "y": 563
311
+ },
312
+ "data_hidelist": {
313
+ "opcode": "data_hidelist",
314
+ "next": null,
315
+ "parent": null,
316
+ "inputs": {},
317
+ "fields": {
318
+ "LIST": [
319
+ "MY_LIST",
320
+ "o6`kIhtT{xWH+rX(5d,A"
321
+ ]
322
+ },
323
+ "shadow": false,
324
+ "topLevel": true,
325
+ "x": 962,
326
+ "y": 716
327
+ }
328
+ }
blocks/classwise_blocks/event_block.json ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "event_whenflagclicked": {
3
+ "opcode": "event_whenflagclicked",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {},
7
+ "fields": {},
8
+ "shadow": false,
9
+ "topLevel": true,
10
+ "x": 166,
11
+ "y": -422
12
+ },
13
+ "event_whenkeypressed": {
14
+ "opcode": "event_whenkeypressed",
15
+ "next": null,
16
+ "parent": null,
17
+ "inputs": {},
18
+ "fields": {
19
+ "KEY_OPTION": [
20
+ "space",
21
+ null
22
+ ]
23
+ },
24
+ "shadow": false,
25
+ "topLevel": true,
26
+ "x": 151,
27
+ "y": -329
28
+ },
29
+ "event_whenthisspriteclicked": {
30
+ "opcode": "event_whenthisspriteclicked",
31
+ "next": null,
32
+ "parent": null,
33
+ "inputs": {},
34
+ "fields": {},
35
+ "shadow": false,
36
+ "topLevel": true,
37
+ "x": 156,
38
+ "y": -223
39
+ },
40
+ "event_whenbackdropswitchesto": {
41
+ "opcode": "event_whenbackdropswitchesto",
42
+ "next": null,
43
+ "parent": null,
44
+ "inputs": {},
45
+ "fields": {
46
+ "BACKDROP": [
47
+ "backdrop1",
48
+ null
49
+ ]
50
+ },
51
+ "shadow": false,
52
+ "topLevel": true,
53
+ "x": 148,
54
+ "y": -101
55
+ },
56
+ "event_whengreaterthan": {
57
+ "opcode": "event_whengreaterthan",
58
+ "next": null,
59
+ "parent": null,
60
+ "inputs": {
61
+ "VALUE": [
62
+ 1,
63
+ [
64
+ 4,
65
+ "10"
66
+ ]
67
+ ]
68
+ },
69
+ "fields": {
70
+ "WHENGREATERTHANMENU": [
71
+ "LOUDNESS",
72
+ null
73
+ ]
74
+ },
75
+ "shadow": false,
76
+ "topLevel": true,
77
+ "x": 150,
78
+ "y": 10
79
+ },
80
+ "event_whenbroadcastreceived": {
81
+ "opcode": "event_whenbroadcastreceived",
82
+ "next": null,
83
+ "parent": null,
84
+ "inputs": {},
85
+ "fields": {
86
+ "BROADCAST_OPTION": [
87
+ "message1",
88
+ "5O!nei;S$!c!=hCT}0:a"
89
+ ]
90
+ },
91
+ "shadow": false,
92
+ "topLevel": true,
93
+ "x": 141,
94
+ "y": 118
95
+ },
96
+ "event_broadcast": {
97
+ "opcode": "event_broadcast",
98
+ "next": null,
99
+ "parent": null,
100
+ "inputs": {
101
+ "BROADCAST_INPUT": [
102
+ 1,
103
+ [
104
+ 11,
105
+ "message1",
106
+ "5O!nei;S$!c!=hCT}0:a"
107
+ ]
108
+ ]
109
+ },
110
+ "fields": {},
111
+ "shadow": false,
112
+ "topLevel": true,
113
+ "x": 151,
114
+ "y": 229
115
+ },
116
+ "event_broadcastandwait": {
117
+ "opcode": "event_broadcastandwait",
118
+ "next": null,
119
+ "parent": null,
120
+ "inputs": {
121
+ "BROADCAST_INPUT": [
122
+ 1,
123
+ [
124
+ 11,
125
+ "message1",
126
+ "5O!nei;S$!c!=hCT}0:a"
127
+ ]
128
+ ]
129
+ },
130
+ "fields": {},
131
+ "shadow": false,
132
+ "topLevel": true,
133
+ "x": 157,
134
+ "y": 340
135
+ }
136
+ }
blocks/classwise_blocks/look_block.json ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "looks_sayforsecs": {
3
+ "opcode": "looks_sayforsecs",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "MESSAGE": [
8
+ 1,
9
+ [
10
+ 10,
11
+ "Hello!"
12
+ ]
13
+ ],
14
+ "SECS": [
15
+ 1,
16
+ [
17
+ 4,
18
+ "2"
19
+ ]
20
+ ]
21
+ },
22
+ "fields": {},
23
+ "shadow": false,
24
+ "topLevel": true,
25
+ "x": 408,
26
+ "y": 91
27
+ },
28
+ "looks_say": {
29
+ "opcode": "looks_say",
30
+ "next": null,
31
+ "parent": null,
32
+ "inputs": {
33
+ "MESSAGE": [
34
+ 1,
35
+ [
36
+ 10,
37
+ "Hello!"
38
+ ]
39
+ ]
40
+ },
41
+ "fields": {},
42
+ "shadow": false,
43
+ "topLevel": true,
44
+ "x": 413,
45
+ "y": 213
46
+ },
47
+ "looks_thinkforsecs": {
48
+ "opcode": "looks_thinkforsecs",
49
+ "next": null,
50
+ "parent": null,
51
+ "inputs": {
52
+ "MESSAGE": [
53
+ 1,
54
+ [
55
+ 10,
56
+ "Hmm..."
57
+ ]
58
+ ],
59
+ "SECS": [
60
+ 1,
61
+ [
62
+ 4,
63
+ "2"
64
+ ]
65
+ ]
66
+ },
67
+ "fields": {},
68
+ "shadow": false,
69
+ "topLevel": true,
70
+ "x": 413,
71
+ "y": 317
72
+ },
73
+ "looks_think": {
74
+ "opcode": "looks_think",
75
+ "next": null,
76
+ "parent": null,
77
+ "inputs": {
78
+ "MESSAGE": [
79
+ 1,
80
+ [
81
+ 10,
82
+ "Hmm..."
83
+ ]
84
+ ]
85
+ },
86
+ "fields": {},
87
+ "shadow": false,
88
+ "topLevel": true,
89
+ "x": 412,
90
+ "y": 432
91
+ },
92
+ "looks_switchcostumeto": {
93
+ "opcode": "looks_switchcostumeto",
94
+ "next": null,
95
+ "parent": null,
96
+ "inputs": {
97
+ "COSTUME": [
98
+ 1,
99
+ "looks_costume"
100
+ ]
101
+ },
102
+ "fields": {},
103
+ "shadow": false,
104
+ "topLevel": true,
105
+ "x": 411,
106
+ "y": 555
107
+ },
108
+ "looks_costume": {
109
+ "opcode": "looks_costume",
110
+ "next": null,
111
+ "parent": "looks_switchcostumeto",
112
+ "inputs": {},
113
+ "fields": {
114
+ "COSTUME": [
115
+ "costume2",
116
+ null
117
+ ]
118
+ },
119
+ "shadow": true,
120
+ "topLevel": false
121
+ },
122
+ "looks_nextcostume": {
123
+ "opcode": "looks_nextcostume",
124
+ "next": null,
125
+ "parent": null,
126
+ "inputs": {},
127
+ "fields": {},
128
+ "shadow": false,
129
+ "topLevel": true,
130
+ "x": 419,
131
+ "y": 687
132
+ },
133
+ "looks_switchbackdropto": {
134
+ "opcode": "looks_switchbackdropto",
135
+ "next": null,
136
+ "parent": null,
137
+ "inputs": {
138
+ "BACKDROP": [
139
+ 1,
140
+ "looks_backdrops"
141
+ ]
142
+ },
143
+ "fields": {},
144
+ "shadow": false,
145
+ "topLevel": true,
146
+ "x": 901,
147
+ "y": 91
148
+ },
149
+ "looks_backdrops": {
150
+ "opcode": "looks_backdrops",
151
+ "next": null,
152
+ "parent": "looks_switchbackdropto",
153
+ "inputs": {},
154
+ "fields": {
155
+ "BACKDROP": [
156
+ "backdrop1",
157
+ null
158
+ ]
159
+ },
160
+ "shadow": true,
161
+ "topLevel": false
162
+ },
163
+ "looks_changesizeby": {
164
+ "opcode": "looks_changesizeby",
165
+ "next": null,
166
+ "parent": null,
167
+ "inputs": {
168
+ "CHANGE": [
169
+ 1,
170
+ [
171
+ 4,
172
+ "10"
173
+ ]
174
+ ]
175
+ },
176
+ "fields": {},
177
+ "shadow": false,
178
+ "topLevel": true,
179
+ "x": 895,
180
+ "y": 192
181
+ },
182
+ "looks_setsizeto": {
183
+ "opcode": "looks_setsizeto",
184
+ "next": null,
185
+ "parent": null,
186
+ "inputs": {
187
+ "SIZE": [
188
+ 1,
189
+ [
190
+ 4,
191
+ "100"
192
+ ]
193
+ ]
194
+ },
195
+ "fields": {},
196
+ "shadow": false,
197
+ "topLevel": true,
198
+ "x": 896,
199
+ "y": 303
200
+ },
201
+ "looks_changeeffectby": {
202
+ "opcode": "looks_changeeffectby",
203
+ "next": null,
204
+ "parent": null,
205
+ "inputs": {
206
+ "CHANGE": [
207
+ 1,
208
+ [
209
+ 4,
210
+ "25"
211
+ ]
212
+ ]
213
+ },
214
+ "fields": {
215
+ "EFFECT": [
216
+ "COLOR",
217
+ null
218
+ ]
219
+ },
220
+ "shadow": false,
221
+ "topLevel": true,
222
+ "x": 892,
223
+ "y": 416
224
+ },
225
+ "looks_seteffectto": {
226
+ "opcode": "looks_seteffectto",
227
+ "next": null,
228
+ "parent": null,
229
+ "inputs": {
230
+ "VALUE": [
231
+ 1,
232
+ [
233
+ 4,
234
+ "0"
235
+ ]
236
+ ]
237
+ },
238
+ "fields": {
239
+ "EFFECT": [
240
+ "COLOR",
241
+ null
242
+ ]
243
+ },
244
+ "shadow": false,
245
+ "topLevel": true,
246
+ "x": 902,
247
+ "y": 527
248
+ },
249
+ "looks_cleargraphiceffects": {
250
+ "opcode": "looks_cleargraphiceffects",
251
+ "next": null,
252
+ "parent": null,
253
+ "inputs": {},
254
+ "fields": {},
255
+ "shadow": false,
256
+ "topLevel": true,
257
+ "x": 902,
258
+ "y": 638
259
+ },
260
+ "looks_show": {
261
+ "opcode": "looks_show",
262
+ "next": null,
263
+ "parent": null,
264
+ "inputs": {},
265
+ "fields": {},
266
+ "shadow": false,
267
+ "topLevel": true,
268
+ "x": 908,
269
+ "y": 758
270
+ },
271
+ "looks_hide": {
272
+ "opcode": "looks_hide",
273
+ "next": null,
274
+ "parent": null,
275
+ "inputs": {},
276
+ "fields": {},
277
+ "shadow": false,
278
+ "topLevel": true,
279
+ "x": 455,
280
+ "y": 861
281
+ },
282
+ "looks_gotofrontback": {
283
+ "opcode": "looks_gotofrontback",
284
+ "next": null,
285
+ "parent": null,
286
+ "inputs": {},
287
+ "fields": {
288
+ "FRONT_BACK": [
289
+ "front",
290
+ null
291
+ ]
292
+ },
293
+ "shadow": false,
294
+ "topLevel": true,
295
+ "x": 853,
296
+ "y": 878
297
+ },
298
+ "looks_goforwardbackwardlayers": {
299
+ "opcode": "looks_goforwardbackwardlayers",
300
+ "next": null,
301
+ "parent": null,
302
+ "inputs": {
303
+ "NUM": [
304
+ 1,
305
+ [
306
+ 7,
307
+ "1"
308
+ ]
309
+ ]
310
+ },
311
+ "fields": {
312
+ "FORWARD_BACKWARD": [
313
+ "forward",
314
+ null
315
+ ]
316
+ },
317
+ "shadow": false,
318
+ "topLevel": true,
319
+ "x": 851,
320
+ "y": 999
321
+ },
322
+ "looks_costumenumbername": {
323
+ "opcode": "looks_costumenumbername",
324
+ "next": null,
325
+ "parent": null,
326
+ "inputs": {},
327
+ "fields": {
328
+ "NUMBER_NAME": [
329
+ "number",
330
+ null
331
+ ]
332
+ },
333
+ "shadow": false,
334
+ "topLevel": true,
335
+ "x": 458,
336
+ "y": 1007
337
+ },
338
+ "looks_backdropnumbername": {
339
+ "opcode": "looks_backdropnumbername",
340
+ "next": null,
341
+ "parent": null,
342
+ "inputs": {},
343
+ "fields": {
344
+ "NUMBER_NAME": [
345
+ "number",
346
+ null
347
+ ]
348
+ },
349
+ "shadow": false,
350
+ "topLevel": true,
351
+ "x": 1242,
352
+ "y": 753
353
+ },
354
+ "looks_size": {
355
+ "opcode": "looks_size",
356
+ "next": null,
357
+ "parent": null,
358
+ "inputs": {},
359
+ "fields": {},
360
+ "shadow": false,
361
+ "topLevel": true,
362
+ "x": 1249,
363
+ "y": 876
364
+ }
365
+ }
blocks/classwise_blocks/motion_block.json ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "motion_goto_menu"
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": "motion_goto",
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
+ "motion_glideto_menu"
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": "motion_glideto",
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
+ }
blocks/classwise_blocks/operator_block.json ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "operator_add": {
3
+ "opcode": "operator_add",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "NUM1": [
8
+ 1,
9
+ [
10
+ 4,
11
+ ""
12
+ ]
13
+ ],
14
+ "NUM2": [
15
+ 1,
16
+ [
17
+ 4,
18
+ ""
19
+ ]
20
+ ]
21
+ },
22
+ "fields": {},
23
+ "shadow": false,
24
+ "topLevel": true,
25
+ "x": 128,
26
+ "y": 153
27
+ },
28
+ "operator_subtract": {
29
+ "opcode": "operator_subtract",
30
+ "next": null,
31
+ "parent": null,
32
+ "inputs": {
33
+ "NUM1": [
34
+ 1,
35
+ [
36
+ 4,
37
+ ""
38
+ ]
39
+ ],
40
+ "NUM2": [
41
+ 1,
42
+ [
43
+ 4,
44
+ ""
45
+ ]
46
+ ]
47
+ },
48
+ "fields": {},
49
+ "shadow": false,
50
+ "topLevel": true,
51
+ "x": 134,
52
+ "y": 214
53
+ },
54
+ "operator_multiply": {
55
+ "opcode": "operator_multiply",
56
+ "next": null,
57
+ "parent": null,
58
+ "inputs": {
59
+ "NUM1": [
60
+ 1,
61
+ [
62
+ 4,
63
+ ""
64
+ ]
65
+ ],
66
+ "NUM2": [
67
+ 1,
68
+ [
69
+ 4,
70
+ ""
71
+ ]
72
+ ]
73
+ },
74
+ "fields": {},
75
+ "shadow": false,
76
+ "topLevel": true,
77
+ "x": 134,
78
+ "y": 278
79
+ },
80
+ "operator_divide": {
81
+ "opcode": "operator_divide",
82
+ "next": null,
83
+ "parent": null,
84
+ "inputs": {
85
+ "NUM1": [
86
+ 1,
87
+ [
88
+ 4,
89
+ ""
90
+ ]
91
+ ],
92
+ "NUM2": [
93
+ 1,
94
+ [
95
+ 4,
96
+ ""
97
+ ]
98
+ ]
99
+ },
100
+ "fields": {},
101
+ "shadow": false,
102
+ "topLevel": true,
103
+ "x": 138,
104
+ "y": 359
105
+ },
106
+ "operator_random": {
107
+ "opcode": "operator_random",
108
+ "next": null,
109
+ "parent": null,
110
+ "inputs": {
111
+ "FROM": [
112
+ 1,
113
+ [
114
+ 4,
115
+ "1"
116
+ ]
117
+ ],
118
+ "TO": [
119
+ 1,
120
+ [
121
+ 4,
122
+ "10"
123
+ ]
124
+ ]
125
+ },
126
+ "fields": {},
127
+ "shadow": false,
128
+ "topLevel": true,
129
+ "x": 311,
130
+ "y": 157
131
+ },
132
+ "operator_gt": {
133
+ "opcode": "operator_gt",
134
+ "next": null,
135
+ "parent": null,
136
+ "inputs": {
137
+ "OPERAND1": [
138
+ 1,
139
+ [
140
+ 10,
141
+ ""
142
+ ]
143
+ ],
144
+ "OPERAND2": [
145
+ 1,
146
+ [
147
+ 10,
148
+ "50"
149
+ ]
150
+ ]
151
+ },
152
+ "fields": {},
153
+ "shadow": false,
154
+ "topLevel": true,
155
+ "x": 348,
156
+ "y": 217
157
+ },
158
+ "operator_lt": {
159
+ "opcode": "operator_lt",
160
+ "next": null,
161
+ "parent": null,
162
+ "inputs": {
163
+ "OPERAND1": [
164
+ 1,
165
+ [
166
+ 10,
167
+ ""
168
+ ]
169
+ ],
170
+ "OPERAND2": [
171
+ 1,
172
+ [
173
+ 10,
174
+ "50"
175
+ ]
176
+ ]
177
+ },
178
+ "fields": {},
179
+ "shadow": false,
180
+ "topLevel": true,
181
+ "x": 345,
182
+ "y": 286
183
+ },
184
+ "operator_equals": {
185
+ "opcode": "operator_equals",
186
+ "next": null,
187
+ "parent": null,
188
+ "inputs": {
189
+ "OPERAND1": [
190
+ 1,
191
+ [
192
+ 10,
193
+ ""
194
+ ]
195
+ ],
196
+ "OPERAND2": [
197
+ 1,
198
+ [
199
+ 10,
200
+ "50"
201
+ ]
202
+ ]
203
+ },
204
+ "fields": {},
205
+ "shadow": false,
206
+ "topLevel": true,
207
+ "x": 345,
208
+ "y": 372
209
+ },
210
+ "operator_and": {
211
+ "opcode": "operator_and",
212
+ "next": null,
213
+ "parent": null,
214
+ "inputs": {},
215
+ "fields": {},
216
+ "shadow": false,
217
+ "topLevel": true,
218
+ "x": 701,
219
+ "y": 158
220
+ },
221
+ "operator_or": {
222
+ "opcode": "operator_or",
223
+ "next": null,
224
+ "parent": null,
225
+ "inputs": {},
226
+ "fields": {},
227
+ "shadow": false,
228
+ "topLevel": true,
229
+ "x": 705,
230
+ "y": 222
231
+ },
232
+ "operator_not": {
233
+ "opcode": "operator_not",
234
+ "next": null,
235
+ "parent": null,
236
+ "inputs": {},
237
+ "fields": {},
238
+ "shadow": false,
239
+ "topLevel": true,
240
+ "x": 734,
241
+ "y": 283
242
+ },
243
+ "operator_join": {
244
+ "opcode": "operator_join",
245
+ "next": null,
246
+ "parent": null,
247
+ "inputs": {
248
+ "STRING1": [
249
+ 1,
250
+ [
251
+ 10,
252
+ "apple "
253
+ ]
254
+ ],
255
+ "STRING2": [
256
+ 1,
257
+ [
258
+ 10,
259
+ "banana"
260
+ ]
261
+ ]
262
+ },
263
+ "fields": {},
264
+ "shadow": false,
265
+ "topLevel": true,
266
+ "x": 663,
267
+ "y": 378
268
+ },
269
+ "operator_letter_of": {
270
+ "opcode": "operator_letter_of",
271
+ "next": null,
272
+ "parent": null,
273
+ "inputs": {
274
+ "LETTER": [
275
+ 1,
276
+ [
277
+ 6,
278
+ "1"
279
+ ]
280
+ ],
281
+ "STRING": [
282
+ 1,
283
+ [
284
+ 10,
285
+ "apple"
286
+ ]
287
+ ]
288
+ },
289
+ "fields": {},
290
+ "shadow": false,
291
+ "topLevel": true,
292
+ "x": 664,
293
+ "y": 445
294
+ },
295
+ "operator_length": {
296
+ "opcode": "operator_length",
297
+ "next": null,
298
+ "parent": null,
299
+ "inputs": {
300
+ "STRING": [
301
+ 1,
302
+ [
303
+ 10,
304
+ "apple"
305
+ ]
306
+ ]
307
+ },
308
+ "fields": {},
309
+ "shadow": false,
310
+ "topLevel": true,
311
+ "x": 664,
312
+ "y": 521
313
+ },
314
+ "operator_contains": {
315
+ "opcode": "operator_contains",
316
+ "next": null,
317
+ "parent": null,
318
+ "inputs": {
319
+ "STRING1": [
320
+ 1,
321
+ [
322
+ 10,
323
+ "apple"
324
+ ]
325
+ ],
326
+ "STRING2": [
327
+ 1,
328
+ [
329
+ 10,
330
+ "a"
331
+ ]
332
+ ]
333
+ },
334
+ "fields": {},
335
+ "shadow": false,
336
+ "topLevel": true,
337
+ "x": 634,
338
+ "y": 599
339
+ },
340
+ "operator_mod": {
341
+ "opcode": "operator_mod",
342
+ "next": null,
343
+ "parent": null,
344
+ "inputs": {
345
+ "NUM1": [
346
+ 1,
347
+ [
348
+ 4,
349
+ ""
350
+ ]
351
+ ],
352
+ "NUM2": [
353
+ 1,
354
+ [
355
+ 4,
356
+ ""
357
+ ]
358
+ ]
359
+ },
360
+ "fields": {},
361
+ "shadow": false,
362
+ "topLevel": true,
363
+ "x": 295,
364
+ "y": 594
365
+ },
366
+ "operator_round": {
367
+ "opcode": "operator_round",
368
+ "next": null,
369
+ "parent": null,
370
+ "inputs": {
371
+ "NUM": [
372
+ 1,
373
+ [
374
+ 4,
375
+ ""
376
+ ]
377
+ ]
378
+ },
379
+ "fields": {},
380
+ "shadow": false,
381
+ "topLevel": true,
382
+ "x": 307,
383
+ "y": 674
384
+ },
385
+ "operator_mathop": {
386
+ "opcode": "operator_mathop",
387
+ "next": null,
388
+ "parent": null,
389
+ "inputs": {
390
+ "NUM": [
391
+ 1,
392
+ [
393
+ 4,
394
+ ""
395
+ ]
396
+ ]
397
+ },
398
+ "fields": {
399
+ "OPERATOR": [
400
+ "abs",
401
+ null
402
+ ]
403
+ },
404
+ "shadow": false,
405
+ "topLevel": true,
406
+ "x": 280,
407
+ "y": 754
408
+ }
409
+ }
blocks/classwise_blocks/sensing_block.json ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sensing_touchingobject": {
3
+ "opcode": "sensing_touchingobject",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "TOUCHINGOBJECTMENU": [
8
+ 1,
9
+ "sensing_touchingobjectmenu"
10
+ ]
11
+ },
12
+ "fields": {},
13
+ "shadow": false,
14
+ "topLevel": true,
15
+ "x": 359,
16
+ "y": 116
17
+ },
18
+ "sensing_touchingobjectmenu": {
19
+ "opcode": "sensing_touchingobjectmenu",
20
+ "next": null,
21
+ "parent": "sensing_touchingobject",
22
+ "inputs": {},
23
+ "fields": {
24
+ "TOUCHINGOBJECTMENU": [
25
+ "_mouse_",
26
+ null
27
+ ]
28
+ },
29
+ "shadow": true,
30
+ "topLevel": false
31
+ },
32
+ "sensing_touchingcolor": {
33
+ "opcode": "sensing_touchingcolor",
34
+ "next": null,
35
+ "parent": null,
36
+ "inputs": {
37
+ "COLOR": [
38
+ 1,
39
+ [
40
+ 9,
41
+ "#55b888"
42
+ ]
43
+ ]
44
+ },
45
+ "fields": {},
46
+ "shadow": false,
47
+ "topLevel": true,
48
+ "x": 360,
49
+ "y": 188
50
+ },
51
+ "sensing_coloristouchingcolor": {
52
+ "opcode": "sensing_coloristouchingcolor",
53
+ "next": null,
54
+ "parent": null,
55
+ "inputs": {
56
+ "COLOR": [
57
+ 1,
58
+ [
59
+ 9,
60
+ "#d019f2"
61
+ ]
62
+ ],
63
+ "COLOR2": [
64
+ 1,
65
+ [
66
+ 9,
67
+ "#2b0de3"
68
+ ]
69
+ ]
70
+ },
71
+ "fields": {},
72
+ "shadow": false,
73
+ "topLevel": true,
74
+ "x": 348,
75
+ "y": 277
76
+ },
77
+ "sensing_askandwait": {
78
+ "opcode": "sensing_askandwait",
79
+ "next": null,
80
+ "parent": null,
81
+ "inputs": {
82
+ "QUESTION": [
83
+ 1,
84
+ [
85
+ 10,
86
+ "What's your name?"
87
+ ]
88
+ ]
89
+ },
90
+ "fields": {},
91
+ "shadow": false,
92
+ "topLevel": true,
93
+ "x": 338,
94
+ "y": 354
95
+ },
96
+ "sensing_answer": {
97
+ "opcode": "sensing_answer",
98
+ "next": null,
99
+ "parent": null,
100
+ "inputs": {},
101
+ "fields": {},
102
+ "shadow": false,
103
+ "topLevel": true,
104
+ "x": 782,
105
+ "y": 111
106
+ },
107
+ "sensing_keypressed": {
108
+ "opcode": "sensing_keypressed",
109
+ "next": null,
110
+ "parent": null,
111
+ "inputs": {
112
+ "KEY_OPTION": [
113
+ 1,
114
+ "sensing_keyoptions"
115
+ ]
116
+ },
117
+ "fields": {},
118
+ "shadow": false,
119
+ "topLevel": true,
120
+ "x": 762,
121
+ "y": 207
122
+ },
123
+ "sensing_keyoptions": {
124
+ "opcode": "sensing_keyoptions",
125
+ "next": null,
126
+ "parent": "sensing_keypressed",
127
+ "inputs": {},
128
+ "fields": {
129
+ "KEY_OPTION": [
130
+ "space",
131
+ null
132
+ ]
133
+ },
134
+ "shadow": true,
135
+ "topLevel": false
136
+ },
137
+ "sensing_mousedown": {
138
+ "opcode": "sensing_mousedown",
139
+ "next": null,
140
+ "parent": null,
141
+ "inputs": {},
142
+ "fields": {},
143
+ "shadow": false,
144
+ "topLevel": true,
145
+ "x": 822,
146
+ "y": 422
147
+ },
148
+ "sensing_mousex": {
149
+ "opcode": "sensing_mousex",
150
+ "next": null,
151
+ "parent": null,
152
+ "inputs": {},
153
+ "fields": {},
154
+ "shadow": false,
155
+ "topLevel": true,
156
+ "x": 302,
157
+ "y": 528
158
+ },
159
+ "sensing_mousey": {
160
+ "opcode": "sensing_mousey",
161
+ "next": null,
162
+ "parent": null,
163
+ "inputs": {},
164
+ "fields": {},
165
+ "shadow": false,
166
+ "topLevel": true,
167
+ "x": 668,
168
+ "y": 547
169
+ },
170
+ "sensing_setdragmode": {
171
+ "opcode": "sensing_setdragmode",
172
+ "next": null,
173
+ "parent": null,
174
+ "inputs": {},
175
+ "fields": {
176
+ "DRAG_MODE": [
177
+ "draggable",
178
+ null
179
+ ]
180
+ },
181
+ "shadow": false,
182
+ "topLevel": true,
183
+ "x": 950,
184
+ "y": 574
185
+ },
186
+ "sensing_loudness": {
187
+ "opcode": "sensing_loudness",
188
+ "next": null,
189
+ "parent": null,
190
+ "inputs": {},
191
+ "fields": {},
192
+ "shadow": false,
193
+ "topLevel": true,
194
+ "x": 658,
195
+ "y": 703
196
+ },
197
+ "sensing_timer": {
198
+ "opcode": "sensing_timer",
199
+ "next": null,
200
+ "parent": null,
201
+ "inputs": {},
202
+ "fields": {},
203
+ "shadow": false,
204
+ "topLevel": true,
205
+ "x": 459,
206
+ "y": 671
207
+ },
208
+ "sensing_resettimer": {
209
+ "opcode": "sensing_resettimer",
210
+ "next": null,
211
+ "parent": null,
212
+ "inputs": {},
213
+ "fields": {},
214
+ "shadow": false,
215
+ "topLevel": true,
216
+ "x": 462,
217
+ "y": 781
218
+ },
219
+ "sensing_of": {
220
+ "opcode": "sensing_of",
221
+ "next": null,
222
+ "parent": null,
223
+ "inputs": {
224
+ "OBJECT": [
225
+ 1,
226
+ "sensing_of_object_menu"
227
+ ]
228
+ },
229
+ "fields": {
230
+ "PROPERTY": [
231
+ "backdrop #",
232
+ null
233
+ ]
234
+ },
235
+ "shadow": false,
236
+ "topLevel": true,
237
+ "x": 997,
238
+ "y": 754
239
+ },
240
+ "sensing_of_object_menu": {
241
+ "opcode": "sensing_of_object_menu",
242
+ "next": null,
243
+ "parent": "sensing_of",
244
+ "inputs": {},
245
+ "fields": {
246
+ "OBJECT": [
247
+ "_stage_",
248
+ null
249
+ ]
250
+ },
251
+ "shadow": true,
252
+ "topLevel": false
253
+ },
254
+ "sensing_current": {
255
+ "opcode": "sensing_current",
256
+ "next": null,
257
+ "parent": null,
258
+ "inputs": {},
259
+ "fields": {
260
+ "CURRENTMENU": [
261
+ "YEAR",
262
+ null
263
+ ]
264
+ },
265
+ "shadow": false,
266
+ "topLevel": true,
267
+ "x": 627,
268
+ "y": 884
269
+ },
270
+ "sensing_dayssince2000": {
271
+ "opcode": "sensing_dayssince2000",
272
+ "next": null,
273
+ "parent": null,
274
+ "inputs": {},
275
+ "fields": {},
276
+ "shadow": false,
277
+ "topLevel": true,
278
+ "x": 959,
279
+ "y": 903
280
+ },
281
+ "sensing_username": {
282
+ "opcode": "sensing_username",
283
+ "next": null,
284
+ "parent": null,
285
+ "inputs": {},
286
+ "fields": {},
287
+ "shadow": false,
288
+ "topLevel": true,
289
+ "x": 833,
290
+ "y": 757
291
+ }
292
+ }
blocks/classwise_blocks/sound_block.json ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sound_playuntildone": {
3
+ "opcode": "sound_playuntildone",
4
+ "next": null,
5
+ "parent": null,
6
+ "inputs": {
7
+ "SOUND_MENU": [
8
+ 1,
9
+ "sound_sounds_menu"
10
+ ]
11
+ },
12
+ "fields": {},
13
+ "shadow": false,
14
+ "topLevel": true,
15
+ "x": 253,
16
+ "y": 17
17
+ },
18
+ "sound_sounds_menu": {
19
+ "opcode": "sound_sounds_menu",
20
+ "next": null,
21
+ "parent": "sound_playuntildone and sound_play",
22
+ "inputs": {},
23
+ "fields": {
24
+ "SOUND_MENU": [
25
+ "Meow",
26
+ null
27
+ ]
28
+ },
29
+ "shadow": true,
30
+ "topLevel": false
31
+ },
32
+ "sound_play": {
33
+ "opcode": "sound_play",
34
+ "next": null,
35
+ "parent": null,
36
+ "inputs": {
37
+ "SOUND_MENU": [
38
+ 1,
39
+ "sound_sounds_menu"
40
+ ]
41
+ },
42
+ "fields": {},
43
+ "shadow": false,
44
+ "topLevel": true,
45
+ "x": 245,
46
+ "y": 122
47
+ },
48
+ "sound_stopallsounds": {
49
+ "opcode": "sound_stopallsounds",
50
+ "next": null,
51
+ "parent": null,
52
+ "inputs": {},
53
+ "fields": {},
54
+ "shadow": false,
55
+ "topLevel": true,
56
+ "x": 253,
57
+ "y": 245
58
+ },
59
+ "sound_changeeffectby": {
60
+ "opcode": "sound_changeeffectby",
61
+ "next": null,
62
+ "parent": null,
63
+ "inputs": {
64
+ "VALUE": [
65
+ 1,
66
+ [
67
+ 4,
68
+ "10"
69
+ ]
70
+ ]
71
+ },
72
+ "fields": {
73
+ "EFFECT": [
74
+ "PITCH",
75
+ null
76
+ ]
77
+ },
78
+ "shadow": false,
79
+ "topLevel": true,
80
+ "x": 653,
81
+ "y": 14
82
+ },
83
+ "sound_seteffectto": {
84
+ "opcode": "sound_seteffectto",
85
+ "next": null,
86
+ "parent": null,
87
+ "inputs": {
88
+ "VALUE": [
89
+ 1,
90
+ [
91
+ 4,
92
+ "100"
93
+ ]
94
+ ]
95
+ },
96
+ "fields": {
97
+ "EFFECT": [
98
+ "PITCH",
99
+ null
100
+ ]
101
+ },
102
+ "shadow": false,
103
+ "topLevel": true,
104
+ "x": 653,
105
+ "y": 139
106
+ },
107
+ "sound_cleareffects": {
108
+ "opcode": "sound_cleareffects",
109
+ "next": null,
110
+ "parent": null,
111
+ "inputs": {},
112
+ "fields": {},
113
+ "shadow": false,
114
+ "topLevel": true,
115
+ "x": 651,
116
+ "y": 242
117
+ },
118
+ "sound_changevolumeby": {
119
+ "opcode": "sound_changevolumeby",
120
+ "next": null,
121
+ "parent": null,
122
+ "inputs": {
123
+ "VOLUME": [
124
+ 1,
125
+ [
126
+ 4,
127
+ "-10"
128
+ ]
129
+ ]
130
+ },
131
+ "fields": {},
132
+ "shadow": false,
133
+ "topLevel": true,
134
+ "x": 645,
135
+ "y": 353
136
+ },
137
+ "sound_setvolumeto": {
138
+ "opcode": "sound_setvolumeto",
139
+ "next": null,
140
+ "parent": null,
141
+ "inputs": {
142
+ "VOLUME": [
143
+ 1,
144
+ [
145
+ 4,
146
+ "100"
147
+ ]
148
+ ]
149
+ },
150
+ "fields": {},
151
+ "shadow": false,
152
+ "topLevel": true,
153
+ "x": 1108,
154
+ "y": 5
155
+ },
156
+ "sound_volume": {
157
+ "opcode": "sound_volume",
158
+ "next": null,
159
+ "parent": null,
160
+ "inputs": {},
161
+ "fields": {},
162
+ "shadow": false,
163
+ "topLevel": true,
164
+ "x": 1136,
165
+ "y": 123
166
+ }
167
+ }
blocks/hat_blocks.json ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "block_category": "Hat Blocks",
3
+ "description": "Hat blocks are characterized by a rounded top and a bump at the bottom. They initiate scripts, meaning they are the starting point for a sequence of interconnected blocks.",
4
+ "blocks": [
5
+ {
6
+ "block_name": "when green flag pressed",
7
+ "block_type": "Events",
8
+ "op_code": "event_whenflagclicked",
9
+ "block_shape": "Hat Block",
10
+ "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
11
+ "inputs": null,
12
+ "example_standalone": "when green flag clicked",
13
+ "example_with_other_blocks": [
14
+ {
15
+ "script": "when green flag clicked\n go to x: (0) y: (0)\n say [Hello!] for (2) seconds\nend",
16
+ "explanation": "This script makes the sprite go to the center of the stage and then say 'Hello!' for 2 seconds when the green flag is clicked."
17
+ }
18
+ ]
19
+ },
20
+ {
21
+ "block_name": "when () key pressed",
22
+ "block_type": "Events",
23
+ "op_code": "event_whenkeypressed",
24
+ "block_shape": "Hat Block",
25
+ "functionality": "This Hat block initiates the script when a specified keyboard key is pressed.",
26
+ "inputs": [
27
+ {
28
+ "name": "key",
29
+ "type": "dropdown",
30
+ "options": [
31
+ "space",
32
+ "up arrow",
33
+ "down arrow",
34
+ "right arrow",
35
+ "left arrow",
36
+ "any",
37
+ "a",
38
+ "b",
39
+ "c",
40
+ "d",
41
+ "e",
42
+ "f",
43
+ "g",
44
+ "h",
45
+ "i",
46
+ "j",
47
+ "k",
48
+ "l",
49
+ "m",
50
+ "n",
51
+ "o",
52
+ "p",
53
+ "q",
54
+ "r",
55
+ "s",
56
+ "t",
57
+ "u",
58
+ "v",
59
+ "w",
60
+ "x",
61
+ "y",
62
+ "z",
63
+ "0",
64
+ "1",
65
+ "2",
66
+ "3",
67
+ "4",
68
+ "5",
69
+ "6",
70
+ "7",
71
+ "8",
72
+ "9"
73
+ ]
74
+ }
75
+ ],
76
+ "example_standalone": "when [space v] key pressed",
77
+ "example_with_other_blocks": [
78
+ {
79
+ "script": "when [space v] key pressed\n repeat (10)\n change y by (10)\n wait (0.1) seconds\n change y by (-10)\n end",
80
+ "explanation": "This script makes the sprite jump when the spacebar is pressed."
81
+ },
82
+ {
83
+ "script": "when [right arrow v] key pressed\n point in direction (90)\n move (10) steps\nend",
84
+ "explanation": "This script moves the sprite right when the right arrow key is pressed."
85
+ }
86
+ ]
87
+ },
88
+ {
89
+ "block_name": "when this sprite clicked",
90
+ "block_type": "Events",
91
+ "op_code": "event_whenthisspriteclicked",
92
+ "block_shape": "Hat Block",
93
+ "functionality": "This Hat block starts the script when the sprite itself is clicked.",
94
+ "inputs": null,
95
+ "example_standalone": "when this sprite clicked",
96
+ "example_with_other_blocks": [
97
+ {
98
+ "script": "when this sprite clicked\n say [Ouch!] for (1) seconds\n change [score v] by (-1)\nend",
99
+ "explanation": "This script makes the sprite say 'Ouch!' and decreases the score by 1 when the sprite is clicked."
100
+ }
101
+ ]
102
+ },
103
+ {
104
+ "block_name": "when backdrop switches to ()",
105
+ "block_type": "Events",
106
+ "op_code": "event_whenbackdropswitchesto",
107
+ "block_shape": "Hat Block",
108
+ "functionality": "This Hat block triggers the script when the stage backdrop changes to a specified backdrop.",
109
+ "inputs": [
110
+ {
111
+ "name": "backdrop name",
112
+ "type": "dropdown",
113
+ "options": ["backdrop1", "backdrop2", "..."]
114
+ }
115
+ ],
116
+ "example_standalone": "when backdrop switches to [game over v]",
117
+ "example_with_other_blocks": [
118
+ {
119
+ "script": "when backdrop switches to [game over v]\n stop [all v]\nend",
120
+ "explanation": "This script stops all running processes when the backdrop changes to 'game over'."
121
+ },
122
+ {
123
+ "script": "when backdrop switches to [level completed v]\n stop [all v]\nend",
124
+ "explanation": "This script stops all running processes when the backdrop changes to 'level completed'."
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ "block_name": "when () > ()",
130
+ "block_type": "Events",
131
+ "op_code": "event_whengreaterthan",
132
+ "block_shape": "Hat Block",
133
+ "functionality": "This Hat block starts the script when a certain value (e.g., loudness from a microphone, or the timer) exceeds a defined threshold.",
134
+ "inputs": [
135
+ {
136
+ "name": "value type",
137
+ "type": "dropdown",
138
+ "options": [
139
+ "loudness",
140
+ "timer"
141
+ ]
142
+ },
143
+ {
144
+ "name": "threshold",
145
+ "type": "number"
146
+ }
147
+ ],
148
+ "example_standalone": "when [loudness v] > (70)",
149
+ "example_with_other_blocks": [
150
+ {
151
+ "script": "when [loudness v] > (70)\n start sound [scream v]\nend",
152
+ "explanation": "This script starts a 'scream' sound when the microphone loudness exceeds 70."
153
+ }
154
+ ]
155
+ },
156
+ {
157
+ "block_name": "when I receive ()",
158
+ "block_type": "Events",
159
+ "op_code": "event_whenbroadcastreceived",
160
+ "block_shape": "Hat Block",
161
+ "functionality": "This Hat block initiates the script upon the reception of a specific broadcast message. This mechanism facilitates indirect communication between sprites or the stage.",
162
+ "inputs": [
163
+ {
164
+ "name": "message name",
165
+ "type": "dropdown",
166
+ "options": ["message1", "message2", "new message..."]
167
+ }
168
+ ],
169
+ "example_standalone": "when I receive [start game v]",
170
+ "example_with_other_blocks": [
171
+ {
172
+ "script": "when I receive [start game v]\n show\n go to x: (0) y: (0)\nend",
173
+ "explanation": "This script makes the sprite visible and moves it to the center of the stage when it receives the 'start game' broadcast."
174
+ },
175
+ {
176
+ "script": "when I receive [game over v]\n set score to 0\n stop [all v]\nend",
177
+ "explanation": "This script stops all and resets the score on stage when it receives the 'game over' broadcast."
178
+ }
179
+ ]
180
+ },
181
+ {
182
+ "block_name": "When I Start as a Clone",
183
+ "block_type": "Control",
184
+ "op_code": "control_start_as_clone",
185
+ "block_shape": "Hat Block",
186
+ "functionality": "This Hat block initiates the script when a clone of the sprite is created. It defines the behavior of individual clones.",
187
+ "inputs": null,
188
+ "example_standalone": "When I Start as a Clone",
189
+ "example_with_other_blocks": [
190
+ {
191
+ "script": "when I start as a clone\n go to x: (pick random -240 to 240) y: (pick random -180 to 180)\n show\n forever\n move (10) steps\n if on edge, bounce\n end\nend",
192
+ "explanation": "This script makes a newly created clone appear at a random position, become visible, and then continuously move 10 steps, bouncing if it hits an edge."
193
+ }
194
+ ]
195
+ },
196
+ {
197
+ "block_name": "define [my custom block]",
198
+ "block_type": "My Blocks",
199
+ "op_code": "procedures_definition",
200
+ "block_shape": "Hat Block",
201
+ "functionality": "This Hat block serves as the definition header for a custom block's script. It allows users to define reusable sequences of code by specifying the block's name and any input parameters it will accept. This promotes modularity and abstraction in projects.",
202
+ "inputs": [
203
+ {
204
+ "name": "PROCCONTAINER",
205
+ "type": "block_prototype"
206
+ }
207
+ ],
208
+ "example_standalone": "define jump (height)",
209
+ "example_with_other_blocks": [
210
+ {
211
+ "script": "define jump (height)\n change y by (height)\n wait (0.5) seconds\n change y by (0 - (height))\nend\n\nwhen green flag clicked\n jump (50)\nend",
212
+ "explanation": "This script first defines a custom block named 'jump' that takes a numerical input 'height'. The definition outlines the actions for jumping up and then down. Later, 'jump (50)' is called to make the sprite jump 50 units."
213
+ }
214
+ ]
215
+ }
216
+ ]
217
+ }
blocks/reporter_blocks.json ADDED
@@ -0,0 +1,709 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "block_category": "Reporter Blocks",
3
+ "description": "Reporter blocks have rounded edges. Their purpose is to report values, which can be numbers or strings, and are designed to fit into input slots of other blocks.",
4
+ "blocks": [
5
+ {
6
+ "block_name": "(x position)",
7
+ "block_type": "Motion",
8
+ "op_code": "motion_xposition",
9
+ "block_shape": "Reporter Block",
10
+ "functionality": "Reports the current X-coordinate of the sprite.[NOTE: not used in stage/backdrops]",
11
+ "inputs": null,
12
+ "example_standalone": "x position",
13
+ "example_with_other_blocks": [
14
+ {
15
+ "script": "when green flag clicked\n say (x position) for (2) seconds\nend",
16
+ "explanation": "This script makes the sprite say its current X-coordinate for 2 seconds."
17
+ }
18
+ ]
19
+ },
20
+ {
21
+ "block_name": "(y position)",
22
+ "block_type": "Motion",
23
+ "op_code": "motion_yposition",
24
+ "block_shape": "Reporter Block",
25
+ "functionality": "Reports the current Y coordinate of the sprite on the stage.[NOTE: not used in stage/backdrops]",
26
+ "inputs": null,
27
+ "example_standalone": "y position",
28
+ "example_with_other_blocks": [
29
+ {
30
+ "script": "set [worms v] to (y position)",
31
+ "explanation": "This script assigns the sprite's current Y position to the 'worms' variable."
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "block_name": "(direction)",
37
+ "block_type": "Motion",
38
+ "op_code": "motion_direction",
39
+ "block_shape": "Reporter Block",
40
+ "functionality": "Reports the current direction of the sprite in degrees (0 = up, 90 = right, 180 = down, -90 = left).[NOTE: not used in stage/backdrops]",
41
+ "inputs": null,
42
+ "example_standalone": "direction",
43
+ "example_with_other_blocks": [
44
+ {
45
+ "script": "when green flag clicked\n say (direction) for (2) seconds\nend",
46
+ "explanation": "This script makes the sprite say its current direction in degrees for 2 seconds."
47
+ }
48
+ ]
49
+ },
50
+ {
51
+ "block_name": "(costume ())",
52
+ "block_type": "Looks",
53
+ "op_code": "looks_costumenumbername",
54
+ "block_shape": "Reporter Block",
55
+ "functionality": "Reports the current costume's number or name.",
56
+ "inputs": [
57
+ {
58
+ "name": "NUMBER_NAME",
59
+ "type": "dropdown",
60
+ "options": [
61
+ "number",
62
+ "name"
63
+ ]
64
+ }
65
+ ],
66
+ "example_standalone": "costume [number v]",
67
+ "example_with_other_blocks": [
68
+ {
69
+ "script": "say join [I am costume ] (costume [name v])",
70
+ "explanation": "This script makes the sprite display its current costume name in a speech bubble."
71
+ }
72
+ ]
73
+ },
74
+ {
75
+ "block_name": "(size)",
76
+ "block_type": "Looks",
77
+ "op_code": "looks_size",
78
+ "block_shape": "Reporter Block",
79
+ "functionality": "Reports the current size of the sprite as a percentage.",
80
+ "inputs": null,
81
+ "example_standalone": "size",
82
+ "example_with_other_blocks": [
83
+ {
84
+ "script": "set size to ( (size) + (10) )",
85
+ "explanation": "This script increases the sprite's size by 10% from its current size."
86
+ }
87
+ ]
88
+ },
89
+ {
90
+ "block_name": "(backdrop ())",
91
+ "block_type": "Looks",
92
+ "op_code": "looks_backdropnumbername",
93
+ "block_shape": "Reporter Block",
94
+ "functionality": "Reports the current backdrop's number or name.",
95
+ "inputs": [
96
+ {
97
+ "name": "NUMBER_NAME",
98
+ "type": "dropdown",
99
+ "options": [
100
+ "number",
101
+ "name"
102
+ ]
103
+ }
104
+ ],
105
+ "example_standalone": "(backdrop [number v])",
106
+ "example_with_other_blocks": [
107
+ {
108
+ "script": "say join [Current backdrop: ] (backdrop [name v]) for (2) seconds",
109
+ "explanation": "This script makes the sprite say the name of the current stage backdrop for 2 seconds."
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ "block_name": "(volume)",
115
+ "block_type": "Sound",
116
+ "op_code": "sound_volume",
117
+ "block_shape": "Reporter Block",
118
+ "functionality": "Reports the current volume level of the sprite.",
119
+ "inputs": null,
120
+ "example_standalone": "volume",
121
+ "example_with_other_blocks": [
122
+ {
123
+ "script": "say join [Current volume: ] (volume)",
124
+ "explanation": "This script makes the sprite display its current volume level in a speech bubble."
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ "block_name": "(distance to ())",
130
+ "block_type": "Sensing",
131
+ "op_code": "sensing_distanceto",
132
+ "block_shape": "Reporter Block",
133
+ "functionality": "Reports the distance from the current sprite to the mouse-pointer or another specified sprite.",
134
+ "inputs": [
135
+ {
136
+ "name": "target",
137
+ "type": "dropdown",
138
+ "options": ["mouse-pointer", "Sprite1", "Sprite2", "...", "_edge_"]
139
+ }
140
+ ],
141
+ "example_standalone": "distance to [mouse-pointer v]",
142
+ "example_with_other_blocks": [
143
+ {
144
+ "script": "if <(distance to [Sprite2 v]) < (50)> then\n say [Too close!]\nend",
145
+ "explanation": "This script makes the sprite say 'Too close!' if it is less than 50 steps away from 'Sprite2'."
146
+ }
147
+ ]
148
+ },
149
+ {
150
+ "block_name": "(answer)",
151
+ "block_type": "Sensing",
152
+ "op_code": "sensing_answer",
153
+ "block_shape": "Reporter Block",
154
+ "functionality": "Holds the most recent text inputted using the 'Ask () and Wait' block.",
155
+ "inputs": null,
156
+ "example_standalone": "answer",
157
+ "example_with_other_blocks": [
158
+ {
159
+ "script": "ask [What is your name?] and wait\n say join [Hello ] (answer)",
160
+ "explanation": "This script prompts the user for their name and then uses the 'answer' block to incorporate their input into a greeting."
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ "block_name": "(mouse x)",
166
+ "block_type": "Sensing",
167
+ "op_code": "sensing_mousex",
168
+ "block_shape": "Reporter Block",
169
+ "functionality": "Reports the mouse-pointer’s current X position on the stage.",
170
+ "inputs": null,
171
+ "example_standalone": "mouse x",
172
+ "example_with_other_blocks": [
173
+ {
174
+ "script": "go to x: (mouse x) y: (mouse y)",
175
+ "explanation": "This script makes the sprite follow the mouse pointer's X and Y coordinates."
176
+ }
177
+ ]
178
+ },
179
+ {
180
+ "block_name": "(mouse y)",
181
+ "block_type": "Sensing",
182
+ "op_code": "sensing_mousey",
183
+ "block_shape": "Reporter Block",
184
+ "functionality": "Reports the mouse-pointer’s current Y position on the stage.",
185
+ "inputs": null,
186
+ "example_standalone": "mouse y",
187
+ "example_with_other_blocks": [
188
+ {
189
+ "script": "if <(mouse y) < (0)> then\n say [Below center]",
190
+ "explanation": "This script makes the sprite say 'Below center' if the mouse pointer is in the lower half of the stage."
191
+ }
192
+ ]
193
+ },
194
+ {
195
+ "block_name": "(loudness)",
196
+ "block_type": "Sensing",
197
+ "op_code": "sensing_loudness",
198
+ "block_shape": "Reporter Block",
199
+ "functionality": "Reports the loudness of noise received by a microphone on a scale of 0 to 100.",
200
+ "inputs": null,
201
+ "example_standalone": "loudness",
202
+ "example_with_other_blocks": [
203
+ {
204
+ "script": "when green flag clicked\n forever\n if <(loudness) > (30)> then\n start sound [pop v]\nend",
205
+ "explanation": "This script continuously checks the microphone loudness and plays a 'pop' sound if it exceeds 30."
206
+ }
207
+ ]
208
+ },
209
+ {
210
+ "block_name": "(timer)",
211
+ "block_type": "Sensing",
212
+ "op_code": "sensing_timer",
213
+ "block_shape": "Reporter Block",
214
+ "functionality": "Reports the elapsed time since Scratch was launched or the timer was reset, increasing by 1 every second.",
215
+ "inputs": null,
216
+ "example_standalone": "timer",
217
+ "example_with_other_blocks": [
218
+ {
219
+ "script": "when green flag clicked\n reset timer\n wait (5) seconds\n say join [Time elapsed: ] (timer)",
220
+ "explanation": "This script resets the timer when the green flag is clicked, waits for 5 seconds, and then reports the elapsed time."
221
+ }
222
+ ]
223
+ },
224
+ {
225
+ "block_name": "(() of ())",
226
+ "block_type": "Sensing",
227
+ "op_code": "sensing_of",
228
+ "block_shape": "Reporter Block",
229
+ "functionality": "Reports a specified value (e.g., x position, direction, costume number) of a specified sprite or the Stage to be accessed in current sprite or stage.",
230
+ "inputs": [
231
+ {
232
+ "name": "value to report",
233
+ "type": "dropdown",
234
+ "options": [
235
+ "x position",
236
+ "y position",
237
+ "direction",
238
+ "costume #",
239
+ "costume name",
240
+ "size",
241
+ "volume",
242
+ "backdrop #",
243
+ "backdrop name"
244
+ ]
245
+ },
246
+ {
247
+ "name": "sprite/stage",
248
+ "type": "dropdown",
249
+ "options": ["Stage", "Sprite1", "Sprite2", "...", "_edge_"]
250
+ }
251
+ ],
252
+ "example_standalone": "x position of [Sprite1 v]",
253
+ "example_with_other_blocks": [
254
+ {
255
+ "script": "set [other sprite X v] to ( (x position) of [Sprite2 v] )",
256
+ "explanation": "This script sets the 'other sprite X' variable to the current X-position of 'Sprite2'."
257
+ }
258
+ ]
259
+ },
260
+ {
261
+ "block_name": "(current ())",
262
+ "block_type": "Sensing",
263
+ "op_code": "sensing_current",
264
+ "block_shape": "Reporter Block",
265
+ "functionality": "Reports the current local year, month, date, day of the week, hour, minutes, or seconds.",
266
+ "inputs": [
267
+ {
268
+ "name": "time unit",
269
+ "type": "dropdown",
270
+ "options": [
271
+ "year",
272
+ "month",
273
+ "date",
274
+ "day of week",
275
+ "hour",
276
+ "minute",
277
+ "second"
278
+ ]
279
+ }
280
+ ],
281
+ "example_standalone": "current [hour v]",
282
+ "example_with_other_blocks": [
283
+ {
284
+ "script": "say join [The current hour is ] (current [hour v])",
285
+ "explanation": "This script makes the sprite say the current hour."
286
+ }
287
+ ]
288
+ },
289
+ {
290
+ "block_name": "(days since 2000)",
291
+ "block_type": "Sensing",
292
+ "op_code": "sensing_dayssince2000",
293
+ "block_shape": "Reporter Block",
294
+ "functionality": "Reports the number of days (and fractions of a day) since 00:00:00 UTC on January 1, 2000.",
295
+ "inputs": null,
296
+ "example_standalone": "days since 2000",
297
+ "example_with_other_blocks": [
298
+ {
299
+ "script": "say join [Days passed: ] (days since 2000)",
300
+ "explanation": "This script makes the sprite display the number of days that have passed since January 1, 2000."
301
+ }
302
+ ]
303
+ },
304
+ {
305
+ "block_name": "(username)",
306
+ "block_type": "Sensing",
307
+ "op_code": "sensing_username",
308
+ "block_shape": "Reporter Block",
309
+ "functionality": "Reports the username of the user currently logged into Scratch. If no user is logged in, it reports nothing.",
310
+ "inputs": null,
311
+ "example_standalone": "username",
312
+ "example_with_other_blocks": [
313
+ {
314
+ "script": "say join [Hello, ] (username)",
315
+ "explanation": "This script makes the sprite greet the user by their Scratch username."
316
+ }
317
+ ]
318
+ },
319
+ {
320
+ "block_name": "(() + ())",
321
+ "block_type": "operator",
322
+ "op_code": "operator_add",
323
+ "block_shape": "Reporter Block",
324
+ "functionality": "Adds two numerical values.",
325
+ "inputs": [
326
+ {
327
+ "name": "number1",
328
+ "type": "number"
329
+ },
330
+ {
331
+ "name": "number2",
332
+ "type": "number"
333
+ }
334
+ ],
335
+ "example_standalone": "(5) + (3)",
336
+ "example_with_other_blocks": [
337
+ {
338
+ "script": "set [total v] to ( (number 1) + (number 2) )",
339
+ "explanation": "This script calculates the sum of 'number 1' and 'number 2' and stores the result in the 'total' variable."
340
+ }
341
+ ]
342
+ },
343
+ {
344
+ "block_name": "(() - ())",
345
+ "block_type": "operator",
346
+ "op_code": "operator_subtract",
347
+ "block_shape": "Reporter Block",
348
+ "functionality": "Subtracts the second numerical value from the first.",
349
+ "inputs": [
350
+ {
351
+ "name": "number1",
352
+ "type": "number"
353
+ },
354
+ {
355
+ "name": "number2",
356
+ "type": "number"
357
+ }
358
+ ],
359
+ "example_standalone": "((10) - (4))",
360
+ "example_with_other_blocks": [
361
+ {
362
+ "script": "set [difference v] to ( (number 1) - (number 2) )",
363
+ "explanation": "This script calculates the subtraction of 'number 2' from 'number 1' and stores the result in the 'difference' variable."
364
+ }
365
+ ]
366
+ },
367
+ {
368
+ "block_name": "(() * ())",
369
+ "block_type": "operator",
370
+ "op_code": "operator_multiply",
371
+ "block_shape": "Reporter Block",
372
+ "functionality": "Multiplies two numerical values.",
373
+ "inputs": [
374
+ {
375
+ "name": "number1",
376
+ "type": "number"
377
+ },
378
+ {
379
+ "name": "number2",
380
+ "type": "number"
381
+ }
382
+ ],
383
+ "example_standalone": "(6) * (7)",
384
+ "example_with_other_blocks": [
385
+ {
386
+ "script": "set [area v] to ( (length) * (width) )",
387
+ "explanation": "This script calculates the area by multiplying 'length' and 'width' variables and stores it in the 'area' variable."
388
+ }
389
+ ]
390
+ },
391
+ {
392
+ "block_name": "(() / ())",
393
+ "block_type": "operator",
394
+ "op_code": "operator_divide",
395
+ "block_shape": "Reporter Block",
396
+ "functionality": "Divides the first numerical value by the second.",
397
+ "inputs": [
398
+ {
399
+ "name": "number1",
400
+ "type": "number"
401
+ },
402
+ {
403
+ "name": "number2",
404
+ "type": "number"
405
+ }
406
+ ],
407
+ "example_standalone": "((20) / (5))",
408
+ "example_with_other_blocks": [
409
+ {
410
+ "script": "set [average v] to ( (total score) / (number of students) )",
411
+ "explanation": "This script calculates the average by dividing 'total score' by 'number of students' and stores it in the 'average' variable."
412
+ }
413
+ ]
414
+ },
415
+ {
416
+ "block_name": "(pick random () to ())",
417
+ "block_type": "operator",
418
+ "op_code": "operator_random",
419
+ "block_shape": "Reporter Block",
420
+ "functionality": "Generates a random integer within a specified inclusive range.",
421
+ "inputs": [
422
+ {
423
+ "name": "min",
424
+ "type": "number"
425
+ },
426
+ {
427
+ "name": "max",
428
+ "type": "number"
429
+ }
430
+ ],
431
+ "example_standalone": "(pick random (1) to (10))",
432
+ "example_with_other_blocks": [
433
+ {
434
+ "script": "go to x: (pick random -240 to 240) y: (pick random -180 to 180)",
435
+ "explanation": "This script moves the sprite to a random position on the stage."
436
+ }
437
+ ]
438
+ },
439
+ {
440
+ "block_name": "(join ()())",
441
+ "block_type": "operator",
442
+ "op_code": "operator_join",
443
+ "block_shape": "Reporter Block",
444
+ "functionality": "Concatenates two strings or values into a single string.",
445
+ "inputs": [
446
+ {
447
+ "name": "string1",
448
+ "type": "string/number"
449
+ },
450
+ {
451
+ "name": "string2",
452
+ "type": "string/number"
453
+ }
454
+ ],
455
+ "example_standalone": "(join [Hello ][World!])",
456
+ "example_with_other_blocks": [
457
+ {
458
+ "script": "say (join [Hello ][World!])",
459
+ "explanation": "This script makes the sprite display 'Hello World!' in a speech bubble by joining two string literals."
460
+ }
461
+ ]
462
+ },
463
+ {
464
+ "block_name": "letter () of ()",
465
+ "block_type": "operator",
466
+ "op_code": "operator_letterof",
467
+ "block_shape": "Reporter Block",
468
+ "functionality": "Reports the character at a specific numerical position within a string.",
469
+ "inputs": [
470
+ {
471
+ "name": "index",
472
+ "type": "number"
473
+ },
474
+ {
475
+ "name": "text",
476
+ "type": "string"
477
+ }
478
+ ],
479
+ "example_standalone": "(letter (1) of [apple])",
480
+ "example_with_other_blocks": [
481
+ {
482
+ "script": "say (letter (1) of [apple])",
483
+ "explanation": "This script makes the sprite display the first character of the string 'apple', which is 'a'."
484
+ }
485
+ ]
486
+ },
487
+ {
488
+ "block_name": "(length of ())",
489
+ "block_type": "operator",
490
+ "op_code": "operator_length",
491
+ "block_shape": "Reporter Block",
492
+ "functionality": "Reports the total number of characters in a given string.",
493
+ "inputs": [
494
+ {
495
+ "name": "text",
496
+ "type": "string"
497
+ }
498
+ ],
499
+ "example_standalone": "(length of [banana])",
500
+ "example_with_other_blocks": [
501
+ {
502
+ "script": "say (length of [banana])",
503
+ "explanation": "This script makes the sprite display the length of the string 'banana', which is 6."
504
+ }
505
+ ]
506
+ },
507
+ {
508
+ "block_name": "(() mod ())",
509
+ "block_type": "operator",
510
+ "op_code": "operator_mod",
511
+ "block_shape": "Reporter Block",
512
+ "functionality": "Reports the remainder when the first number is divided by the second.",
513
+ "inputs": [
514
+ {
515
+ "name": "number1",
516
+ "type": "number"
517
+ },
518
+ {
519
+ "name": "number2",
520
+ "type": "number"
521
+ }
522
+ ],
523
+ "example_standalone": "((10) mod (3))",
524
+ "example_with_other_blocks": [
525
+ {
526
+ "script": "if <([number v] mod (2) = (0))> then\n say [Even number]",
527
+ "explanation": "This script checks if a 'number' variable is even by checking if its remainder when divided by 2 is 0."
528
+ }
529
+ ]
530
+ },
531
+ {
532
+ "block_name": "(round ())",
533
+ "block_type": "operator",
534
+ "op_code": "operator_round",
535
+ "block_shape": "Reporter Block",
536
+ "functionality": "Rounds a numerical value to the nearest integer.",
537
+ "inputs": [
538
+ {
539
+ "name": "number",
540
+ "type": "number"
541
+ }
542
+ ],
543
+ "example_standalone": "(round (3.7))",
544
+ "example_with_other_blocks": [
545
+ {
546
+ "script": "set [rounded score v] to (round (score))",
547
+ "explanation": "This script rounds the 'score' variable to the nearest whole number and stores it in 'rounded score'."
548
+ }
549
+ ]
550
+ },
551
+ {
552
+ "block_name": "(() of ())",
553
+ "block_type": "operator",
554
+ "op_code": "operator_mathop",
555
+ "block_shape": "Reporter Block",
556
+ "functionality": "Performs various mathematical functions (e.g., absolute value, square root, trigonometric functions).",
557
+ "inputs": [
558
+ {
559
+ "name": "function type",
560
+ "type": "dropdown",
561
+ "options": [
562
+ "abs",
563
+ "floor",
564
+ "ceiling",
565
+ "sqrt",
566
+ "sin",
567
+ "cos",
568
+ "tan",
569
+ "asin",
570
+ "acos",
571
+ "atan",
572
+ "ln",
573
+ "log",
574
+ "e ^",
575
+ "10 ^"
576
+ ]
577
+ },
578
+ {
579
+ "name": "value",
580
+ "type": "number"
581
+ }
582
+ ],
583
+ "example_standalone": "([sqrt v] of (25))",
584
+ "example_with_other_blocks": [
585
+ {
586
+ "script": "set [distance v] to ([sqrt v] of ( ( (x position) * (x position) ) + ( (y position) * (y position) ) ))",
587
+ "explanation": "This script calculates the distance from the origin (0,0) using the Pythagorean theorem and stores it in 'distance'."
588
+ }
589
+ ]
590
+ },
591
+ {
592
+ "block_name": "[variable v]",
593
+ "block_type": "Data",
594
+ "op_code": "data_variable",
595
+ "block_shape": "Reporter Block",
596
+ "functionality": "Provides the current value stored in a variable.",
597
+ "inputs": [
598
+ {
599
+ "name": "variable name",
600
+ "type": "dropdown",
601
+ "options": ["my variable", "score", "..."]
602
+ }
603
+ ],
604
+ "example_standalone": "[score v]",
605
+ "example_with_other_blocks": [
606
+ {
607
+ "script": "say ([score v]) for (2) seconds",
608
+ "explanation": "This script makes the sprite say the current value of the 'score' variable for 2 seconds."
609
+ }
610
+ ]
611
+ },
612
+ {
613
+ "block_name": "[list v]",
614
+ "block_type": "Data",
615
+ "op_code": "data_list",
616
+ "block_shape": "Reporter Block",
617
+ "functionality": "Reports the entire content of a specified list. When clicked in the editor, it displays the list as a monitor.",
618
+ "inputs": [
619
+ {
620
+ "name": "list name",
621
+ "type": "dropdown",
622
+ "options": ["my list", "list2", "..."]
623
+ }
624
+ ],
625
+ "example_standalone": "[my list v]",
626
+ "example_with_other_blocks": [
627
+ {
628
+ "script": "say ([my list v]) ",
629
+ "explanation": "This script makes the sprite say all the contents of 'my list'."
630
+ }
631
+ ]
632
+ },
633
+ {
634
+ "block_name": "(item (2) of [myList v])",
635
+ "block_type": "Data",
636
+ "op_code": "data_itemoflist",
637
+ "block_shape": "Reporter Block",
638
+ "functionality": "Reports the item located at a specific position in a list.",
639
+ "inputs": [
640
+ {
641
+ "name": "index/option",
642
+ "type": "number or dropdown",
643
+ "options": [
644
+ "last",
645
+ "random"
646
+ ]
647
+ },
648
+ {
649
+ "name": "list name",
650
+ "type": "dropdown",
651
+ "options": ["shopping list", "my list", "..."]
652
+ }
653
+ ],
654
+ "example_standalone": "item (1) of [shopping list v]",
655
+ "example_with_other_blocks": [
656
+ {
657
+ "script": "say (item (2) of [myList v]) for 2 seconds ",
658
+ "explanation": "This script makes the sprite display the first item from the 'shopping list'."
659
+ }
660
+ ]
661
+ },
662
+ {
663
+ "block_name": "(length of [myList v])",
664
+ "block_type": "Data",
665
+ "op_code": "data_lengthoflist",
666
+ "block_shape": "Reporter Block",
667
+ "functionality": "Provides the total number of items contained in a list.",
668
+ "inputs": [
669
+ {
670
+ "name": "list name",
671
+ "type": "dropdown",
672
+ "options": ["my list", "shopping list", "..."]
673
+ }
674
+ ],
675
+ "example_standalone": "(length of [myList v])",
676
+ "example_with_other_blocks": [
677
+ {
678
+ "script": "say join (length of [shopping list v]) [ items in the list.]",
679
+ "explanation": "This script makes the sprite display the total number of items currently in the 'shopping list'."
680
+ }
681
+ ]
682
+ },
683
+ {
684
+ "block_name": "(item # of [Dog] in [myList v])",
685
+ "block_type": "Data",
686
+ "op_code": "data_itemnumoflist",
687
+ "block_shape": "Reporter Block",
688
+ "functionality": "Reports the index number of the first occurrence of a specified item in a list. If the item is not found, it reports 0.",
689
+ "inputs": [
690
+ {
691
+ "name": "item",
692
+ "type": "string/number"
693
+ },
694
+ {
695
+ "name": "list name",
696
+ "type": "dropdown",
697
+ "options": ["my list", "shopping list", "..."]
698
+ }
699
+ ],
700
+ "example_standalone": "(item # of [apple] in [shopping list v])",
701
+ "example_with_other_blocks": [
702
+ {
703
+ "script": "if <(item # of [Dog] in [myList v])> (0)> then\n say join [Dog found at position ] (item # of [Dog] in [my list v])",
704
+ "explanation": "This script checks if 'banana' is in 'my list' and, if so, reports its position."
705
+ }
706
+ ]
707
+ }
708
+ ]
709
+ }
blocks/stack_blocks.json ADDED
@@ -0,0 +1,1321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "block_category": "Stack Blocks",
3
+ "description": "Stack blocks are the most common block shape, featuring a notch at the top and a bump at the bottom. They perform the main commands within a script and can connect both above and below them.",
4
+ "blocks": [
5
+ {
6
+ "block_name": "move () steps",
7
+ "block_type": "Motion",
8
+ "block_shape": "Stack Block",
9
+ "op_code": "motion_movesteps",
10
+ "functionality": "Moves the sprite forward by the specified number of steps in the direction it is currently facing. A positive value moves it forward, and a negative value moves it backward.",
11
+ "inputs": [
12
+ {
13
+ "name": "STEPS",
14
+ "type": "number"
15
+ }
16
+ ],
17
+ "example_standalone": "move () steps",
18
+ "example_with_other_blocks": [
19
+ {
20
+ "script": "when green flag clicked\n go to x: (0) y: (0)\n point in direction (90)\n move (50) steps\nend",
21
+ "explanation": "This script first places the sprite at the center of the stage, points it to the right (90 degrees), and then moves it 50 steps in that direction."
22
+ }
23
+ ]
24
+ },
25
+ {
26
+ "block_name": "turn right () degrees",
27
+ "block_type": "Motion",
28
+ "block_shape": "Stack Block",
29
+ "op_code": "motion_turnright",
30
+ "functionality": "Turns the sprite clockwise by the specified number of degrees.",
31
+ "inputs": [
32
+ {
33
+ "name": "DEGREES",
34
+ "type": "number"
35
+ }
36
+ ],
37
+ "example_standalone": "turn (clockwise icon) (15) degrees",
38
+ "example_with_other_blocks": [
39
+ {
40
+ "script": "when [right arrow v] key pressed\n turn (clockwise icon) (15) degrees\nend",
41
+ "explanation": "This script makes the sprite turn clockwise by 15 degrees every time the right arrow key is pressed."
42
+ },
43
+ {
44
+ "script": "when green flag clicked\n forever\n turn (clockwise icon) (15) degrees\n wait (0.5) seconds\n end",
45
+ "explanation": "This script makes the sprite continuously spin clockwise by 15 degrees every half second."
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "block_name": "turn left () degrees",
51
+ "block_type": "Motion",
52
+ "block_shape": "Stack Block",
53
+ "op_code": "motion_turnleft",
54
+ "functionality": "Turns the sprite counter-clockwise by the specified number of degrees.",
55
+ "inputs": [
56
+ {
57
+ "name": "DEGREES",
58
+ "type": "number"
59
+ }
60
+ ],
61
+ "example_standalone": "turn (counter-clockwise icon) (15) degrees",
62
+ "example_with_other_blocks": [
63
+ {
64
+ "script": "when [left arrow v] key pressed\n turn (counter-clockwise icon) (15) degrees\nend",
65
+ "explanation": "This script makes the sprite turn counter-clockwise by 15 degrees every time the left arrow key is pressed."
66
+ },
67
+ {
68
+ "script": "when green flag clicked\n forever\n turn (counter-clockwise icon) (15) degrees\n wait (0.5) seconds\n end\nend",
69
+ "explanation": "This script makes the sprite continuously spin counter-clockwise by 15 degrees every half second."
70
+ }
71
+ ]
72
+ },
73
+ {
74
+ "block_name": "go to ()",
75
+ "block_type": "Motion",
76
+ "block_shape": "Stack Block",
77
+ "op_code": "motion_goto",
78
+ "functionality": "Moves the sprite to a specified location, which can be a random position or at the mouse pointer or another to the sprite.",
79
+ "inputs": [
80
+ {
81
+ "name": "TO",
82
+ "type": "dropdown",
83
+ "options": [
84
+ "random position",
85
+ "mouse-pointer",
86
+ "sprite1",
87
+ "..."
88
+ ]
89
+ }
90
+ ],
91
+ "example_standalone": "go to [random position v]",
92
+ "example_with_other_blocks": [
93
+ {
94
+ "script": "when this sprite clicked\n go to [mouse-pointer v]",
95
+ "explanation": "This script moves the sprite to the current position of the mouse pointer whenever the sprite is clicked."
96
+ },
97
+ {
98
+ "script": "when this sprite clicked\n go to [sprite v]",
99
+ "explanation": "This script moves the sprite to the another sprite's position whenever the sprite is clicked."
100
+ }
101
+ ]
102
+ },
103
+ {
104
+ "block_name": "go to x: () y: ()",
105
+ "block_type": "Motion",
106
+ "block_shape": "Stack Block",
107
+ "op_code": "motion_gotoxy",
108
+ "functionality": "Moves the sprite to the specified X and Y coordinates on the stage.",
109
+ "inputs": [
110
+ {
111
+ "name": "X",
112
+ "type": "number"
113
+ },
114
+ {
115
+ "name": "Y",
116
+ "type": "number"
117
+ }
118
+ ],
119
+ "example_standalone": "go to x: (0) y: (0)",
120
+ "example_with_other_blocks": [
121
+ {
122
+ "script": "when green flag clicked\n go to x: (120) y: (0)\n say [Ready to start! v] for (1) seconds\nend",
123
+ "explanation": "This script positions the sprite at the center of the stage at the beginning of the project and then makes it say 'Ready to start!'."
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ "block_name": "glide () secs to ()",
129
+ "block_type": "Motion",
130
+ "block_shape": "Stack Block",
131
+ "op_code": "motion_glideto",
132
+ "functionality": "Glides the sprite smoothly to a specified location (random position, mouse pointer, or another sprite) over a given number of seconds.",
133
+ "inputs": [
134
+ {
135
+ "name": "SECS",
136
+ "type": "number"
137
+ },
138
+ {
139
+ "name": "TO",
140
+ "type": "dropdown",
141
+ "options": [
142
+ "random position",
143
+ "mouse-pointer",
144
+ "sprite1",
145
+ "sprite2",
146
+ "..."
147
+ ]
148
+ }
149
+ ],
150
+ "example_standalone": "glide (1) secs to ([random position v])",
151
+ "example_with_other_blocks": [
152
+ {
153
+ "script": "when green flag clicked\n glide (1) secs to ([mouse-pointer v])\nend",
154
+ "explanation": "This script makes the sprite glide smoothly to the mouse pointer's position over 1 second when the green flag is clicked."
155
+ }
156
+ ]
157
+ },
158
+ {
159
+ "block_name": "glide () secs to x: () y: ()",
160
+ "block_type": "Motion",
161
+ "block_shape": "Stack Block",
162
+ "op_code": "motion_glidesecstoxy",
163
+ "functionality": "Glides the sprite smoothly to the specified X and Y coordinates over a given number of seconds.",
164
+ "inputs": [
165
+ {
166
+ "name": "SECS",
167
+ "type": "number"
168
+ },
169
+ {
170
+ "name": "X",
171
+ "type": "number"
172
+ },
173
+ {
174
+ "name": "Y",
175
+ "type": "number"
176
+ }
177
+ ],
178
+ "example_standalone": "glide (1) secs to x: (100) y: (50)",
179
+ "example_with_other_blocks": [
180
+ {
181
+ "script": "when green flag clicked\n glide (2) secs to x: (150) y: (-100)\n glide (2) secs to x: (-150) y: (100)\nend",
182
+ "explanation": "This script makes the sprite glide to two different points on the stage, taking 2 seconds for each movement."
183
+ }
184
+ ]
185
+ },
186
+ {
187
+ "block_name": "point in direction ()",
188
+ "block_type": "Motion",
189
+ "block_shape": "Stack Block",
190
+ "op_code": "motion_pointindirection",
191
+ "functionality": "Sets the sprite's direction to a specified angle in degrees (0 = up, 90 = right, 180 = down, -90 = left).",
192
+ "inputs": [
193
+ {
194
+ "name": "DIRECTION",
195
+ "type": "number"
196
+ }
197
+ ],
198
+ "example_standalone": "point in direction (90)",
199
+ "example_with_other_blocks": [
200
+ {
201
+ "script": "when green flag clicked\n point in direction (0)\n move (100) steps\nend",
202
+ "explanation": "This script makes the sprite point upwards (0 degrees) and then move 100 steps in that direction."
203
+ }
204
+ ]
205
+ },
206
+ {
207
+ "block_name": "point towards ()",
208
+ "block_type": "Motion",
209
+ "block_shape": "Stack Block",
210
+ "op_code": "motion_pointtowards",
211
+ "functionality": "Points the sprite towards the mouse pointer or another specified sprite.",
212
+ "inputs": [
213
+ {
214
+ "name": "TOWARDS",
215
+ "type": "dropdown",
216
+ "options": [
217
+ "mouse-pointer",
218
+ "sprite1",
219
+ "..."
220
+ ]
221
+ }
222
+ ],
223
+ "example_standalone": "point towards [mouse-pointer v]",
224
+ "example_with_other_blocks": [
225
+ {
226
+ "script": "when this sprite clicked\n point towards [mouse-pointer v]\n move (10) steps\nend",
227
+ "explanation": "When the sprite is clicked, it will point towards the mouse pointer and then move 10 steps in that direction."
228
+ },
229
+ {
230
+ "script": "when green flag clicked\n forever\n point towards [mouse-pointer v]\n move (5) steps\n end\nend",
231
+ "explanation": "This script makes the sprite continuously follow the mouse pointer."
232
+ }
233
+ ]
234
+ },
235
+ {
236
+ "block_name": "change x by ()",
237
+ "block_type": "Motion",
238
+ "block_shape": "Stack Block",
239
+ "op_code": "motion_changexby",
240
+ "functionality": "Changes the sprite's X-coordinate by the specified amount, moving it horizontally.",
241
+ "inputs": [
242
+ {
243
+ "name": "DX",
244
+ "type": "number"
245
+ }
246
+ ],
247
+ "example_standalone": "change x by (10)",
248
+ "example_with_other_blocks": [
249
+ {
250
+ "script": "when [right arrow v] key pressed\n change x by (10)\nend",
251
+ "explanation": "This script moves the sprite 10 steps to the right when the right arrow key is pressed."
252
+ }
253
+ ]
254
+ },
255
+ {
256
+ "block_name": "set x to ()",
257
+ "block_type": "Motion",
258
+ "block_shape": "Stack Block",
259
+ "op_code": "motion_setx",
260
+ "functionality": "Sets the sprite's X-coordinate to a specific value, placing it at a precise horizontal position.",
261
+ "inputs": [
262
+ {
263
+ "name": "X",
264
+ "type": "number"
265
+ }
266
+ ],
267
+ "example_standalone": "set x to (0)",
268
+ "example_with_other_blocks": [
269
+ {
270
+ "script": "when green flag clicked\n set x to (0)\n set y to (0)\nend",
271
+ "explanation": "This script centers the sprite horizontally at the start of the project."
272
+ }
273
+ ]
274
+ },
275
+ {
276
+ "block_name": "change y by ()",
277
+ "block_type": "Motion",
278
+ "block_shape": "Stack Block",
279
+ "op_code": "motion_changeyby",
280
+ "functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
281
+ "inputs": [
282
+ {
283
+ "name": "DY",
284
+ "type": "number"
285
+ }
286
+ ],
287
+ "example_standalone": "change y by (10)",
288
+ "example_with_other_blocks": [
289
+ {
290
+ "script": "when [up arrow v] key pressed\n change y by (10)\nend",
291
+ "explanation": "This script moves the sprite 10 steps up when the up arrow key is pressed."
292
+ }
293
+ ]
294
+ },
295
+ {
296
+ "block_name": "set y to ()",
297
+ "block_type": "Motion",
298
+ "block_shape": "Stack Block",
299
+ "op_code": "motion_sety",
300
+ "functionality": "Sets the sprite's Y-coordinate to a specific value, placing it at a precise vertical position.",
301
+ "inputs": [
302
+ {
303
+ "name": "Y",
304
+ "type": "number"
305
+ }
306
+ ],
307
+ "example_standalone": "set y to (0)",
308
+ "example_with_other_blocks": [
309
+ {
310
+ "script": "when green flag clicked\n set x to (0)\n set y to (0)\nend",
311
+ "explanation": "This script centers the sprite vertically at the start of the project."
312
+ }
313
+ ]
314
+ },
315
+ {
316
+ "block_name": "if on edge, bounce",
317
+ "block_type": "Motion",
318
+ "block_shape": "Stack Block",
319
+ "op_code": "motion_ifonedgebounce",
320
+ "functionality": "Reverses the sprite's direction if it touches the edge of the stage.",
321
+ "inputs": null,
322
+ "example_standalone": "if on edge, bounce",
323
+ "example_with_other_blocks": [
324
+ {
325
+ "script": "when I receive [start moving v]\n repeat (50)\n move (5) steps\n if on edge, bounce\n end\nend",
326
+ "explanation": "Upon receiving the 'start moving' broadcast, the sprite will move 5 steps repeatedly for 50 times, bouncing off edges if it touches them."
327
+ },
328
+ {
329
+ "script": "when green flag clicked\n forever\n move (10) steps\n if on edge, bounce\n end\nend",
330
+ "explanation": "This script makes the sprite move continuously and bounce off the edges of the stage."
331
+ }
332
+ ]
333
+ },
334
+ {
335
+ "block_name": "set rotation style ()",
336
+ "block_type": "Motion",
337
+ "block_shape": "Stack Block",
338
+ "op_code": "motion_setrotationstyle",
339
+ "functionality": "Determines how the sprite rotates: 'left-right' (flips horizontally), 'don't rotate' (stays facing one direction), or 'all around' (rotates freely).",
340
+ "inputs": [
341
+ {
342
+ "name": "STYLE",
343
+ "type": "dropdown",
344
+ "options": [
345
+ "left-right",
346
+ "don't rotate",
347
+ "all around"
348
+ ]
349
+ }
350
+ ],
351
+ "example_standalone": "set rotation style [left-right v]",
352
+ "example_with_other_blocks": [
353
+ {
354
+ "script": "when backdrop switches to [game level 1 v]\n set rotation style [all around v]\nend",
355
+ "explanation": "When the backdrop changes to 'game level 1', the sprite's rotation style will be set to 'all around', allowing it to rotate freely."
356
+ },
357
+ {
358
+ "script": "when green flag clicked\n set rotation style [left-right v]\n forever\n move (10) steps\n if on edge, bounce\n end \nend",
359
+ "explanation": "This script makes the sprite move horizontally and flip its costume when it hits an edge, instead of rotating."
360
+ }
361
+ ]
362
+ },
363
+ {
364
+ "block_name": "say () for () seconds",
365
+ "block_type": "Looks",
366
+ "block_shape": "Stack Block",
367
+ "op_code": "looks_sayforsecs",
368
+ "functionality": "Displays a speech bubble containing specified text for a set duration.",
369
+ "inputs": [
370
+ {
371
+ "name": "MESSAGE",
372
+ "type": "string"
373
+ },
374
+ {
375
+ "name": "SECS",
376
+ "type": "number"
377
+ }
378
+ ],
379
+ "example_standalone": "say [Hello!] for (2) seconds",
380
+ "example_with_other_blocks": [
381
+ {
382
+ "script": "when green flag clicked\n say [Grr] for (3) seconds\n say [Have you seen my honey? v] for (3) seconds\nend",
383
+ "explanation": "This script makes the sprite display two sequential speech bubbles with different messages and durations. First, it says 'Grr' for 3 seconds, then 'Have you seen my honey?' for another 3 seconds."
384
+ }
385
+ ]
386
+ },
387
+ {
388
+ "block_name": "say ()",
389
+ "block_type": "Looks",
390
+ "block_shape": "Stack Block",
391
+ "op_code": "looks_say",
392
+ "functionality": "Displays a speech bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
393
+ "inputs": [
394
+ {
395
+ "name": "MESSAGE",
396
+ "type": "string"
397
+ }
398
+ ],
399
+ "example_standalone": "say [Hello! v]",
400
+ "example_with_other_blocks": [
401
+ {
402
+ "script": "when green flag clicked\n say [Welcome to my game! v]\n wait (2) seconds\n say [] \nend",
403
+ "explanation": "This script makes the sprite say 'Welcome to my game!' for 2 seconds, then clears the speech bubble."
404
+ }
405
+ ]
406
+ },
407
+ {
408
+ "block_name": "think () for () seconds",
409
+ "block_type": "Looks",
410
+ "block_shape": "Stack Block",
411
+ "op_code": "looks_thinkforsecs",
412
+ "functionality": "Displays a thought bubble containing specified text for a set duration.",
413
+ "inputs": [
414
+ {
415
+ "name": "MESSAGE",
416
+ "type": "string"
417
+ },
418
+ {
419
+ "name": "SECS",
420
+ "type": "number"
421
+ }
422
+ ],
423
+ "example_standalone": "think [Hmm... v] for (2) seconds",
424
+ "example_with_other_blocks": [
425
+ {
426
+ "script": "when this sprite clicked\n think [What should I do? v] for (2) seconds\nend",
427
+ "explanation": "This script makes the sprite display a thought bubble saying 'What should I do?' for 2 seconds when clicked."
428
+ }
429
+ ]
430
+ },
431
+ {
432
+ "block_name": "think ()",
433
+ "block_type": "Looks",
434
+ "block_shape": "Stack Block",
435
+ "op_code": "looks_think",
436
+ "functionality": "Displays a thought bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
437
+ "inputs": [
438
+ {
439
+ "name": "MESSAGE",
440
+ "type": "string"
441
+ }
442
+ ],
443
+ "example_standalone": "think [Got it! v]",
444
+ "example_with_other_blocks": [
445
+ {
446
+ "script": "when I receive [correct answer v]\n think [That's right! v]\n wait (1) seconds\n think [good v] \nend",
447
+ "explanation": "This script makes the sprite think 'That's right!' for 1 second when a 'correct answer' broadcast is received, then clears the thought bubble."
448
+ }
449
+ ]
450
+ },
451
+ {
452
+ "block_name": "switch costume to ()",
453
+ "block_type": "Looks",
454
+ "block_shape": "Stack Block",
455
+ "op_code": "looks_switchcostumeto",
456
+ "functionality": "Alters the sprite's appearance to a designated costume.",
457
+ "inputs": [
458
+ {
459
+ "name": "COSTUME",
460
+ "type": "dropdown/number"
461
+ }
462
+ ],
463
+ "example_standalone": "switch costume to [costume1 v]",
464
+ "example_with_other_blocks": [
465
+ {
466
+ "script": "when I receive [explosion v]\n repeat (5)\n next costume\n end\n hide[costume1 v] \nend",
467
+ "explanation": "This script animates an explosion by rapidly switching costumes, then hides the sprite. [3]"
468
+ }
469
+ ]
470
+ },
471
+ {
472
+ "block_name": "next costume",
473
+ "block_type": "Looks",
474
+ "block_shape": "Stack Block",
475
+ "op_code": "looks_nextcostume",
476
+ "functionality": "Switches the sprite's costume to the next one in its costume list. If it's the last costume, it cycles back to the first.",
477
+ "inputs": null,
478
+ "example_standalone": "next costume",
479
+ "example_with_other_blocks": [
480
+ {
481
+ "script": "when [space v] key pressed\n repeat (3)\n next costume\n wait (0.1) seconds\n end \nend",
482
+ "explanation": "When the space key is pressed, the sprite will cycle through its next three costumes with a short delay between each change."
483
+ },
484
+ {
485
+ "script": "when green flag clicked\n forever\n next costume\n wait (0.2) seconds\n end \nend",
486
+ "explanation": "This script continuously animates the sprite by switching to the next costume every 0.2 seconds, creating a walking or flying effect."
487
+ }
488
+ ]
489
+ },
490
+ {
491
+ "block_name": "switch backdrop to ()",
492
+ "block_type": "Looks",
493
+ "block_shape": "Stack Block",
494
+ "op_code": "looks_switchbackdropto",
495
+ "functionality": "Changes the stage's backdrop to a specified backdrop.",
496
+ "inputs": [
497
+ {
498
+ "name": "BACKDROP",
499
+ "type": "dropdown/number"
500
+ }
501
+ ],
502
+ "example_standalone": "switch backdrop to [backdrop1 v]",
503
+ "example_with_other_blocks": [
504
+ {
505
+ "script": "when green flag clicked\n switch backdrop to [start screen v]\nend ",
506
+ "explanation": "This script sets the stage to a 'start screen' backdrop when the project begins."
507
+ }
508
+ ]
509
+ },
510
+ {
511
+ "block_name": "switch backdrop to () and wait",
512
+ "block_type": "Looks",
513
+ "block_shape": "Stack Block",
514
+ "op_code": "looks_switchbackdroptowait",
515
+ "functionality": "Changes the stage's backdrop to a specified backdrop and pauses the script until any 'When backdrop switches to' scripts for that backdrop have finished.",
516
+ "inputs": [
517
+ {
518
+ "name": "BACKDROP",
519
+ "type": "dropdown/number"
520
+ }
521
+ ],
522
+ "example_standalone": "switch backdrop to [game over v] and wait",
523
+ "example_with_other_blocks": [
524
+ {
525
+ "script": "broadcast [game over v]\n switch backdrop to [game over v] and wait\n stop [all v] \nend",
526
+ "explanation": "This script broadcasts a 'game over' message, then changes the backdrop to 'game over' and waits for any associated scripts to finish before stopping all processes."
527
+ }
528
+ ]
529
+ },
530
+ {
531
+ "block_name": "next backdrop",
532
+ "block_type": "Looks",
533
+ "block_shape": "Stack Block",
534
+ "op_code": "looks_nextbackdrop",
535
+ "functionality": "Switches the stage's backdrop to the next one in its backdrop list. If it's the last backdrop, it cycles back to the first.",
536
+ "inputs": null,
537
+ "example_standalone": "next backdrop",
538
+ "example_with_other_blocks": [
539
+ {
540
+ "script": "when [space v] key pressed\n next backdrop\nend",
541
+ "explanation": "This script changes the stage to the next backdrop in the list each time the space key is pressed."
542
+ }
543
+ ]
544
+ },
545
+ {
546
+ "block_name": "change size by ()",
547
+ "block_type": "Looks",
548
+ "block_shape": "Stack Block",
549
+ "op_code": "looks_changesizeby",
550
+ "functionality": "Changes the sprite's size by a specified percentage. Positive values make it larger, negative values make it smaller.",
551
+ "inputs": [
552
+ {
553
+ "name": "CHANGE",
554
+ "type": "number"
555
+ }
556
+ ],
557
+ "example_standalone": "change size by (10)",
558
+ "example_with_other_blocks": [
559
+ {
560
+ "script": "when green flag clicked\n repeat (10)\n change size by (5)\n wait (0.1) seconds\n end \nend ",
561
+ "explanation": "This script makes the sprite gradually grow larger over 10 steps, with a short pause between each size change."
562
+ }
563
+ ]
564
+ },
565
+ {
566
+ "block_name": "set size to ()",
567
+ "block_type": "Looks",
568
+ "block_shape": "Stack Block",
569
+ "op_code": "looks_setsizeto",
570
+ "functionality": "Sets the sprite's size to a specific percentage of its original size.",
571
+ "inputs": [
572
+ {
573
+ "name": "SIZE",
574
+ "type": "number"
575
+ }
576
+ ],
577
+ "example_standalone": "set size to (100)",
578
+ "example_with_other_blocks": [
579
+ {
580
+ "script": "when green flag clicked\n set size to (50)\n wait (1) seconds\n set size to (100) \nend ",
581
+ "explanation": "This script makes the sprite shrink to half its original size at the start, waits for 1 second, then returns to its original size."
582
+ }
583
+ ]
584
+ },
585
+ {
586
+ "block_name": "change () effect by ()",
587
+ "block_type": "Looks",
588
+ "block_shape": "Stack Block",
589
+ "op_code": "looks_changeeffectby",
590
+ "functionality": "Changes a visual effect on the sprite by a specified amount (e.g., color, fisheye, whirl, pixelate, mosaic, brightness, ghost).",
591
+ "inputs": [
592
+ {
593
+ "name": "EFFECT",
594
+ "type": "dropdown",
595
+ "options": [
596
+ "color",
597
+ "fisheye",
598
+ "whirl",
599
+ "pixelate",
600
+ "mosaic",
601
+ "brightness",
602
+ "ghost"
603
+ ]
604
+ },
605
+ {
606
+ "name": "CHANGE",
607
+ "type": "number"
608
+ }
609
+ ],
610
+ "example_standalone": "change [color v] effect by (25)",
611
+ "example_with_other_blocks": [
612
+ {
613
+ "script": "when loudness > (10)\n change [fisheye v] effect by (5)\nend",
614
+ "explanation": "When the loudness detected by the microphone is greater than 10, the sprite's fisheye effect will increase by 5."
615
+ },
616
+ {
617
+ "script": "when green flag clicked\n forever\n change [color v] effect by (5)\n wait (0.1) seconds\n end \nend",
618
+ "explanation": "This script makes the sprite continuously cycle through different colors."
619
+ }
620
+ ]
621
+ },
622
+ {
623
+ "block_name": "set () effect to ()",
624
+ "block_type": "Looks",
625
+ "block_shape": "Stack Block",
626
+ "op_code": "looks_seteffectto",
627
+ "functionality": "Sets a visual effect on the sprite to a specific value.",
628
+ "inputs": [
629
+ {
630
+ "name": "EFFECT",
631
+ "type": "dropdown",
632
+ "options": [
633
+ "color",
634
+ "fisheye",
635
+ "whirl",
636
+ "pixelate",
637
+ "mosaic",
638
+ "brightness",
639
+ "ghost"
640
+ ]
641
+ },
642
+ {
643
+ "name": "VALUE",
644
+ "type": "number"
645
+ }
646
+ ],
647
+ "example_standalone": "set [ghost v] effect to (50)",
648
+ "example_with_other_blocks": [
649
+ {
650
+ "script": "when green flag clicked\n set [ghost v] effect to (75)\nend",
651
+ "explanation": "This script makes the sprite 75% transparent at the start of the project."
652
+ }
653
+ ]
654
+ },
655
+ {
656
+ "block_name": "clear graphic effects",
657
+ "block_type": "Looks",
658
+ "block_shape": "Stack Block",
659
+ "op_code": "looks_cleargraphiceffects",
660
+ "functionality": "Removes all visual effects applied to the sprite.",
661
+ "inputs": null,
662
+ "example_standalone": "clear graphic effects",
663
+ "example_with_other_blocks": [
664
+ {
665
+ "script": "when green flag clicked\n change [color v] effect by (50)\n wait (2) seconds\n clear graphic effects\nend",
666
+ "explanation": "This script changes the sprite's color effect, waits 2 seconds, then resets all graphic effects."
667
+ }
668
+ ]
669
+ },
670
+ {
671
+ "block_name": "show",
672
+ "block_type": "Looks",
673
+ "block_shape": "Stack Block",
674
+ "op_code": "looks_show",
675
+ "functionality": "Makes the sprite visible on the stage.",
676
+ "inputs": null,
677
+ "example_standalone": "show",
678
+ "example_with_other_blocks": [
679
+ {
680
+ "script": "when green flag clicked\n hide[start game v]\nwhen I receive [start game v]\n show [start game v] \nend",
681
+ "explanation": "This script hides the sprite at the beginning of the project and makes it visible when a 'start game' broadcast is received."
682
+ }
683
+ ]
684
+ },
685
+ {
686
+ "block_name": "hide",
687
+ "block_type": "Looks",
688
+ "block_shape": "Stack Block",
689
+ "op_code": "looks_hide",
690
+ "functionality": "Makes the sprite invisible on the stage.",
691
+ "inputs": null,
692
+ "example_standalone": "hide",
693
+ "example_with_other_blocks": [
694
+ {
695
+ "script": "when green flag clicked\n hide \nend",
696
+ "explanation": "This script hides the sprite from the stage when the green flag is clicked."
697
+ }
698
+ ]
699
+ },
700
+ {
701
+ "block_name": "go to () layer",
702
+ "block_type": "Looks",
703
+ "block_shape": "Stack Block",
704
+ "op_code": "looks_gotofrontback",
705
+ "functionality": "Moves the sprite to the front-most or back-most layer of other sprites on the stage.",
706
+ "inputs": [
707
+ {
708
+ "name": "FRONT_BACK",
709
+ "type": "dropdown",
710
+ "options": [
711
+ "front",
712
+ "back"
713
+ ]
714
+ }
715
+ ],
716
+ "example_standalone": "go to [front v] layer",
717
+ "example_with_other_blocks": [
718
+ {
719
+ "script": "when green flag clicked\n go to [front v] layer\nend",
720
+ "explanation": "This script ensures the sprite is always visible on top of other sprites at the start of the project."
721
+ }
722
+ ]
723
+ },
724
+ {
725
+ "block_name": "go () layers",
726
+ "block_type": "Looks",
727
+ "block_shape": "Stack Block",
728
+ "op_code": "looks_goforwardbackwardlayers",
729
+ "functionality": "Moves the sprite forward or backward a specified number of layers in relation to other sprites.",
730
+ "inputs": [
731
+ {
732
+ "name": "FORWARD_BACKWARD",
733
+ "type": "dropdown",
734
+ "options": [
735
+ "forward",
736
+ "backward"
737
+ ]
738
+ },
739
+ {
740
+ "name": "NUM",
741
+ "type": "number"
742
+ }
743
+ ],
744
+ "example_standalone": "go [forward v] (1) layers",
745
+ "example_with_other_blocks": [
746
+ {
747
+ "script": "when this sprite clicked go [forward v] (1) layers\nend",
748
+ "explanation": "This script brings the clicked sprite one layer closer to the front."
749
+ }
750
+ ]
751
+ },
752
+ {
753
+ "block_name": "play sound () until done",
754
+ "block_type": "Sound",
755
+ "block_shape": "Stack Block",
756
+ "op_code": "sound_playuntildone",
757
+ "functionality": "Plays a specified sound and pauses the script's execution until the sound has completed.",
758
+ "inputs": [
759
+ {
760
+ "name": "sound name",
761
+ "type": "dropdown"
762
+ }
763
+ ],
764
+ "example_standalone": "play sound [Meow v] until done",
765
+ "example_with_other_blocks": [
766
+ {
767
+ "script": "when backdrop switches to [winning screen v]\n play sound [fanfare v] until done\n say [You won!] for (2) seconds\nend",
768
+ "explanation": "When the backdrop changes to the 'winning screen', a 'fanfare' sound will play until it finishes, and then the sprite will say 'You won!' for 2 seconds."
769
+ },
770
+ {
771
+ "script": "forever\n play sound [Music v] until done \nend",
772
+ "explanation": "This script creates a continuous loop for background music, playing the 'Music' sound repeatedly."
773
+ }
774
+ ]
775
+ },
776
+ {
777
+ "block_name": "start sound ()",
778
+ "block_type": "Sound",
779
+ "block_shape": "Stack Block",
780
+ "op_code": "sound_play",
781
+ "functionality": "Initiates playback of a specified sound without pausing the script, allowing other actions to proceed concurrently.",
782
+ "inputs": [
783
+ {
784
+ "name": "sound name",
785
+ "type": "dropdown"
786
+ }
787
+ ],
788
+ "example_standalone": "start sound [Pop v]",
789
+ "example_with_other_blocks": [
790
+ {
791
+ "script": "when this sprite clicked\n start sound [Pop v]\n change [score v] by (1)\nend",
792
+ "explanation": "This script plays a 'Pop' sound and increments the score simultaneously when the sprite is clicked."
793
+ }
794
+ ]
795
+ },
796
+ {
797
+ "block_name": "stop all sounds",
798
+ "block_type": "Sound",
799
+ "block_shape": "Stack Block",
800
+ "op_code": "sound_stopallsounds",
801
+ "functionality": "Stops all currently playing sounds.",
802
+ "inputs": null,
803
+ "example_standalone": "stop all sounds",
804
+ "example_with_other_blocks": [
805
+ {
806
+ "script": "when I receive [game over v]\n stop all sounds\nend",
807
+ "explanation": "This script stops any sounds currently playing when the 'game over' broadcast is received."
808
+ }
809
+ ]
810
+ },
811
+ {
812
+ "block_name": "change volume by ()",
813
+ "block_type": "Sound",
814
+ "block_shape": "Stack Block",
815
+ "op_code": "sound_changevolumeby",
816
+ "functionality": "Changes the project's sound volume by a specified amount.",
817
+ "inputs": [
818
+ {
819
+ "name": "change",
820
+ "type": "number"
821
+ }
822
+ ],
823
+ "example_standalone": "change volume by (-10)",
824
+ "example_with_other_blocks": [
825
+ {
826
+ "script": "when [down arrow v] key pressed\n change volume by (-5)\nend",
827
+ "explanation": "This script decreases the project's volume by 5 when the down arrow key is pressed."
828
+ }
829
+ ]
830
+ },
831
+ {
832
+ "block_name": "set volume to () %",
833
+ "block_type": "Sound",
834
+ "block_shape": "Stack Block",
835
+ "op_code": "sound_setvolumeto",
836
+ "functionality": "Sets the sound volume to a specific percentage (0-100).",
837
+ "inputs": [
838
+ {
839
+ "name": "percentage",
840
+ "type": "number"
841
+ }
842
+ ],
843
+ "example_standalone": "set volume to (100) %",
844
+ "example_with_other_blocks": [
845
+ {
846
+ "script": "when green flag clicked\n set volume to (50) %\nend",
847
+ "explanation": "This script sets the project's volume to 50% when the green flag is clicked."
848
+ }
849
+ ]
850
+ },
851
+ {
852
+ "block_name": "broadcast ()",
853
+ "block_type": "Events",
854
+ "block_shape": "Stack Block",
855
+ "op_code": "event_broadcast",
856
+ "functionality": "Sends a broadcast message throughout the Scratch program, activating any 'when I receive ()' blocks that are set to listen for that message, enabling indirect communication.",
857
+ "inputs": [
858
+ {
859
+ "name": "message name",
860
+ "type": "string/dropdown"
861
+ }
862
+ ],
863
+ "example_standalone": "broadcast [start game v]",
864
+ "example_with_other_blocks": [
865
+ {
866
+ "script": "if <key [space v] pressed?> then\n broadcast [jump v]\nend",
867
+ "explanation": "This script sends a 'jump' message to other scripts or sprites when the space key is pressed."
868
+ }
869
+ ]
870
+ },
871
+ {
872
+ "block_name": "broadcast () and wait",
873
+ "block_type": "Events",
874
+ "block_shape": "Stack Block",
875
+ "op_code": "event_broadcastandwait",
876
+ "functionality": "Sends a broadcast message and pauses the current script until all other scripts activated by that broadcast have completed their execution, ensuring sequential coordination.",
877
+ "inputs": [
878
+ {
879
+ "name": "message name",
880
+ "type": "string/dropdown"
881
+ }
882
+ ],
883
+ "example_standalone": "broadcast [initialize sprites v] and wait",
884
+ "example_with_other_blocks": [
885
+ {
886
+ "script": "broadcast [initialize sprites v] and wait\n say [Game Started!] for (2) seconds",
887
+ "explanation": "This script ensures all sprite initialization routines complete before displaying 'Game Started!' for 2 seconds."
888
+ }
889
+ ]
890
+ },
891
+ {
892
+ "block_name": "wait () seconds",
893
+ "block_type": "Control",
894
+ "block_shape": "Stack Block",
895
+ "op_code": "control_wait",
896
+ "functionality": "Pauses the script for a specified duration.",
897
+ "inputs": [
898
+ {
899
+ "name": "seconds",
900
+ "type": "number"
901
+ }
902
+ ],
903
+ "example_standalone": "wait (1) seconds",
904
+ "example_with_other_blocks": [
905
+ {
906
+ "script": "say [Hello!] for (1) seconds\n wait (0.5) seconds\n say [Goodbye!] for (1) seconds",
907
+ "explanation": "This script creates a timed dialogue sequence, pausing for 0.5 seconds between two speech bubbles."
908
+ }
909
+ ]
910
+ },
911
+ {
912
+ "block_name": "wait until <>",
913
+ "block_type": "Control",
914
+ "block_shape": "Stack Block",
915
+ "op_code": "control_wait_until",
916
+ "functionality": "Pauses the script until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
917
+ "inputs": [
918
+ {
919
+ "name": "condition",
920
+ "type": "boolean"
921
+ }
922
+ ],
923
+ "example_standalone": "wait until <key [space v] pressed?>",
924
+ "example_with_other_blocks": [
925
+ {
926
+ "script": "wait until <key [space v] pressed?>\n start sound [pop v]\nend",
927
+ "explanation": "This script pauses until the space key is pressed, then plays a 'pop' sound."
928
+ }
929
+ ]
930
+ },
931
+ {
932
+ "block_name": "stop ()",
933
+ "block_type": "Control",
934
+ "block_shape": "Stack Block",
935
+ "op_code": "control_stop",
936
+ "functionality": "Stops all scripts, this script, or other scripts in the sprite. Becomes a Cap Block if 'all' or 'this script' is selected in the dropdown menu.",
937
+ "inputs": [
938
+ {
939
+ "name": "option",
940
+ "type": "dropdown",
941
+ "options": [
942
+ "all",
943
+ "this script",
944
+ "other scripts in sprite"
945
+ ]
946
+ }
947
+ ],
948
+ "example_standalone": "stop [all v]",
949
+ "example_with_other_blocks": [
950
+ {
951
+ "script": "if <score = (0)> then\n stop [all v]\nend",
952
+ "explanation": "This script stops the entire project if the 'score' variable becomes 0."
953
+ }
954
+ ]
955
+ },
956
+ {
957
+ "block_name": "create clone of ()",
958
+ "block_type": "Control",
959
+ "block_shape": "Stack Block",
960
+ "op_code": "control_create_clone_of",
961
+ "functionality": "Generates a copy, or clone, of a specified sprite (or 'myself' for the current sprite).",
962
+ "inputs": [
963
+ {
964
+ "name": "sprite_name",
965
+ "type": "dropdown",
966
+ "options": [
967
+ "myself",
968
+ "sprite1",
969
+ "..."
970
+ ]
971
+ }
972
+ ],
973
+ "example_standalone": "create clone of [myself v]",
974
+ "example_with_other_blocks": [
975
+ {
976
+ "script": "when I start as a clone\n show\n go to random position\n wait (2) seconds\n delete this clone\nend",
977
+ "explanation": "When a clone is created, it will show itself, go to a random position, wait for 2 seconds, and then delete itself."
978
+ }
979
+ ]
980
+ },
981
+ {
982
+ "block_name": "delete this clone",
983
+ "block_type": "Control",
984
+ "block_shape": "Stack Block",
985
+ "op_code": "control_delete_this_clone",
986
+ "functionality": "Deletes the clone that is currently running the script.",
987
+ "inputs": null,
988
+ "example_standalone": "delete this clone",
989
+ "example_with_other_blocks": [
990
+ {
991
+ "script": "when I start as a clone\n wait (5) seconds\n delete this clone\nend",
992
+ "explanation": "This script makes each clone wait for 5 seconds after it's created, then deletes itself."
993
+ }
994
+ ]
995
+ },
996
+ {
997
+ "block_name": "set [my variable v] to ()",
998
+ "block_type": "Data",
999
+ "block_shape": "Stack Block",
1000
+ "op_code": "data_setvariableto",
1001
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
1002
+ "inputs": [
1003
+ {
1004
+ "name": "variable name",
1005
+ "type": "dropdown"
1006
+ },
1007
+ {
1008
+ "name": "value",
1009
+ "type": "any"
1010
+ }
1011
+ ],
1012
+ "example_standalone": "set [score v] to (0)",
1013
+ "example_with_other_blocks": [
1014
+ {
1015
+ "script": "when green flag clicked\n set [score v] to (0)\n set [player name v] to [Guest]\nend",
1016
+ "explanation": "This script initializes the 'score' variable to 0 and the 'player name' variable to 'Guest' when the project starts."
1017
+ }
1018
+ ]
1019
+ },
1020
+ {
1021
+ "block_name": "change [my variable v] by ()",
1022
+ "block_type": "Data",
1023
+ "block_shape": "Stack Block",
1024
+ "op_code": "data_changevariableby",
1025
+ "functionality": "Increases or decreases a variable's numerical value by a specified amount.",
1026
+ "inputs": [
1027
+ {
1028
+ "name": "variable name",
1029
+ "type": "dropdown"
1030
+ },
1031
+ {
1032
+ "name": "value",
1033
+ "type": "number"
1034
+ }
1035
+ ],
1036
+ "example_standalone": "change [score v] by (1)",
1037
+ "example_with_other_blocks": [
1038
+ {
1039
+ "script": "when this sprite clicked\n change [score v] by (1)\nend",
1040
+ "explanation": "This script increments the 'score' variable by 1 each time the sprite is clicked."
1041
+ }
1042
+ ]
1043
+ },
1044
+ {
1045
+ "block_name": "add () to [my list v]",
1046
+ "block_type": "Data",
1047
+ "block_shape": "Stack Block",
1048
+ "op_code": "data_addtolist",
1049
+ "functionality": "Appends an item to the end of a list.",
1050
+ "inputs": [
1051
+ {
1052
+ "name": "item",
1053
+ "type": "any"
1054
+ },
1055
+ {
1056
+ "name": "list name",
1057
+ "type": "dropdown"
1058
+ }
1059
+ ],
1060
+ "example_standalone": "add [apple] to [shopping list v]",
1061
+ "example_with_other_blocks": [
1062
+ {
1063
+ "script": "when green flag clicked\n add [apple] to [shopping list v]\n add [banana] to [shopping list v]\nend",
1064
+ "explanation": "This script adds 'apple' and 'banana' as new items to the 'shopping list' when the project starts."
1065
+ }
1066
+ ]
1067
+ },
1068
+ {
1069
+ "block_name": "delete () of [my list v]",
1070
+ "block_type": "Data",
1071
+ "block_shape": "Stack Block",
1072
+ "op_code": "data_deleteoflist",
1073
+ "functionality": "Removes an item from a list by its index or by selecting 'all' items.",
1074
+ "inputs": [
1075
+ {
1076
+ "name": "index/option",
1077
+ "type": "number/dropdown",
1078
+ "options": [
1079
+ "all",
1080
+ "last",
1081
+ "random"
1082
+ ]
1083
+ },
1084
+ {
1085
+ "name": "list name",
1086
+ "type": "dropdown"
1087
+ }
1088
+ ],
1089
+ "example_standalone": "delete (1) of [my list v]",
1090
+ "example_with_other_blocks": [
1091
+ {
1092
+ "script": "when green flag clicked\n delete (all) of [my list v]\nend",
1093
+ "explanation": "This script clears all items from 'my list' when the green flag is clicked."
1094
+ }
1095
+ ]
1096
+ },
1097
+ {
1098
+ "block_name": "insert () at () of [my list v]",
1099
+ "block_type": "Data",
1100
+ "block_shape": "Stack Block",
1101
+ "op_code": "data_insertatlist",
1102
+ "functionality": "Inserts an item at a specific position within a list.",
1103
+ "inputs": [
1104
+ {
1105
+ "name": "item",
1106
+ "type": "any"
1107
+ },
1108
+ {
1109
+ "name": "index",
1110
+ "type": "number"
1111
+ },
1112
+ {
1113
+ "name": "list name",
1114
+ "type": "dropdown"
1115
+ }
1116
+ ],
1117
+ "example_standalone": "insert [orange] at (2) of [fruits v]",
1118
+ "example_with_other_blocks": [
1119
+ {
1120
+ "script": "insert [orange] at (2) of [fruits v]",
1121
+ "explanation": "This script inserts 'orange' as the second item in the 'fruits' list, shifting subsequent items."
1122
+ }
1123
+ ]
1124
+ },
1125
+ {
1126
+ "block_name": "replace item () of [my list v] with ()",
1127
+ "block_type": "Data",
1128
+ "block_shape": "Stack Block",
1129
+ "op_code": "data_replaceitemoflist",
1130
+ "functionality": "Replaces an item at a specific position in a list with a new value.",
1131
+ "inputs": [
1132
+ {
1133
+ "name": "index",
1134
+ "type": "number"
1135
+ },
1136
+ {
1137
+ "name": "list name",
1138
+ "type": "dropdown"
1139
+ },
1140
+ {
1141
+ "name": "new item",
1142
+ "type": "any"
1143
+ }
1144
+ ],
1145
+ "example_standalone": "replace item (1) of [colors v] with [blue]",
1146
+ "example_with_other_blocks": [
1147
+ {
1148
+ "script": "replace item (1) of [colors v] with [blue]",
1149
+ "explanation": "This script changes the first item in the 'colors' list to 'blue'."
1150
+ }
1151
+ ]
1152
+ },
1153
+ {
1154
+ "block_name": "show variable [my variable v]",
1155
+ "block_type": "Data",
1156
+ "block_shape": "Stack Block",
1157
+ "op_code": "data_showvariable",
1158
+ "functionality": "Makes a variable's monitor visible on the stage.",
1159
+ "inputs": [
1160
+ {
1161
+ "name": "variable name",
1162
+ "type": "dropdown"
1163
+ }
1164
+ ],
1165
+ "example_standalone": "show variable [score v]",
1166
+ "example_with_other_blocks": [
1167
+ {
1168
+ "script": "when green flag clicked\n show variable [score v]\nend",
1169
+ "explanation": "This script displays the 'score' variable on the stage when the project starts."
1170
+ }
1171
+ ]
1172
+ },
1173
+ {
1174
+ "block_name": "hide variable [my variable v]",
1175
+ "block_type": "Data",
1176
+ "block_shape": "Stack Block",
1177
+ "op_code": "data_hidevariable",
1178
+ "functionality": "Hides a variable's monitor from the stage.",
1179
+ "inputs": [
1180
+ {
1181
+ "name": "variable name",
1182
+ "type": "dropdown"
1183
+ }
1184
+ ],
1185
+ "example_standalone": "hide variable [score v]",
1186
+ "example_with_other_blocks": [
1187
+ {
1188
+ "script": "when I receive [game over v]\n hide variable [score v]\nend",
1189
+ "explanation": "This script hides the 'score' variable when the 'game over' broadcast is received."
1190
+ }
1191
+ ]
1192
+ },
1193
+ {
1194
+ "block_name": "show list [my list v]",
1195
+ "block_type": "Data",
1196
+ "block_shape": "Stack Block",
1197
+ "op_code": "data_showlist",
1198
+ "functionality": "Makes a list's monitor visible on the stage.",
1199
+ "inputs": [
1200
+ {
1201
+ "name": "list name",
1202
+ "type": "dropdown"
1203
+ }
1204
+ ],
1205
+ "example_standalone": "show list [shopping list v]",
1206
+ "example_with_other_blocks": [
1207
+ {
1208
+ "script": "when green flag clicked\n show list [shopping list v]\nend",
1209
+ "explanation": "This script displays the 'shopping list' on the stage when the project starts."
1210
+ }
1211
+ ]
1212
+ },
1213
+ {
1214
+ "block_name": "hide list [my list v]",
1215
+ "block_type": "Data",
1216
+ "block_shape": "Stack Block",
1217
+ "op_code": "data_hidelist",
1218
+ "functionality": "Hides a list's monitor from the stage.",
1219
+ "inputs": [
1220
+ {
1221
+ "name": "list name",
1222
+ "type": "dropdown"
1223
+ }
1224
+ ],
1225
+ "example_standalone": "hide list [shopping list v]",
1226
+ "example_with_other_blocks": [
1227
+ {
1228
+ "script": "when I receive [game over v]\n hide list [shopping list v]\nend",
1229
+ "explanation": "This script hides the 'shopping list' when the 'game over' broadcast is received."
1230
+ }
1231
+ ]
1232
+ },
1233
+ {
1234
+ "block_name": "Ask () and Wait",
1235
+ "block_type": "Sensing",
1236
+ "block_shape": "Stack Block",
1237
+ "op_code": "sensing_askandwait",
1238
+ "functionality": "Displays an input box with specified text at the bottom of the screen, allowing users to input text, which is stored in the 'Answer' block.",
1239
+ "inputs": [
1240
+ {
1241
+ "name": "question",
1242
+ "type": "text"
1243
+ }
1244
+ ],
1245
+ "example_standalone": "ask [What is your name? v] and wait",
1246
+ "example_with_other_blocks": [
1247
+ {
1248
+ "script": "ask [What is your name? v] and wait\n say join [Hello v] (answer) for (2) seconds \nend",
1249
+ "explanation": "This script prompts the user for their name, waits for input, then greets them using the provided answer."
1250
+ }
1251
+ ]
1252
+ },
1253
+ {
1254
+ "block_name": "Reset Timer",
1255
+ "block_type": "Sensing",
1256
+ "block_shape": "Stack Block",
1257
+ "op_code": "sensing_resettimer",
1258
+ "functionality": "Sets the timer’s value back to 0.0.",
1259
+ "inputs": null,
1260
+ "example_standalone": "reset timer",
1261
+ "example_with_other_blocks": [
1262
+ {
1263
+ "script": "when green flag clicked\n reset timer\n wait (5) seconds\n say timer for (2) seconds\nend",
1264
+ "explanation": "This script resets the timer at the start, waits for 5 seconds, then says the current timer value."
1265
+ }
1266
+ ]
1267
+ },
1268
+ {
1269
+ "block_name": "set drag mode [draggable v]",
1270
+ "block_type": "Sensing",
1271
+ "block_shape": "Stack Block",
1272
+ "op_code": "sensing_setdragmode",
1273
+ "functionality": "Sets whether the sprite can be dragged by the mouse on the stage.",
1274
+ "inputs": null,
1275
+ "fields": {
1276
+ "DRAG_MODE": {
1277
+ "type": "dropdown",
1278
+ "options": [
1279
+ "draggable",
1280
+ "not draggable"
1281
+ ]
1282
+ }
1283
+ },
1284
+ "example_standalone": "set drag mode [draggable v]",
1285
+ "example_with_other_blocks": [
1286
+ {
1287
+ "script": "when green flag clicked\n set drag mode [not draggable v]\nend when this sprite clicked\n set drag mode [draggable v] \nend",
1288
+ "explanation": "This script makes the sprite not draggable when the project starts, but allows it to be dragged once it's clicked."
1289
+ }
1290
+ ]
1291
+ },
1292
+ {
1293
+ "block_name": "[my custom block]",
1294
+ "block_type": "My Blocks",
1295
+ "block_shape": "Stack Block",
1296
+ "op_code": "procedures_call",
1297
+ "functionality": "Executes the script defined by a corresponding 'define' Hat block. This block allows users to call and reuse custom code sequences by simply dragging and dropping it into their scripts, optionally providing required input values.",
1298
+ "inputs": [
1299
+ {
1300
+ "name": "argument_name_1",
1301
+ "type": "any"
1302
+ },
1303
+ {
1304
+ "name": "argument_name_2",
1305
+ "type": "any"
1306
+ }
1307
+ ],
1308
+ "example_standalone": "jump (50)",
1309
+ "example_with_other_blocks": [
1310
+ {
1311
+ "script": "when green flag clicked\n go to x: (0) y: (0)\n jump (50)\n wait (1) seconds\n say [I jumped!] for (2) seconds",
1312
+ "explanation": "This script moves the sprite to a starting position, then calls the 'jump' custom block with an input of 50 (assuming 'jump' is a custom block that moves the sprite up and down). After the jump, the sprite says 'I jumped!'."
1313
+ },
1314
+ {
1315
+ "script": "when green flag clicked\n hide\n forever\n create clone of [myself v]\n wait (1) seconds\n end",
1316
+ "explanation": "This script continuously creates new clones of the current sprite every second after the original sprite hides itself."
1317
+ }
1318
+ ]
1319
+ }
1320
+ ]
1321
+ }
chat_agent/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Multi-language Chat Agent Package
chat_agent/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (152 Bytes). View file
 
chat_agent/api/README.md ADDED
@@ -0,0 +1,898 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chat Agent API Documentation
2
+
3
+ This document describes the REST API endpoints for the multi-language chat agent.
4
+
5
+ ## Base URL
6
+
7
+ All API endpoints are prefixed with `/api/v1/chat`
8
+
9
+ ## Authentication
10
+
11
+ All endpoints (except health check and supported languages) require authentication via one of the following methods:
12
+
13
+ ### Header-based Authentication (Development)
14
+ ```
15
+ X-User-ID: your-user-id
16
+ ```
17
+
18
+ ### Token-based Authentication (Production)
19
+ ```
20
+ Authorization: Bearer your-session-token
21
+ ```
22
+
23
+ ## Rate Limiting
24
+
25
+ The API implements rate limiting to prevent abuse:
26
+ - Default: 200 requests per day, 50 requests per hour
27
+ - Session creation: 10 requests per minute
28
+ - Session deletion: 5 requests per minute
29
+ - Other endpoints: 20-30 requests per minute
30
+
31
+ Rate limit exceeded responses return HTTP 429 with retry information.
32
+
33
+ ## Endpoints
34
+
35
+ ### 1. Get Supported Languages
36
+
37
+ Get a list of all supported programming languages.
38
+
39
+ **Endpoint:** `GET /api/v1/chat/languages`
40
+
41
+ **Authentication:** Not required
42
+
43
+ **Response:**
44
+ ```json
45
+ {
46
+ "languages": [
47
+ {
48
+ "code": "python",
49
+ "name": "Python",
50
+ "syntax_highlighting": "python",
51
+ "file_extensions": [".py", ".pyw"]
52
+ },
53
+ {
54
+ "code": "javascript",
55
+ "name": "JavaScript",
56
+ "syntax_highlighting": "javascript",
57
+ "file_extensions": [".js", ".mjs"]
58
+ }
59
+ ],
60
+ "default_language": "python",
61
+ "total_count": 8
62
+ }
63
+ ```
64
+
65
+ ### 2. Create Chat Session
66
+
67
+ Create a new chat session for a user.
68
+
69
+ **Endpoint:** `POST /api/v1/chat/sessions`
70
+
71
+ **Authentication:** Required
72
+
73
+ **Request Body:**
74
+ ```json
75
+ {
76
+ "language": "python",
77
+ "metadata": {
78
+ "source": "web_app",
79
+ "user_preferences": {}
80
+ }
81
+ }
82
+ ```
83
+
84
+ **Response (201 Created):**
85
+ ```json
86
+ {
87
+ "session_id": "uuid-string",
88
+ "user_id": "user-123",
89
+ "language": "python",
90
+ "created_at": "2023-01-01T00:00:00",
91
+ "message_count": 0,
92
+ "metadata": {
93
+ "source": "web_app"
94
+ }
95
+ }
96
+ ```
97
+
98
+ **Error Responses:**
99
+ - `400 Bad Request`: Invalid language or missing required fields
100
+ - `401 Unauthorized`: Missing or invalid authentication
101
+ - `429 Too Many Requests`: Rate limit exceeded
102
+
103
+ ### 3. Get Chat Session
104
+
105
+ Retrieve information about a specific chat session.
106
+
107
+ **Endpoint:** `GET /api/v1/chat/sessions/{session_id}`
108
+
109
+ **Authentication:** Required (must own the session)
110
+
111
+ **Response (200 OK):**
112
+ ```json
113
+ {
114
+ "session_id": "uuid-string",
115
+ "user_id": "user-123",
116
+ "language": "python",
117
+ "created_at": "2023-01-01T00:00:00",
118
+ "last_active": "2023-01-01T01:00:00",
119
+ "message_count": 5,
120
+ "is_active": true,
121
+ "metadata": {}
122
+ }
123
+ ```
124
+
125
+ **Error Responses:**
126
+ - `403 Forbidden`: Session belongs to different user
127
+ - `404 Not Found`: Session does not exist
128
+ - `410 Gone`: Session has expired
129
+
130
+ ### 4. List User Sessions
131
+
132
+ Get all sessions for the authenticated user.
133
+
134
+ **Endpoint:** `GET /api/v1/chat/sessions`
135
+
136
+ **Authentication:** Required
137
+
138
+ **Query Parameters:**
139
+ - `active_only` (boolean, default: true): Only return active sessions
140
+
141
+ **Response (200 OK):**
142
+ ```json
143
+ {
144
+ "sessions": [
145
+ {
146
+ "session_id": "uuid-string",
147
+ "language": "python",
148
+ "created_at": "2023-01-01T00:00:00",
149
+ "last_active": "2023-01-01T01:00:00",
150
+ "message_count": 5,
151
+ "is_active": true,
152
+ "metadata": {}
153
+ }
154
+ ],
155
+ "total_count": 1,
156
+ "active_only": true
157
+ }
158
+ ```
159
+
160
+ ### 5. Delete Chat Session
161
+
162
+ Delete a chat session and all associated data.
163
+
164
+ **Endpoint:** `DELETE /api/v1/chat/sessions/{session_id}`
165
+
166
+ **Authentication:** Required (must own the session)
167
+
168
+ **Response (200 OK):**
169
+ ```json
170
+ {
171
+ "message": "Session deleted successfully",
172
+ "session_id": "uuid-string",
173
+ "messages_deleted": 10
174
+ }
175
+ ```
176
+
177
+ **Error Responses:**
178
+ - `403 Forbidden`: Session belongs to different user
179
+ - `404 Not Found`: Session does not exist
180
+
181
+ ### 6. Get Chat History
182
+
183
+ Retrieve chat history for a session.
184
+
185
+ **Endpoint:** `GET /api/v1/chat/sessions/{session_id}/history`
186
+
187
+ **Authentication:** Required (must own the session)
188
+
189
+ **Query Parameters:**
190
+ - `page` (integer, default: 1): Page number for pagination
191
+ - `page_size` (integer, default: 50, max: 100): Messages per page
192
+ - `recent_only` (boolean, default: false): Get only recent messages
193
+ - `limit` (integer, default: 10, max: 50): Number of recent messages (when recent_only=true)
194
+
195
+ **Response (200 OK):**
196
+ ```json
197
+ {
198
+ "messages": [
199
+ {
200
+ "id": "uuid-string",
201
+ "role": "user",
202
+ "content": "Hello, can you help me with Python?",
203
+ "language": "python",
204
+ "timestamp": "2023-01-01T00:00:00",
205
+ "metadata": {}
206
+ },
207
+ {
208
+ "id": "uuid-string",
209
+ "role": "assistant",
210
+ "content": "Of course! I'd be happy to help you with Python.",
211
+ "language": "python",
212
+ "timestamp": "2023-01-01T00:01:00",
213
+ "metadata": {
214
+ "tokens": 15
215
+ }
216
+ }
217
+ ],
218
+ "session_id": "uuid-string",
219
+ "total_count": 2,
220
+ "page": 1,
221
+ "page_size": 50,
222
+ "total_pages": 1
223
+ }
224
+ ```
225
+
226
+ ### 7. Search Chat History
227
+
228
+ Search messages within a session.
229
+
230
+ **Endpoint:** `GET /api/v1/chat/sessions/{session_id}/history/search`
231
+
232
+ **Authentication:** Required (must own the session)
233
+
234
+ **Query Parameters:**
235
+ - `q` (string, required, min: 3 chars): Search query
236
+ - `limit` (integer, default: 20, max: 50): Maximum results
237
+
238
+ **Response (200 OK):**
239
+ ```json
240
+ {
241
+ "messages": [
242
+ {
243
+ "id": "uuid-string",
244
+ "role": "user",
245
+ "content": "How do I use Python lists?",
246
+ "language": "python",
247
+ "timestamp": "2023-01-01T00:00:00",
248
+ "metadata": {}
249
+ }
250
+ ],
251
+ "session_id": "uuid-string",
252
+ "query": "Python",
253
+ "result_count": 1
254
+ }
255
+ ```
256
+
257
+ **Error Responses:**
258
+ - `400 Bad Request`: Query too short or missing
259
+
260
+ ### 8. Get Language Context
261
+
262
+ Get the current language context for a session.
263
+
264
+ **Endpoint:** `GET /api/v1/chat/sessions/{session_id}/language`
265
+
266
+ **Authentication:** Required (must own the session)
267
+
268
+ **Response (200 OK):**
269
+ ```json
270
+ {
271
+ "session_id": "uuid-string",
272
+ "language": "python",
273
+ "prompt_template": "You are a helpful Python programming assistant...",
274
+ "syntax_highlighting": "python",
275
+ "language_info": {
276
+ "name": "Python",
277
+ "syntax_highlighting": "python",
278
+ "file_extensions": [".py", ".pyw"],
279
+ "prompt_template": "..."
280
+ },
281
+ "updated_at": "2023-01-01T00:00:00"
282
+ }
283
+ ```
284
+
285
+ ### 9. Update Language Context
286
+
287
+ Change the programming language for a session.
288
+
289
+ **Endpoint:** `PUT /api/v1/chat/sessions/{session_id}/language`
290
+
291
+ **Authentication:** Required (must own the session)
292
+
293
+ **Request Body:**
294
+ ```json
295
+ {
296
+ "language": "javascript"
297
+ }
298
+ ```
299
+
300
+ **Response (200 OK):**
301
+ ```json
302
+ {
303
+ "session_id": "uuid-string",
304
+ "language": "javascript",
305
+ "prompt_template": "You are a helpful JavaScript programming assistant...",
306
+ "syntax_highlighting": "javascript",
307
+ "language_info": {
308
+ "name": "JavaScript",
309
+ "syntax_highlighting": "javascript",
310
+ "file_extensions": [".js", ".mjs"],
311
+ "prompt_template": "..."
312
+ },
313
+ "updated_at": "2023-01-01T01:00:00"
314
+ }
315
+ ```
316
+
317
+ **Error Responses:**
318
+ - `400 Bad Request`: Unsupported language
319
+
320
+ ### 10. Health Check
321
+
322
+ Check the health status of the API and its dependencies.
323
+
324
+ **Endpoint:** `GET /api/v1/chat/health`
325
+
326
+ **Authentication:** Not required
327
+
328
+ **Response (200 OK):**
329
+ ```json
330
+ {
331
+ "status": "healthy",
332
+ "timestamp": "2023-01-01T00:00:00",
333
+ "services": {
334
+ "database": "connected",
335
+ "redis": "connected"
336
+ }
337
+ }
338
+ ```
339
+
340
+ **Response (503 Service Unavailable):**
341
+ ```json
342
+ {
343
+ "status": "unhealthy",
344
+ "timestamp": "2023-01-01T00:00:00",
345
+ "error": "Database connection failed"
346
+ }
347
+ ```
348
+
349
+ ## Error Responses
350
+
351
+ All error responses follow a consistent format:
352
+
353
+ ```json
354
+ {
355
+ "error": "Error type",
356
+ "message": "Detailed error message"
357
+ }
358
+ ```
359
+
360
+ ### Common HTTP Status Codes
361
+
362
+ - `200 OK`: Request successful
363
+ - `201 Created`: Resource created successfully
364
+ - `400 Bad Request`: Invalid request data
365
+ - `401 Unauthorized`: Authentication required
366
+ - `403 Forbidden`: Access denied
367
+ - `404 Not Found`: Resource not found
368
+ - `410 Gone`: Resource expired
369
+ - `429 Too Many Requests`: Rate limit exceeded
370
+ - `500 Internal Server Error`: Server error
371
+ - `503 Service Unavailable`: Service temporarily unavailable
372
+
373
+ ## Security Considerations
374
+
375
+ 1. **Authentication**: All endpoints require valid authentication
376
+ 2. **Authorization**: Users can only access their own sessions
377
+ 3. **Rate Limiting**: Prevents abuse and manages API costs
378
+ 4. **Input Validation**: All inputs are validated and sanitized
379
+ 5. **Error Handling**: Errors don't expose sensitive information
380
+
381
+ ## Usage Examples
382
+
383
+ ### Create a Session and Send Messages
384
+
385
+ ```bash
386
+ # Create session
387
+ curl -X POST http://localhost:5000/api/v1/chat/sessions \
388
+ -H "X-User-ID: user-123" \
389
+ -H "Content-Type: application/json" \
390
+ -d '{"language": "python"}'
391
+
392
+ # Get session info
393
+ curl -X GET http://localhost:5000/api/v1/chat/sessions/{session_id} \
394
+ -H "X-User-ID: user-123"
395
+
396
+ # Change language
397
+ curl -X PUT http://localhost:5000/api/v1/chat/sessions/{session_id}/language \
398
+ -H "X-User-ID: user-123" \
399
+ -H "Content-Type: application/json" \
400
+ -d '{"language": "javascript"}'
401
+
402
+ # Get chat history
403
+ curl -X GET http://localhost:5000/api/v1/chat/sessions/{session_id}/history \
404
+ -H "X-User-ID: user-123"
405
+
406
+ # Delete session
407
+ curl -X DELETE http://localhost:5000/api/v1/chat/sessions/{session_id} \
408
+ -H "X-User-ID: user-123"
409
+ ```
410
+
411
+ ### JavaScript/TypeScript Example
412
+
413
+ ```javascript
414
+ const API_BASE = 'http://localhost:5000/api/v1/chat';
415
+ const USER_ID = 'user-123';
416
+
417
+ // Create session
418
+ const response = await fetch(`${API_BASE}/sessions`, {
419
+ method: 'POST',
420
+ headers: {
421
+ 'X-User-ID': USER_ID,
422
+ 'Content-Type': 'application/json'
423
+ },
424
+ body: JSON.stringify({
425
+ language: 'python',
426
+ metadata: { source: 'web_app' }
427
+ })
428
+ });
429
+
430
+ const session = await response.json();
431
+ console.log('Created session:', session.session_id);
432
+
433
+ // Get chat history
434
+ const historyResponse = await fetch(
435
+ `${API_BASE}/sessions/${session.session_id}/history`,
436
+ {
437
+ headers: { 'X-User-ID': USER_ID }
438
+ }
439
+ );
440
+
441
+ const history = await historyResponse.json();
442
+ console.log('Messages:', history.messages);
443
+ ```
444
+
445
+ ## WebSocket API Documentation
446
+
447
+ The chat agent also supports real-time communication via WebSocket for interactive chat sessions.
448
+
449
+ ### WebSocket Connection
450
+
451
+ **Endpoint:** `ws://localhost:5000/socket.io/`
452
+
453
+ **Authentication:** Include user ID in connection query parameters or headers
454
+
455
+ **Connection Example:**
456
+ ```javascript
457
+ const socket = io('http://localhost:5000', {
458
+ query: { user_id: 'user-123' },
459
+ transports: ['websocket']
460
+ });
461
+ ```
462
+
463
+ ### WebSocket Events
464
+
465
+ #### 1. Connect Event
466
+ Automatically triggered when client connects.
467
+
468
+ **Client sends:**
469
+ ```javascript
470
+ // Connection is automatic, but you can send auth data
471
+ socket.emit('authenticate', {
472
+ user_id: 'user-123',
473
+ session_token: 'optional-token'
474
+ });
475
+ ```
476
+
477
+ **Server responds:**
478
+ ```javascript
479
+ socket.on('connected', (data) => {
480
+ console.log(data);
481
+ // {
482
+ // "status": "connected",
483
+ // "user_id": "user-123",
484
+ // "timestamp": "2023-01-01T00:00:00Z"
485
+ // }
486
+ });
487
+ ```
488
+
489
+ #### 2. Send Message Event
490
+ Send a chat message to the assistant.
491
+
492
+ **Client sends:**
493
+ ```javascript
494
+ socket.emit('message', {
495
+ session_id: 'session-uuid',
496
+ content: 'How do I create a Python list?',
497
+ language: 'python',
498
+ metadata: {
499
+ source: 'web_chat',
500
+ timestamp: new Date().toISOString()
501
+ }
502
+ });
503
+ ```
504
+
505
+ **Server responds:**
506
+ ```javascript
507
+ socket.on('message_response', (data) => {
508
+ console.log(data);
509
+ // {
510
+ // "session_id": "session-uuid",
511
+ // "message_id": "msg-uuid",
512
+ // "content": "To create a Python list, you can use square brackets...",
513
+ // "language": "python",
514
+ // "timestamp": "2023-01-01T00:01:00Z",
515
+ // "metadata": {
516
+ // "tokens": 45,
517
+ // "response_time": 0.85
518
+ // }
519
+ // }
520
+ });
521
+ ```
522
+
523
+ #### 3. Streaming Response Event
524
+ For real-time streaming responses.
525
+
526
+ **Client sends:**
527
+ ```javascript
528
+ socket.emit('message_stream', {
529
+ session_id: 'session-uuid',
530
+ content: 'Explain Python functions in detail',
531
+ language: 'python'
532
+ });
533
+ ```
534
+
535
+ **Server responds with multiple events:**
536
+ ```javascript
537
+ socket.on('stream_start', (data) => {
538
+ // { "session_id": "session-uuid", "message_id": "msg-uuid" }
539
+ });
540
+
541
+ socket.on('stream_chunk', (data) => {
542
+ // { "session_id": "session-uuid", "chunk": "A Python function is..." }
543
+ });
544
+
545
+ socket.on('stream_end', (data) => {
546
+ // { "session_id": "session-uuid", "complete_response": "..." }
547
+ });
548
+ ```
549
+
550
+ #### 4. Language Switch Event
551
+ Change the programming language context.
552
+
553
+ **Client sends:**
554
+ ```javascript
555
+ socket.emit('language_switch', {
556
+ session_id: 'session-uuid',
557
+ language: 'javascript'
558
+ });
559
+ ```
560
+
561
+ **Server responds:**
562
+ ```javascript
563
+ socket.on('language_switched', (data) => {
564
+ // {
565
+ // "session_id": "session-uuid",
566
+ // "previous_language": "python",
567
+ // "new_language": "javascript",
568
+ // "timestamp": "2023-01-01T00:02:00Z"
569
+ // }
570
+ });
571
+ ```
572
+
573
+ #### 5. Typing Indicator Events
574
+ Show when user or assistant is typing.
575
+
576
+ **Client sends:**
577
+ ```javascript
578
+ socket.emit('typing_start', {
579
+ session_id: 'session-uuid'
580
+ });
581
+
582
+ socket.emit('typing_stop', {
583
+ session_id: 'session-uuid'
584
+ });
585
+ ```
586
+
587
+ **Server responds:**
588
+ ```javascript
589
+ socket.on('assistant_typing', (data) => {
590
+ // { "session_id": "session-uuid", "typing": true }
591
+ });
592
+
593
+ socket.on('assistant_typing_stop', (data) => {
594
+ // { "session_id": "session-uuid", "typing": false }
595
+ });
596
+ ```
597
+
598
+ #### 6. Error Events
599
+ Handle various error conditions.
600
+
601
+ **Server sends:**
602
+ ```javascript
603
+ socket.on('error', (data) => {
604
+ console.error(data);
605
+ // {
606
+ // "error": "session_not_found",
607
+ // "message": "Session does not exist or has expired",
608
+ // "session_id": "session-uuid",
609
+ // "timestamp": "2023-01-01T00:03:00Z"
610
+ // }
611
+ });
612
+ ```
613
+
614
+ ### Complete WebSocket Example
615
+
616
+ ```html
617
+ <!DOCTYPE html>
618
+ <html>
619
+ <head>
620
+ <title>Chat Agent WebSocket Example</title>
621
+ <script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
622
+ </head>
623
+ <body>
624
+ <div id="messages"></div>
625
+ <input type="text" id="messageInput" placeholder="Type your message...">
626
+ <select id="languageSelect">
627
+ <option value="python">Python</option>
628
+ <option value="javascript">JavaScript</option>
629
+ <option value="java">Java</option>
630
+ </select>
631
+ <button onclick="sendMessage()">Send</button>
632
+
633
+ <script>
634
+ const socket = io('http://localhost:5000', {
635
+ query: { user_id: 'demo-user' }
636
+ });
637
+
638
+ let currentSessionId = null;
639
+
640
+ // Create session first
641
+ fetch('/api/v1/chat/sessions', {
642
+ method: 'POST',
643
+ headers: {
644
+ 'X-User-ID': 'demo-user',
645
+ 'Content-Type': 'application/json'
646
+ },
647
+ body: JSON.stringify({ language: 'python' })
648
+ })
649
+ .then(response => response.json())
650
+ .then(data => {
651
+ currentSessionId = data.session_id;
652
+ console.log('Session created:', currentSessionId);
653
+ });
654
+
655
+ // Handle connection
656
+ socket.on('connected', (data) => {
657
+ console.log('Connected to chat agent:', data);
658
+ });
659
+
660
+ // Handle messages
661
+ socket.on('message_response', (data) => {
662
+ addMessage('Assistant', data.content);
663
+ });
664
+
665
+ // Handle streaming
666
+ let streamBuffer = '';
667
+ socket.on('stream_chunk', (data) => {
668
+ streamBuffer += data.chunk;
669
+ updateStreamingMessage(streamBuffer);
670
+ });
671
+
672
+ socket.on('stream_end', (data) => {
673
+ addMessage('Assistant', data.complete_response);
674
+ streamBuffer = '';
675
+ });
676
+
677
+ // Handle language switches
678
+ socket.on('language_switched', (data) => {
679
+ console.log('Language switched:', data);
680
+ addMessage('System', `Language changed to ${data.new_language}`);
681
+ });
682
+
683
+ // Handle errors
684
+ socket.on('error', (data) => {
685
+ console.error('Chat error:', data);
686
+ addMessage('Error', data.message);
687
+ });
688
+
689
+ function sendMessage() {
690
+ const input = document.getElementById('messageInput');
691
+ const message = input.value.trim();
692
+
693
+ if (message && currentSessionId) {
694
+ addMessage('You', message);
695
+
696
+ socket.emit('message', {
697
+ session_id: currentSessionId,
698
+ content: message,
699
+ language: document.getElementById('languageSelect').value
700
+ });
701
+
702
+ input.value = '';
703
+ }
704
+ }
705
+
706
+ function addMessage(sender, content) {
707
+ const messages = document.getElementById('messages');
708
+ const messageDiv = document.createElement('div');
709
+ messageDiv.innerHTML = `<strong>${sender}:</strong> ${content}`;
710
+ messages.appendChild(messageDiv);
711
+ messages.scrollTop = messages.scrollHeight;
712
+ }
713
+
714
+ // Language switching
715
+ document.getElementById('languageSelect').addEventListener('change', (e) => {
716
+ if (currentSessionId) {
717
+ socket.emit('language_switch', {
718
+ session_id: currentSessionId,
719
+ language: e.target.value
720
+ });
721
+ }
722
+ });
723
+
724
+ // Enter key to send message
725
+ document.getElementById('messageInput').addEventListener('keypress', (e) => {
726
+ if (e.key === 'Enter') {
727
+ sendMessage();
728
+ }
729
+ });
730
+ </script>
731
+ </body>
732
+ </html>
733
+ ```
734
+
735
+ ## Advanced Usage Examples
736
+
737
+ ### Error Handling and Retry Logic
738
+
739
+ ```javascript
740
+ class ChatAPIClient {
741
+ constructor(baseURL, userID) {
742
+ this.baseURL = baseURL;
743
+ this.userID = userID;
744
+ this.maxRetries = 3;
745
+ this.retryDelay = 1000; // 1 second
746
+ }
747
+
748
+ async makeRequest(endpoint, options = {}) {
749
+ const url = `${this.baseURL}${endpoint}`;
750
+ const config = {
751
+ ...options,
752
+ headers: {
753
+ 'X-User-ID': this.userID,
754
+ 'Content-Type': 'application/json',
755
+ ...options.headers
756
+ }
757
+ };
758
+
759
+ for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
760
+ try {
761
+ const response = await fetch(url, config);
762
+
763
+ if (response.status === 429) {
764
+ // Rate limited - wait and retry
765
+ const retryAfter = response.headers.get('Retry-After') || this.retryDelay / 1000;
766
+ await this.sleep(retryAfter * 1000);
767
+ continue;
768
+ }
769
+
770
+ if (!response.ok) {
771
+ const error = await response.json();
772
+ throw new Error(`API Error: ${error.message}`);
773
+ }
774
+
775
+ return await response.json();
776
+
777
+ } catch (error) {
778
+ if (attempt === this.maxRetries) {
779
+ throw error;
780
+ }
781
+
782
+ console.warn(`Attempt ${attempt} failed, retrying...`, error.message);
783
+ await this.sleep(this.retryDelay * attempt);
784
+ }
785
+ }
786
+ }
787
+
788
+ async createSession(language = 'python') {
789
+ return await this.makeRequest('/api/v1/chat/sessions', {
790
+ method: 'POST',
791
+ body: JSON.stringify({ language })
792
+ });
793
+ }
794
+
795
+ async getChatHistory(sessionId, page = 1, pageSize = 50) {
796
+ return await this.makeRequest(
797
+ `/api/v1/chat/sessions/${sessionId}/history?page=${page}&page_size=${pageSize}`
798
+ );
799
+ }
800
+
801
+ async switchLanguage(sessionId, language) {
802
+ return await this.makeRequest(`/api/v1/chat/sessions/${sessionId}/language`, {
803
+ method: 'PUT',
804
+ body: JSON.stringify({ language })
805
+ });
806
+ }
807
+
808
+ sleep(ms) {
809
+ return new Promise(resolve => setTimeout(resolve, ms));
810
+ }
811
+ }
812
+
813
+ // Usage
814
+ const client = new ChatAPIClient('http://localhost:5000', 'user-123');
815
+
816
+ try {
817
+ const session = await client.createSession('python');
818
+ console.log('Session created:', session.session_id);
819
+
820
+ const history = await client.getChatHistory(session.session_id);
821
+ console.log('Chat history:', history.messages);
822
+
823
+ await client.switchLanguage(session.session_id, 'javascript');
824
+ console.log('Language switched to JavaScript');
825
+
826
+ } catch (error) {
827
+ console.error('API operation failed:', error.message);
828
+ }
829
+ ```
830
+
831
+ ### Batch Operations
832
+
833
+ ```python
834
+ import asyncio
835
+ import aiohttp
836
+ import json
837
+
838
+ class AsyncChatClient:
839
+ def __init__(self, base_url, user_id):
840
+ self.base_url = base_url
841
+ self.user_id = user_id
842
+ self.headers = {
843
+ 'X-User-ID': user_id,
844
+ 'Content-Type': 'application/json'
845
+ }
846
+
847
+ async def create_multiple_sessions(self, languages):
848
+ """Create multiple sessions concurrently."""
849
+ async with aiohttp.ClientSession() as session:
850
+ tasks = []
851
+ for language in languages:
852
+ task = self._create_session(session, language)
853
+ tasks.append(task)
854
+
855
+ results = await asyncio.gather(*tasks, return_exceptions=True)
856
+ return results
857
+
858
+ async def _create_session(self, session, language):
859
+ url = f"{self.base_url}/api/v1/chat/sessions"
860
+ data = {"language": language}
861
+
862
+ async with session.post(url, headers=self.headers, json=data) as response:
863
+ if response.status == 201:
864
+ return await response.json()
865
+ else:
866
+ error = await response.json()
867
+ raise Exception(f"Failed to create {language} session: {error['message']}")
868
+
869
+ # Usage
870
+ async def main():
871
+ client = AsyncChatClient('http://localhost:5000', 'batch-user')
872
+ languages = ['python', 'javascript', 'java', 'cpp']
873
+
874
+ sessions = await client.create_multiple_sessions(languages)
875
+
876
+ for i, session in enumerate(sessions):
877
+ if isinstance(session, Exception):
878
+ print(f"Failed to create {languages[i]} session: {session}")
879
+ else:
880
+ print(f"Created {languages[i]} session: {session['session_id']}")
881
+
882
+ # Run the async function
883
+ asyncio.run(main())
884
+ ```
885
+
886
+ ## Implementation Notes
887
+
888
+ - The API uses SQLite for testing and PostgreSQL for production
889
+ - Redis is used for caching and session management
890
+ - Rate limiting uses in-memory storage by default (configure Redis for production)
891
+ - All timestamps are in ISO 8601 format (UTC)
892
+ - UUIDs are used for all resource identifiers
893
+ - The API supports both development (header-based) and production (token-based) authentication
894
+ - WebSocket connections are managed using Flask-SocketIO
895
+ - Streaming responses use Server-Sent Events (SSE) over WebSocket
896
+ - All WebSocket events include session_id for proper message routing
897
+ - Error handling includes automatic retry logic for transient failures
898
+ - The system supports concurrent operations across multiple sessions
chat_agent/api/__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Chat Agent API Components."""
2
+
3
+ from .chat_routes import chat_bp
4
+ from .middleware import create_limiter, setup_error_handlers, RequestLoggingMiddleware, create_auth_manager
5
+
6
+ __all__ = [
7
+ 'chat_bp',
8
+ 'create_limiter',
9
+ 'setup_error_handlers',
10
+ 'RequestLoggingMiddleware',
11
+ 'create_auth_manager'
12
+ ]
chat_agent/api/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (446 Bytes). View file
 
chat_agent/api/__pycache__/chat_routes.cpython-312.pyc ADDED
Binary file (27.8 kB). View file
 
chat_agent/api/__pycache__/health.cpython-312.pyc ADDED
Binary file (9.15 kB). View file
 
chat_agent/api/__pycache__/middleware.cpython-312.pyc ADDED
Binary file (17.1 kB). View file
 
chat_agent/api/__pycache__/performance_routes.cpython-312.pyc ADDED
Binary file (16.2 kB). View file
 
chat_agent/api/chat_routes.py ADDED
@@ -0,0 +1,642 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Chat API routes for session management and chat history."""
2
+
3
+ import logging
4
+ from datetime import datetime
5
+ from typing import Dict, Any, Optional
6
+
7
+ from flask import Blueprint, request, jsonify, current_app, g
8
+ from flask_limiter import Limiter
9
+ from flask_limiter.util import get_remote_address
10
+ import redis
11
+
12
+ from ..services.session_manager import SessionManager, SessionManagerError, SessionNotFoundError, SessionExpiredError
13
+ from ..services.chat_history import ChatHistoryManager, ChatHistoryError
14
+ from ..services.language_context import LanguageContextManager, LanguageContextError
15
+ from ..models.language_context import LanguageContext
16
+ from ..models.base import db
17
+ from .middleware import require_auth, validate_json_request, create_limiter
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Create blueprint
23
+ chat_bp = Blueprint('chat', __name__, url_prefix='/api/v1/chat')
24
+
25
+ # Initialize rate limiter (will be configured in app factory)
26
+ limiter = create_limiter()
27
+
28
+
29
+ class APIError(Exception):
30
+ """Base API error class."""
31
+ def __init__(self, message: str, status_code: int = 400, payload: Optional[Dict] = None):
32
+ super().__init__()
33
+ self.message = message
34
+ self.status_code = status_code
35
+ self.payload = payload
36
+
37
+
38
+ def handle_api_error(error: APIError):
39
+ """Handle API errors and return JSON response."""
40
+ response = {'error': error.message}
41
+ if error.payload:
42
+ response.update(error.payload)
43
+ return jsonify(response), error.status_code
44
+
45
+
46
+ def get_services():
47
+ """Get service instances from current app context."""
48
+ redis_client = None
49
+ redis_url = current_app.config.get('REDIS_URL')
50
+
51
+ if redis_url and redis_url != 'None':
52
+ try:
53
+ redis_client = redis.from_url(redis_url)
54
+ # Test the connection
55
+ redis_client.ping()
56
+ except Exception as e:
57
+ logger.warning(f"Redis connection failed: {e}. Running without Redis cache.")
58
+ redis_client = None
59
+ else:
60
+ logger.info("Redis disabled in configuration. Running without Redis cache.")
61
+
62
+ session_manager = SessionManager(redis_client, current_app.config.get('SESSION_TIMEOUT', 3600))
63
+ chat_history_manager = ChatHistoryManager(
64
+ redis_client,
65
+ current_app.config.get('MAX_CHAT_HISTORY', 20),
66
+ current_app.config.get('CONTEXT_WINDOW_SIZE', 10)
67
+ )
68
+ language_context_manager = LanguageContextManager()
69
+
70
+ return session_manager, chat_history_manager, language_context_manager
71
+
72
+
73
+ # Session Management Endpoints
74
+
75
+ @chat_bp.route('/sessions', methods=['POST'])
76
+ @limiter.limit("10 per minute")
77
+ @require_auth
78
+ @validate_json_request(['language'])
79
+ def create_session():
80
+ """
81
+ Create a new chat session.
82
+
83
+ Request body:
84
+ {
85
+ "language": "python",
86
+ "metadata": {"key": "value"} // optional
87
+ }
88
+ """
89
+ try:
90
+ data = request.json_data
91
+ language = data['language']
92
+ metadata = data.get('metadata', {})
93
+
94
+ # Validate language
95
+ if not LanguageContext.is_supported_language(language):
96
+ supported = LanguageContext.get_supported_languages()
97
+ raise APIError(f'Unsupported language: {language}. Supported: {", ".join(supported)}', 400)
98
+
99
+ session_manager, _, language_context_manager = get_services()
100
+
101
+ # Create session
102
+ session = session_manager.create_session(
103
+ user_id=request.user_id,
104
+ language=language,
105
+ session_metadata=metadata
106
+ )
107
+
108
+ # Create language context
109
+ language_context_manager.create_context(session.id, language)
110
+
111
+ logger.info(f"Created session {session.id} for user {request.user_id}")
112
+
113
+ return jsonify({
114
+ 'session_id': session.id,
115
+ 'user_id': session.user_id,
116
+ 'language': session.language,
117
+ 'created_at': session.created_at.isoformat(),
118
+ 'message_count': session.message_count,
119
+ 'metadata': session.session_metadata
120
+ }), 201
121
+
122
+ except (SessionManagerError, LanguageContextError) as e:
123
+ logger.error(f"Error creating session: {e}")
124
+ raise APIError(f'Failed to create session: {str(e)}', 500)
125
+ except APIError:
126
+ raise
127
+ except Exception as e:
128
+ logger.error(f"Unexpected error creating session: {e}")
129
+ raise APIError('Internal server error', 500)
130
+
131
+
132
+ @chat_bp.route('/sessions/<session_id>', methods=['GET'])
133
+ @limiter.limit("30 per minute")
134
+ @require_auth
135
+ def get_session(session_id: str):
136
+ """Get session information."""
137
+ try:
138
+ session_manager, _, _ = get_services()
139
+
140
+ session = session_manager.get_session(session_id)
141
+
142
+ # Check if user owns this session
143
+ if session.user_id != request.user_id:
144
+ raise APIError('Access denied', 403)
145
+
146
+ return jsonify({
147
+ 'session_id': session.id,
148
+ 'user_id': session.user_id,
149
+ 'language': session.language,
150
+ 'created_at': session.created_at.isoformat(),
151
+ 'last_active': session.last_active.isoformat(),
152
+ 'message_count': session.message_count,
153
+ 'is_active': session.is_active,
154
+ 'metadata': session.session_metadata
155
+ })
156
+
157
+ except SessionNotFoundError:
158
+ raise APIError('Session not found', 404)
159
+ except SessionExpiredError:
160
+ raise APIError('Session has expired', 410)
161
+ except SessionManagerError as e:
162
+ logger.error(f"Error getting session: {e}")
163
+ raise APIError(f'Failed to get session: {str(e)}', 500)
164
+ except APIError:
165
+ raise
166
+ except Exception as e:
167
+ logger.error(f"Unexpected error getting session: {e}")
168
+ raise APIError('Internal server error', 500)
169
+
170
+
171
+ @chat_bp.route('/sessions/<session_id>', methods=['DELETE'])
172
+ @limiter.limit("5 per minute")
173
+ @require_auth
174
+ def delete_session(session_id: str):
175
+ """Delete a chat session."""
176
+ try:
177
+ session_manager, chat_history_manager, _ = get_services()
178
+
179
+ # Get session to check ownership
180
+ session = session_manager.get_session(session_id)
181
+
182
+ # Check if user owns this session
183
+ if session.user_id != request.user_id:
184
+ raise APIError('Access denied', 403)
185
+
186
+ # Clear chat history
187
+ message_count = chat_history_manager.clear_session_history(session_id)
188
+
189
+ # Delete session
190
+ session_manager.delete_session(session_id)
191
+
192
+ logger.info(f"Deleted session {session_id} with {message_count} messages")
193
+
194
+ return jsonify({
195
+ 'message': 'Session deleted successfully',
196
+ 'session_id': session_id,
197
+ 'messages_deleted': message_count
198
+ })
199
+
200
+ except SessionNotFoundError:
201
+ raise APIError('Session not found', 404)
202
+ except (SessionManagerError, ChatHistoryError) as e:
203
+ logger.error(f"Error deleting session: {e}")
204
+ raise APIError(f'Failed to delete session: {str(e)}', 500)
205
+ except APIError:
206
+ raise
207
+ except Exception as e:
208
+ logger.error(f"Unexpected error deleting session: {e}")
209
+ raise APIError('Internal server error', 500)
210
+
211
+
212
+ @chat_bp.route('/sessions', methods=['GET'])
213
+ @limiter.limit("20 per minute")
214
+ @require_auth
215
+ def list_user_sessions():
216
+ """List all sessions for the authenticated user."""
217
+ try:
218
+ active_only = request.args.get('active_only', 'true').lower() == 'true'
219
+
220
+ session_manager, _, _ = get_services()
221
+
222
+ sessions = session_manager.get_user_sessions(request.user_id, active_only)
223
+
224
+ session_list = []
225
+ for session in sessions:
226
+ session_list.append({
227
+ 'session_id': session.id,
228
+ 'language': session.language,
229
+ 'created_at': session.created_at.isoformat(),
230
+ 'last_active': session.last_active.isoformat(),
231
+ 'message_count': session.message_count,
232
+ 'is_active': session.is_active,
233
+ 'metadata': session.session_metadata
234
+ })
235
+
236
+ return jsonify({
237
+ 'sessions': session_list,
238
+ 'total_count': len(session_list),
239
+ 'active_only': active_only
240
+ })
241
+
242
+ except SessionManagerError as e:
243
+ logger.error(f"Error listing sessions: {e}")
244
+ raise APIError(f'Failed to list sessions: {str(e)}', 500)
245
+ except Exception as e:
246
+ logger.error(f"Unexpected error listing sessions: {e}")
247
+ raise APIError('Internal server error', 500)
248
+
249
+
250
+ # Chat History Endpoints
251
+
252
+ @chat_bp.route('/sessions/<session_id>/history', methods=['GET'])
253
+ @limiter.limit("30 per minute")
254
+ @require_auth
255
+ def get_chat_history(session_id: str):
256
+ """Get chat history for a session."""
257
+ try:
258
+ # Validate session ownership
259
+ session_manager, chat_history_manager, _ = get_services()
260
+ session = session_manager.get_session(session_id)
261
+
262
+ if session.user_id != request.user_id:
263
+ raise APIError('Access denied', 403)
264
+
265
+ # Get pagination parameters
266
+ page = int(request.args.get('page', 1))
267
+ page_size = min(int(request.args.get('page_size', 50)), 100) # Max 100 messages per page
268
+ recent_only = request.args.get('recent_only', 'false').lower() == 'true'
269
+
270
+ if recent_only:
271
+ # Get recent messages for context
272
+ limit = min(int(request.args.get('limit', 10)), 50) # Max 50 recent messages
273
+ messages = chat_history_manager.get_recent_history(session_id, limit)
274
+ total_count = len(messages)
275
+ else:
276
+ # Get paginated full history
277
+ messages = chat_history_manager.get_full_history(session_id, page, page_size)
278
+ total_count = chat_history_manager.get_message_count(session_id)
279
+
280
+ message_list = []
281
+ for message in messages:
282
+ message_list.append({
283
+ 'id': message.id,
284
+ 'role': message.role,
285
+ 'content': message.content,
286
+ 'language': message.language,
287
+ 'timestamp': message.timestamp.isoformat(),
288
+ 'metadata': message.message_metadata
289
+ })
290
+
291
+ response_data = {
292
+ 'messages': message_list,
293
+ 'session_id': session_id,
294
+ 'total_count': total_count
295
+ }
296
+
297
+ if not recent_only:
298
+ response_data.update({
299
+ 'page': page,
300
+ 'page_size': page_size,
301
+ 'total_pages': (total_count + page_size - 1) // page_size
302
+ })
303
+
304
+ return jsonify(response_data)
305
+
306
+ except SessionNotFoundError:
307
+ raise APIError('Session not found', 404)
308
+ except SessionExpiredError:
309
+ raise APIError('Session has expired', 410)
310
+ except ChatHistoryError as e:
311
+ logger.error(f"Error getting chat history: {e}")
312
+ raise APIError(f'Failed to get chat history: {str(e)}', 500)
313
+ except APIError:
314
+ raise
315
+ except Exception as e:
316
+ logger.error(f"Unexpected error getting chat history: {e}")
317
+ raise APIError('Internal server error', 500)
318
+
319
+
320
+ @chat_bp.route('/sessions/<session_id>/history/search', methods=['GET'])
321
+ @limiter.limit("20 per minute")
322
+ @require_auth
323
+ def search_chat_history(session_id: str):
324
+ """Search chat history for a session."""
325
+ try:
326
+ query = request.args.get('q', '').strip()
327
+ if not query:
328
+ raise APIError('Search query is required', 400)
329
+
330
+ if len(query) < 3:
331
+ raise APIError('Search query must be at least 3 characters', 400)
332
+
333
+ # Validate session ownership
334
+ session_manager, chat_history_manager, _ = get_services()
335
+ session = session_manager.get_session(session_id)
336
+
337
+ if session.user_id != request.user_id:
338
+ raise APIError('Access denied', 403)
339
+
340
+ limit = min(int(request.args.get('limit', 20)), 50) # Max 50 results
341
+
342
+ messages = chat_history_manager.search_messages(session_id, query, limit)
343
+
344
+ message_list = []
345
+ for message in messages:
346
+ message_list.append({
347
+ 'id': message.id,
348
+ 'role': message.role,
349
+ 'content': message.content,
350
+ 'language': message.language,
351
+ 'timestamp': message.timestamp.isoformat(),
352
+ 'metadata': message.message_metadata
353
+ })
354
+
355
+ return jsonify({
356
+ 'messages': message_list,
357
+ 'session_id': session_id,
358
+ 'query': query,
359
+ 'result_count': len(message_list)
360
+ })
361
+
362
+ except SessionNotFoundError:
363
+ raise APIError('Session not found', 404)
364
+ except SessionExpiredError:
365
+ raise APIError('Session has expired', 410)
366
+ except ChatHistoryError as e:
367
+ logger.error(f"Error searching chat history: {e}")
368
+ raise APIError(f'Failed to search chat history: {str(e)}', 500)
369
+ except APIError:
370
+ raise
371
+ except Exception as e:
372
+ logger.error(f"Unexpected error searching chat history: {e}")
373
+ raise APIError('Internal server error', 500)
374
+
375
+
376
+ # Language Context Endpoints
377
+
378
+ @chat_bp.route('/sessions/<session_id>/language', methods=['GET'])
379
+ @limiter.limit("30 per minute")
380
+ @require_auth
381
+ def get_language_context(session_id: str):
382
+ """Get language context for a session."""
383
+ try:
384
+ # Validate session ownership
385
+ session_manager, _, language_context_manager = get_services()
386
+ session = session_manager.get_session(session_id)
387
+
388
+ if session.user_id != request.user_id:
389
+ raise APIError('Access denied', 403)
390
+
391
+ context = language_context_manager.get_context(session_id)
392
+
393
+ return jsonify({
394
+ 'session_id': session_id,
395
+ 'language': context.language,
396
+ 'prompt_template': context.get_prompt_template(),
397
+ 'syntax_highlighting': context.get_syntax_highlighting(),
398
+ 'language_info': context.get_language_info(),
399
+ 'updated_at': context.updated_at.isoformat()
400
+ })
401
+
402
+ except SessionNotFoundError:
403
+ raise APIError('Session not found', 404)
404
+ except SessionExpiredError:
405
+ raise APIError('Session has expired', 410)
406
+ except LanguageContextError as e:
407
+ logger.error(f"Error getting language context: {e}")
408
+ raise APIError(f'Failed to get language context: {str(e)}', 500)
409
+ except APIError:
410
+ raise
411
+ except Exception as e:
412
+ logger.error(f"Unexpected error getting language context: {e}")
413
+ raise APIError('Internal server error', 500)
414
+
415
+
416
+ @chat_bp.route('/sessions/<session_id>/language', methods=['PUT'])
417
+ @limiter.limit("10 per minute")
418
+ @require_auth
419
+ @validate_json_request(['language'])
420
+ def update_language_context(session_id: str):
421
+ """Update language context for a session."""
422
+ try:
423
+ data = request.json_data
424
+ language = data['language']
425
+
426
+ # Validate language
427
+ if not LanguageContext.is_supported_language(language):
428
+ supported = LanguageContext.get_supported_languages()
429
+ raise APIError(f'Unsupported language: {language}. Supported: {", ".join(supported)}', 400)
430
+
431
+ # Validate session ownership
432
+ session_manager, _, language_context_manager = get_services()
433
+ session = session_manager.get_session(session_id)
434
+
435
+ if session.user_id != request.user_id:
436
+ raise APIError('Access denied', 403)
437
+
438
+ # Update language context
439
+ context = language_context_manager.set_language(session_id, language)
440
+
441
+ # Update session language
442
+ session_manager.set_session_language(session_id, language)
443
+
444
+ logger.info(f"Updated language to {language} for session {session_id}")
445
+
446
+ return jsonify({
447
+ 'session_id': session_id,
448
+ 'language': context.language,
449
+ 'prompt_template': context.get_prompt_template(),
450
+ 'syntax_highlighting': context.get_syntax_highlighting(),
451
+ 'language_info': context.get_language_info(),
452
+ 'updated_at': context.updated_at.isoformat()
453
+ })
454
+
455
+ except SessionNotFoundError:
456
+ raise APIError('Session not found', 404)
457
+ except SessionExpiredError:
458
+ raise APIError('Session has expired', 410)
459
+ except (SessionManagerError, LanguageContextError) as e:
460
+ logger.error(f"Error updating language context: {e}")
461
+ raise APIError(f'Failed to update language context: {str(e)}', 500)
462
+ except APIError:
463
+ raise
464
+ except Exception as e:
465
+ logger.error(f"Unexpected error updating language context: {e}")
466
+ raise APIError('Internal server error', 500)
467
+
468
+
469
+ @chat_bp.route('/languages', methods=['GET'])
470
+ @limiter.limit("50 per minute")
471
+ def get_supported_languages():
472
+ """Get list of supported programming languages."""
473
+ try:
474
+ languages = LanguageContext.get_supported_languages()
475
+ language_names = LanguageContext.get_language_display_names()
476
+
477
+ language_list = []
478
+ for lang_code in languages:
479
+ lang_info = LanguageContext.SUPPORTED_LANGUAGES[lang_code]
480
+ language_list.append({
481
+ 'code': lang_code,
482
+ 'name': lang_info['name'],
483
+ 'syntax_highlighting': lang_info['syntax_highlighting'],
484
+ 'file_extensions': lang_info['file_extensions']
485
+ })
486
+
487
+ return jsonify({
488
+ 'languages': language_list,
489
+ 'default_language': 'python',
490
+ 'total_count': len(language_list)
491
+ })
492
+
493
+ except Exception as e:
494
+ logger.error(f"Unexpected error getting supported languages: {e}")
495
+ raise APIError('Internal server error', 500)
496
+
497
+
498
+ # Message Processing Endpoint
499
+
500
+ @chat_bp.route('/sessions/<session_id>/message', methods=['POST'])
501
+ @limiter.limit("30 per minute")
502
+ @require_auth
503
+ @validate_json_request(['content'])
504
+ def send_message(session_id: str):
505
+ """Send a message to the chat agent and get a response."""
506
+ try:
507
+ data = request.json_data
508
+ content = data['content'].strip()
509
+ language = data.get('language') # Optional language override
510
+
511
+ if not content:
512
+ raise APIError('Message content cannot be empty', 400)
513
+
514
+ if len(content) > 5000: # Reasonable message length limit
515
+ raise APIError('Message too long (max 5000 characters)', 400)
516
+
517
+ # Get services
518
+ session_manager, chat_history_manager, language_context_manager = get_services()
519
+
520
+ # Validate session ownership
521
+ session = session_manager.get_session(session_id)
522
+ if session.user_id != request.user_id:
523
+ raise APIError('Access denied', 403)
524
+
525
+ # Initialize chat agent
526
+ from ..services.groq_client import GroqClient
527
+ from ..services.chat_agent import ChatAgent
528
+ from ..services.programming_assistance import ProgrammingAssistanceService
529
+
530
+ groq_client = GroqClient()
531
+ programming_assistance_service = ProgrammingAssistanceService()
532
+
533
+ chat_agent = ChatAgent(
534
+ groq_client=groq_client,
535
+ language_context_manager=language_context_manager,
536
+ session_manager=session_manager,
537
+ chat_history_manager=chat_history_manager,
538
+ programming_assistance_service=programming_assistance_service
539
+ )
540
+
541
+ # Process the message
542
+ result = chat_agent.process_message(session_id, content, language)
543
+
544
+ logger.info(f"Processed message for session {session_id}, response length: {len(result['response'])}")
545
+
546
+ return jsonify({
547
+ 'response': result['response'],
548
+ 'message_id': result['message_id'],
549
+ 'session_id': session_id,
550
+ 'language': result['language'],
551
+ 'processing_time': result['processing_time'],
552
+ 'timestamp': result['timestamp']
553
+ })
554
+
555
+ except SessionNotFoundError:
556
+ raise APIError('Session not found', 404)
557
+ except SessionExpiredError:
558
+ raise APIError('Session has expired', 410)
559
+ except ChatAgentError as e:
560
+ logger.error(f"Chat agent error: {e}")
561
+ raise APIError(f'Failed to process message: {str(e)}', 500)
562
+ except APIError:
563
+ raise
564
+ except Exception as e:
565
+ logger.error(f"Unexpected error processing message: {e}")
566
+ raise APIError('Internal server error', 500)
567
+
568
+
569
+ # Health Check Endpoint
570
+
571
+ @chat_bp.route('/health', methods=['GET'])
572
+ def health_check():
573
+ """Health check endpoint for monitoring."""
574
+ try:
575
+ # Check database connection
576
+ from sqlalchemy import text
577
+ db.session.execute(text('SELECT 1'))
578
+
579
+ # Check Redis connection (if configured)
580
+ redis_status = "disabled"
581
+ redis_url = current_app.config.get('REDIS_URL')
582
+ if redis_url and redis_url != 'None':
583
+ try:
584
+ redis_client = redis.from_url(redis_url)
585
+ redis_client.ping()
586
+ redis_status = "connected"
587
+ except Exception:
588
+ redis_status = "disconnected"
589
+ else:
590
+ redis_status = "disabled"
591
+
592
+ return jsonify({
593
+ 'status': 'healthy',
594
+ 'timestamp': datetime.utcnow().isoformat(),
595
+ 'services': {
596
+ 'database': 'connected',
597
+ 'redis': redis_status
598
+ }
599
+ })
600
+
601
+ except Exception as e:
602
+ logger.error(f"Health check failed: {e}")
603
+ return jsonify({
604
+ 'status': 'unhealthy',
605
+ 'timestamp': datetime.utcnow().isoformat(),
606
+ 'error': str(e)
607
+ }), 503
608
+
609
+
610
+ # Error handlers
611
+ @chat_bp.errorhandler(APIError)
612
+ def handle_api_error_handler(error):
613
+ """Handle APIError exceptions."""
614
+ return handle_api_error(error)
615
+
616
+
617
+ @chat_bp.errorhandler(400)
618
+ def handle_bad_request(error):
619
+ """Handle bad request errors."""
620
+ return jsonify({'error': 'Bad request'}), 400
621
+
622
+
623
+ @chat_bp.errorhandler(404)
624
+ def handle_not_found(error):
625
+ """Handle not found errors."""
626
+ return jsonify({'error': 'Not found'}), 404
627
+
628
+
629
+ @chat_bp.errorhandler(429)
630
+ def handle_rate_limit_exceeded(error):
631
+ """Handle rate limit exceeded errors."""
632
+ return jsonify({
633
+ 'error': 'Rate limit exceeded',
634
+ 'message': 'Too many requests. Please try again later.'
635
+ }), 429
636
+
637
+
638
+ @chat_bp.errorhandler(500)
639
+ def handle_internal_error(error):
640
+ """Handle internal server errors."""
641
+ logger.error(f"Internal server error: {error}")
642
+ return jsonify({'error': 'Internal server error'}), 500
chat_agent/api/health.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Health check endpoints for monitoring and load balancing."""
2
+
3
+ import time
4
+ import psutil
5
+ from datetime import datetime
6
+ from flask import Blueprint, jsonify, current_app
7
+ import redis
8
+ import psycopg2
9
+ from sqlalchemy import text
10
+
11
+ from chat_agent.models.base import db
12
+ from chat_agent.utils.error_handler import get_error_handler
13
+
14
+ health_bp = Blueprint('health', __name__, url_prefix='/health')
15
+
16
+
17
+ def check_database():
18
+ """Check database connectivity and basic operations."""
19
+ try:
20
+ # Test basic database connection
21
+ result = db.session.execute(text('SELECT 1'))
22
+ result.fetchone()
23
+
24
+ # Test if migrations table exists (indicates proper setup)
25
+ result = db.session.execute(text(
26
+ "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'schema_migrations'"
27
+ ))
28
+ migrations_table_exists = result.fetchone()[0] > 0
29
+
30
+ return {
31
+ 'status': 'healthy',
32
+ 'connection': 'ok',
33
+ 'migrations_table': 'exists' if migrations_table_exists else 'missing',
34
+ 'response_time_ms': 0 # Will be calculated by caller
35
+ }
36
+ except Exception as e:
37
+ return {
38
+ 'status': 'unhealthy',
39
+ 'error': str(e),
40
+ 'connection': 'failed'
41
+ }
42
+
43
+
44
+ def check_redis():
45
+ """Check Redis connectivity and basic operations."""
46
+ redis_url = current_app.config.get('REDIS_URL')
47
+ if not redis_url or redis_url == 'None':
48
+ return {
49
+ 'status': 'disabled',
50
+ 'message': 'Redis is disabled in configuration'
51
+ }
52
+
53
+ try:
54
+ redis_client = redis.from_url(redis_url)
55
+
56
+ # Test basic operations
57
+ start_time = time.time()
58
+ redis_client.ping()
59
+ response_time = (time.time() - start_time) * 1000
60
+
61
+ # Test set/get operation
62
+ test_key = 'health_check_test'
63
+ redis_client.set(test_key, 'test_value', ex=10)
64
+ value = redis_client.get(test_key)
65
+ redis_client.delete(test_key)
66
+
67
+ return {
68
+ 'status': 'healthy',
69
+ 'connection': 'ok',
70
+ 'response_time_ms': round(response_time, 2),
71
+ 'operations': 'ok' if value == b'test_value' else 'failed'
72
+ }
73
+ except Exception as e:
74
+ return {
75
+ 'status': 'unhealthy',
76
+ 'error': str(e),
77
+ 'connection': 'failed'
78
+ }
79
+
80
+
81
+ def check_groq_api():
82
+ """Check Groq API configuration and basic connectivity."""
83
+ groq_api_key = current_app.config.get('GROQ_API_KEY')
84
+
85
+ if not groq_api_key:
86
+ return {
87
+ 'status': 'unhealthy',
88
+ 'error': 'GROQ_API_KEY not configured'
89
+ }
90
+
91
+ # Basic configuration check
92
+ return {
93
+ 'status': 'configured',
94
+ 'api_key_present': bool(groq_api_key),
95
+ 'model': current_app.config.get('GROQ_MODEL', 'not_configured'),
96
+ 'note': 'API connectivity not tested in health check to avoid quota usage'
97
+ }
98
+
99
+
100
+ def get_system_metrics():
101
+ """Get basic system metrics."""
102
+ try:
103
+ return {
104
+ 'cpu_percent': psutil.cpu_percent(interval=1),
105
+ 'memory_percent': psutil.virtual_memory().percent,
106
+ 'disk_percent': psutil.disk_usage('/').percent,
107
+ 'load_average': psutil.getloadavg()[0] if hasattr(psutil, 'getloadavg') else None
108
+ }
109
+ except Exception as e:
110
+ return {
111
+ 'error': f'Failed to get system metrics: {str(e)}'
112
+ }
113
+
114
+
115
+ @health_bp.route('/')
116
+ @health_bp.route('/basic')
117
+ def basic_health():
118
+ """Basic health check endpoint for load balancers."""
119
+ return jsonify({
120
+ 'status': 'healthy',
121
+ 'timestamp': datetime.utcnow().isoformat(),
122
+ 'service': 'chat-agent',
123
+ 'version': '1.0.0'
124
+ }), 200
125
+
126
+
127
+ @health_bp.route('/detailed')
128
+ def detailed_health():
129
+ """Detailed health check with all dependencies."""
130
+ start_time = time.time()
131
+
132
+ # Check all components
133
+ db_start = time.time()
134
+ database_health = check_database()
135
+ database_health['response_time_ms'] = round((time.time() - db_start) * 1000, 2)
136
+
137
+ redis_health = check_redis()
138
+ groq_health = check_groq_api()
139
+ system_metrics = get_system_metrics()
140
+
141
+ # Determine overall status
142
+ overall_status = 'healthy'
143
+ if database_health['status'] == 'unhealthy':
144
+ overall_status = 'unhealthy'
145
+ elif redis_health['status'] == 'unhealthy':
146
+ overall_status = 'degraded' # Redis failure is not critical
147
+ elif groq_health['status'] == 'unhealthy':
148
+ overall_status = 'degraded' # Can still serve static content
149
+
150
+ response = {
151
+ 'status': overall_status,
152
+ 'timestamp': datetime.utcnow().isoformat(),
153
+ 'service': 'chat-agent',
154
+ 'version': '1.0.0',
155
+ 'uptime_seconds': round(time.time() - start_time, 2),
156
+ 'components': {
157
+ 'database': database_health,
158
+ 'redis': redis_health,
159
+ 'groq_api': groq_health
160
+ },
161
+ 'system': system_metrics,
162
+ 'config': {
163
+ 'environment': current_app.config.get('FLASK_ENV', 'unknown'),
164
+ 'debug': current_app.config.get('DEBUG', False),
165
+ 'default_language': current_app.config.get('DEFAULT_LANGUAGE', 'python')
166
+ }
167
+ }
168
+
169
+ # Return appropriate HTTP status code
170
+ status_code = 200
171
+ if overall_status == 'unhealthy':
172
+ status_code = 503
173
+ elif overall_status == 'degraded':
174
+ status_code = 200 # Still functional
175
+
176
+ return jsonify(response), status_code
177
+
178
+
179
+ @health_bp.route('/ready')
180
+ def readiness():
181
+ """Readiness probe for Kubernetes/container orchestration."""
182
+ # Check critical dependencies only
183
+ db_health = check_database()
184
+
185
+ if db_health['status'] == 'healthy':
186
+ return jsonify({
187
+ 'status': 'ready',
188
+ 'timestamp': datetime.utcnow().isoformat(),
189
+ 'database': 'connected'
190
+ }), 200
191
+ else:
192
+ return jsonify({
193
+ 'status': 'not_ready',
194
+ 'timestamp': datetime.utcnow().isoformat(),
195
+ 'database': 'disconnected',
196
+ 'error': db_health.get('error', 'Database check failed')
197
+ }), 503
198
+
199
+
200
+ @health_bp.route('/live')
201
+ def liveness():
202
+ """Liveness probe for Kubernetes/container orchestration."""
203
+ # Simple check that the application is running
204
+ return jsonify({
205
+ 'status': 'alive',
206
+ 'timestamp': datetime.utcnow().isoformat(),
207
+ 'service': 'chat-agent'
208
+ }), 200
209
+
210
+
211
+ @health_bp.route('/metrics')
212
+ def metrics():
213
+ """Basic metrics endpoint for monitoring systems."""
214
+ system_metrics = get_system_metrics()
215
+
216
+ # Add application-specific metrics
217
+ app_metrics = {
218
+ 'active_sessions': 0, # TODO: Implement session counting
219
+ 'total_messages': 0, # TODO: Implement message counting
220
+ 'cache_hit_rate': 0.0 # TODO: Implement cache metrics
221
+ }
222
+
223
+ return jsonify({
224
+ 'timestamp': datetime.utcnow().isoformat(),
225
+ 'system': system_metrics,
226
+ 'application': app_metrics
227
+ }), 200
228
+
229
+
230
+ # Error handler for health check blueprint
231
+ @health_bp.errorhandler(Exception)
232
+ def handle_health_error(error):
233
+ """Handle errors in health check endpoints."""
234
+ error_handler = get_error_handler()
235
+ if error_handler:
236
+ error_handler.handle_error(error, context="health_check")
237
+
238
+ return jsonify({
239
+ 'status': 'error',
240
+ 'timestamp': datetime.utcnow().isoformat(),
241
+ 'error': 'Health check failed',
242
+ 'message': str(error)
243
+ }), 500
chat_agent/api/middleware.py ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Middleware for authentication, authorization, and rate limiting."""
2
+
3
+ import logging
4
+ import time
5
+ from functools import wraps
6
+ from typing import Dict, Any, Optional
7
+
8
+ from flask import request, jsonify, current_app, g
9
+ from flask_limiter import Limiter
10
+ from flask_limiter.util import get_remote_address
11
+ import redis
12
+ import jwt
13
+ from datetime import datetime, timedelta
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class AuthenticationError(Exception):
19
+ """Authentication related errors."""
20
+ pass
21
+
22
+
23
+ class AuthorizationError(Exception):
24
+ """Authorization related errors."""
25
+ pass
26
+
27
+
28
+ class RateLimitError(Exception):
29
+ """Rate limiting related errors."""
30
+ pass
31
+
32
+
33
+ def create_limiter(app=None):
34
+ """Create and configure rate limiter."""
35
+ limiter = Limiter(
36
+ key_func=get_remote_address,
37
+ default_limits=["200 per day", "50 per hour"],
38
+ storage_uri=None # Will be set from app config
39
+ )
40
+
41
+ if app:
42
+ limiter.init_app(app)
43
+
44
+ return limiter
45
+
46
+
47
+ class SimpleAuthManager:
48
+ """
49
+ Simple authentication manager for development/testing.
50
+ In production, this would be replaced with proper JWT/OAuth implementation.
51
+ """
52
+
53
+ def __init__(self, redis_client: Optional[redis.Redis] = None):
54
+ """Initialize auth manager."""
55
+ self.redis_client = redis_client
56
+ self.session_prefix = "auth_session:"
57
+ self.user_prefix = "user:"
58
+
59
+ def create_session_token(self, user_id: str, expires_in: int = 3600) -> str:
60
+ """
61
+ Create a simple session token for a user.
62
+
63
+ Args:
64
+ user_id: User identifier
65
+ expires_in: Token expiration in seconds
66
+
67
+ Returns:
68
+ str: Session token
69
+ """
70
+ try:
71
+ # Create a simple token (in production, use proper JWT)
72
+ token_data = {
73
+ 'user_id': user_id,
74
+ 'created_at': time.time(),
75
+ 'expires_at': time.time() + expires_in
76
+ }
77
+
78
+ # For simplicity, use user_id as token (in production, use secure random token)
79
+ token = f"session_{user_id}_{int(time.time())}"
80
+
81
+ if self.redis_client:
82
+ # Store token in Redis
83
+ self.redis_client.setex(
84
+ f"{self.session_prefix}{token}",
85
+ expires_in,
86
+ user_id
87
+ )
88
+
89
+ return token
90
+
91
+ except Exception as e:
92
+ logger.error(f"Error creating session token: {e}")
93
+ raise AuthenticationError(f"Failed to create session token: {e}")
94
+
95
+ def validate_session_token(self, token: str) -> Optional[str]:
96
+ """
97
+ Validate a session token and return user_id if valid.
98
+
99
+ Args:
100
+ token: Session token to validate
101
+
102
+ Returns:
103
+ str: User ID if token is valid, None otherwise
104
+ """
105
+ try:
106
+ if not token:
107
+ return None
108
+
109
+ if self.redis_client:
110
+ # Check Redis for token
111
+ user_id = self.redis_client.get(f"{self.session_prefix}{token}")
112
+ if user_id:
113
+ return user_id.decode('utf-8')
114
+
115
+ # Fallback: simple token validation (for development)
116
+ if token.startswith('session_'):
117
+ parts = token.split('_')
118
+ if len(parts) >= 2:
119
+ return parts[1] # Return user_id part
120
+
121
+ return None
122
+
123
+ except Exception as e:
124
+ logger.error(f"Error validating session token: {e}")
125
+ return None
126
+
127
+ def revoke_session_token(self, token: str) -> bool:
128
+ """
129
+ Revoke a session token.
130
+
131
+ Args:
132
+ token: Session token to revoke
133
+
134
+ Returns:
135
+ bool: True if token was revoked, False otherwise
136
+ """
137
+ try:
138
+ if self.redis_client:
139
+ result = self.redis_client.delete(f"{self.session_prefix}{token}")
140
+ return result > 0
141
+
142
+ return True # For development, always return True
143
+
144
+ except Exception as e:
145
+ logger.error(f"Error revoking session token: {e}")
146
+ return False
147
+
148
+
149
+ def require_auth(f):
150
+ """
151
+ Authentication decorator for API endpoints.
152
+ Supports both header-based authentication and session tokens.
153
+ """
154
+ @wraps(f)
155
+ def decorated_function(*args, **kwargs):
156
+ try:
157
+ # Check for session token first
158
+ auth_header = request.headers.get('Authorization')
159
+ if auth_header and auth_header.startswith('Bearer '):
160
+ token = auth_header.split(' ')[1]
161
+
162
+ # Get auth manager from app context
163
+ redis_client = redis.from_url(current_app.config['REDIS_URL'])
164
+ auth_manager = SimpleAuthManager(redis_client)
165
+
166
+ user_id = auth_manager.validate_session_token(token)
167
+ if user_id:
168
+ g.user_id = user_id
169
+ request.user_id = user_id
170
+ return f(*args, **kwargs)
171
+
172
+ # Fallback to simple header-based auth (for development)
173
+ user_id = request.headers.get('X-User-ID')
174
+ if user_id:
175
+ g.user_id = user_id
176
+ request.user_id = user_id
177
+ return f(*args, **kwargs)
178
+
179
+ # No valid authentication found
180
+ return jsonify({
181
+ 'error': 'Authentication required',
182
+ 'message': 'Please provide a valid Authorization header or X-User-ID header'
183
+ }), 401
184
+
185
+ except Exception as e:
186
+ logger.error(f"Authentication error: {e}")
187
+ return jsonify({
188
+ 'error': 'Authentication failed',
189
+ 'message': 'Invalid authentication credentials'
190
+ }), 401
191
+
192
+ return decorated_function
193
+
194
+
195
+ def require_session_ownership(f):
196
+ """
197
+ Authorization decorator to ensure user owns the session.
198
+ Must be used after require_auth.
199
+ """
200
+ @wraps(f)
201
+ def decorated_function(*args, **kwargs):
202
+ try:
203
+ session_id = kwargs.get('session_id') or request.view_args.get('session_id')
204
+ if not session_id:
205
+ return jsonify({
206
+ 'error': 'Session ID required',
207
+ 'message': 'Session ID must be provided in the URL'
208
+ }), 400
209
+
210
+ user_id = getattr(g, 'user_id', None) or getattr(request, 'user_id', None)
211
+ if not user_id:
212
+ return jsonify({
213
+ 'error': 'User not authenticated',
214
+ 'message': 'User authentication required'
215
+ }), 401
216
+
217
+ # Import here to avoid circular imports
218
+ from ..services.session_manager import SessionManager, SessionNotFoundError
219
+
220
+ redis_client = redis.from_url(current_app.config['REDIS_URL'])
221
+ session_manager = SessionManager(redis_client)
222
+
223
+ try:
224
+ session = session_manager.get_session(session_id)
225
+ if session.user_id != user_id:
226
+ return jsonify({
227
+ 'error': 'Access denied',
228
+ 'message': 'You do not have permission to access this session'
229
+ }), 403
230
+
231
+ # Store session in request context for use in endpoint
232
+ g.session = session
233
+ request.session = session
234
+
235
+ except SessionNotFoundError:
236
+ return jsonify({
237
+ 'error': 'Session not found',
238
+ 'message': f'Session {session_id} does not exist'
239
+ }), 404
240
+
241
+ return f(*args, **kwargs)
242
+
243
+ except Exception as e:
244
+ logger.error(f"Authorization error: {e}")
245
+ return jsonify({
246
+ 'error': 'Authorization failed',
247
+ 'message': 'Failed to verify session ownership'
248
+ }), 500
249
+
250
+ return decorated_function
251
+
252
+
253
+ def validate_json_request(required_fields: list = None, optional_fields: list = None):
254
+ """
255
+ Decorator to validate JSON request data.
256
+
257
+ Args:
258
+ required_fields: List of required field names
259
+ optional_fields: List of optional field names (for documentation)
260
+ """
261
+ def decorator(f):
262
+ @wraps(f)
263
+ def decorated_function(*args, **kwargs):
264
+ if not request.is_json:
265
+ return jsonify({
266
+ 'error': 'Invalid content type',
267
+ 'message': 'Request must be JSON'
268
+ }), 400
269
+
270
+ try:
271
+ data = request.get_json()
272
+ except Exception as e:
273
+ return jsonify({
274
+ 'error': 'Invalid JSON',
275
+ 'message': f'Failed to parse JSON: {str(e)}'
276
+ }), 400
277
+
278
+ if not data:
279
+ return jsonify({
280
+ 'error': 'Empty request body',
281
+ 'message': 'Request body cannot be empty'
282
+ }), 400
283
+
284
+ if required_fields:
285
+ missing_fields = [field for field in required_fields if field not in data]
286
+ if missing_fields:
287
+ return jsonify({
288
+ 'error': 'Missing required fields',
289
+ 'message': f'Required fields: {", ".join(missing_fields)}',
290
+ 'missing_fields': missing_fields
291
+ }), 400
292
+
293
+ # Store validated data in request context
294
+ g.json_data = data
295
+ request.json_data = data
296
+
297
+ return f(*args, **kwargs)
298
+
299
+ return decorated_function
300
+ return decorator
301
+
302
+
303
+ def handle_rate_limit_exceeded(e):
304
+ """Handle rate limit exceeded errors."""
305
+ return jsonify({
306
+ 'error': 'Rate limit exceeded',
307
+ 'message': 'Too many requests. Please try again later.',
308
+ 'retry_after': getattr(e, 'retry_after', None)
309
+ }), 429
310
+
311
+
312
+ def setup_error_handlers(app):
313
+ """Setup error handlers for the application."""
314
+
315
+ @app.errorhandler(AuthenticationError)
316
+ def handle_auth_error(error):
317
+ return jsonify({
318
+ 'error': 'Authentication failed',
319
+ 'message': str(error)
320
+ }), 401
321
+
322
+ @app.errorhandler(AuthorizationError)
323
+ def handle_authz_error(error):
324
+ return jsonify({
325
+ 'error': 'Authorization failed',
326
+ 'message': str(error)
327
+ }), 403
328
+
329
+ @app.errorhandler(429)
330
+ def handle_rate_limit(error):
331
+ return handle_rate_limit_exceeded(error)
332
+
333
+ @app.errorhandler(400)
334
+ def handle_bad_request(error):
335
+ return jsonify({
336
+ 'error': 'Bad request',
337
+ 'message': 'The request could not be understood by the server'
338
+ }), 400
339
+
340
+ @app.errorhandler(404)
341
+ def handle_not_found(error):
342
+ return jsonify({
343
+ 'error': 'Not found',
344
+ 'message': 'The requested resource was not found'
345
+ }), 404
346
+
347
+ @app.errorhandler(500)
348
+ def handle_internal_error(error):
349
+ logger.error(f"Internal server error: {error}")
350
+ return jsonify({
351
+ 'error': 'Internal server error',
352
+ 'message': 'An unexpected error occurred'
353
+ }), 500
354
+
355
+
356
+ class RequestLoggingMiddleware:
357
+ """Middleware for logging API requests."""
358
+
359
+ def __init__(self, app=None):
360
+ self.app = app
361
+ if app:
362
+ self.init_app(app)
363
+
364
+ def init_app(self, app):
365
+ """Initialize the middleware with the Flask app."""
366
+ app.before_request(self.log_request)
367
+ app.after_request(self.log_response)
368
+
369
+ def log_request(self):
370
+ """Log incoming requests."""
371
+ if request.endpoint and not request.endpoint.startswith('static'):
372
+ logger.info(f"API Request: {request.method} {request.path} from {request.remote_addr}")
373
+
374
+ # Log request data for debugging (be careful with sensitive data)
375
+ if request.is_json and current_app.debug:
376
+ try:
377
+ data = request.get_json()
378
+ # Remove sensitive fields before logging
379
+ safe_data = {k: v for k, v in data.items() if k not in ['password', 'token', 'secret']}
380
+ logger.debug(f"Request data: {safe_data}")
381
+ except:
382
+ pass
383
+
384
+ def log_response(self, response):
385
+ """Log outgoing responses."""
386
+ if request.endpoint and not request.endpoint.startswith('static'):
387
+ logger.info(f"API Response: {response.status_code} for {request.method} {request.path}")
388
+
389
+ return response
390
+
391
+
392
+ def create_auth_manager(redis_client: redis.Redis) -> SimpleAuthManager:
393
+ """
394
+ Factory function to create an authentication manager.
395
+
396
+ Args:
397
+ redis_client: Redis client instance
398
+
399
+ Returns:
400
+ SimpleAuthManager: Configured auth manager instance
401
+ """
402
+ return SimpleAuthManager(redis_client)
chat_agent/api/performance_routes.py ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Performance monitoring API routes.
3
+
4
+ This module provides endpoints for monitoring system performance,
5
+ connection pool status, cache statistics, and other performance metrics.
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime
10
+ from typing import Dict, Any
11
+
12
+ from flask import Blueprint, jsonify, current_app
13
+ from flask_limiter import Limiter
14
+
15
+ from ..utils.connection_pool import get_connection_pool_manager
16
+ from ..services.cache_service import get_cache_service
17
+ from .middleware import require_auth, create_limiter
18
+ from ..utils.response_optimization import cache_response, compress_response
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Create blueprint
23
+ performance_bp = Blueprint('performance', __name__, url_prefix='/api/v1/performance')
24
+
25
+ # Initialize rate limiter
26
+ limiter = create_limiter()
27
+
28
+
29
+ @performance_bp.route('/status', methods=['GET'])
30
+ @limiter.limit("30 per minute")
31
+ @cache_response(max_age=60, cache_type='private') # Cache for 1 minute
32
+ @compress_response
33
+ def get_performance_status():
34
+ """
35
+ Get overall system performance status.
36
+
37
+ Returns:
38
+ JSON response with performance metrics
39
+ """
40
+ try:
41
+ status = {
42
+ 'timestamp': datetime.utcnow().isoformat(),
43
+ 'status': 'healthy',
44
+ 'services': {}
45
+ }
46
+
47
+ # Database connection pool status
48
+ connection_pool_manager = get_connection_pool_manager()
49
+ if connection_pool_manager:
50
+ pool_status = connection_pool_manager.get_status()
51
+ status['services']['database'] = {
52
+ 'status': 'connected',
53
+ 'pool_stats': pool_status['database']
54
+ }
55
+
56
+ if pool_status['redis']:
57
+ status['services']['redis'] = {
58
+ 'status': 'connected' if pool_status['redis']['healthy'] else 'unhealthy',
59
+ 'pool_stats': pool_status['redis']
60
+ }
61
+ else:
62
+ status['services']['redis'] = {
63
+ 'status': 'disabled'
64
+ }
65
+ else:
66
+ status['services']['database'] = {'status': 'no_pool_manager'}
67
+ status['services']['redis'] = {'status': 'no_pool_manager'}
68
+
69
+ # Cache service status
70
+ cache_service = get_cache_service()
71
+ if cache_service:
72
+ cache_stats = cache_service.get_cache_stats()
73
+ status['services']['cache'] = {
74
+ 'status': 'enabled' if cache_stats['redis_enabled'] else 'disabled',
75
+ 'stats': cache_stats
76
+ }
77
+ else:
78
+ status['services']['cache'] = {'status': 'not_initialized'}
79
+
80
+ # Application configuration
81
+ status['configuration'] = {
82
+ 'compression_enabled': current_app.config.get('ENABLE_COMPRESSION', False),
83
+ 'caching_enabled': current_app.config.get('ENABLE_CACHING', False),
84
+ 'performance_monitoring': current_app.config.get('ENABLE_PERFORMANCE_MONITORING', False),
85
+ 'db_pool_size': current_app.config.get('DB_POOL_SIZE', 10),
86
+ 'redis_max_connections': current_app.config.get('REDIS_MAX_CONNECTIONS', 20)
87
+ }
88
+
89
+ return jsonify(status)
90
+
91
+ except Exception as e:
92
+ logger.error(f"Error getting performance status: {e}")
93
+ return jsonify({
94
+ 'timestamp': datetime.utcnow().isoformat(),
95
+ 'status': 'error',
96
+ 'error': str(e)
97
+ }), 500
98
+
99
+
100
+ @performance_bp.route('/database', methods=['GET'])
101
+ @limiter.limit("20 per minute")
102
+ @require_auth
103
+ @cache_response(max_age=30, cache_type='private')
104
+ @compress_response
105
+ def get_database_performance():
106
+ """
107
+ Get database performance metrics.
108
+
109
+ Returns:
110
+ JSON response with database performance data
111
+ """
112
+ try:
113
+ connection_pool_manager = get_connection_pool_manager()
114
+
115
+ if not connection_pool_manager:
116
+ return jsonify({
117
+ 'error': 'Connection pool manager not available'
118
+ }), 503
119
+
120
+ # Get database pool status
121
+ pool_status = connection_pool_manager.get_status()
122
+ database_stats = pool_status['database']
123
+
124
+ # Calculate pool utilization
125
+ total_connections = database_stats['pool_size'] + database_stats['overflow']
126
+ active_connections = database_stats['checked_out']
127
+ utilization = (active_connections / total_connections * 100) if total_connections > 0 else 0
128
+
129
+ response_data = {
130
+ 'timestamp': datetime.utcnow().isoformat(),
131
+ 'pool_stats': database_stats,
132
+ 'utilization': {
133
+ 'active_connections': active_connections,
134
+ 'total_connections': total_connections,
135
+ 'utilization_percent': round(utilization, 2)
136
+ },
137
+ 'health': {
138
+ 'status': 'healthy' if utilization < 80 else 'warning' if utilization < 95 else 'critical',
139
+ 'invalid_connections': database_stats['invalid']
140
+ }
141
+ }
142
+
143
+ return jsonify(response_data)
144
+
145
+ except Exception as e:
146
+ logger.error(f"Error getting database performance: {e}")
147
+ return jsonify({
148
+ 'timestamp': datetime.utcnow().isoformat(),
149
+ 'error': str(e)
150
+ }), 500
151
+
152
+
153
+ @performance_bp.route('/redis', methods=['GET'])
154
+ @limiter.limit("20 per minute")
155
+ @require_auth
156
+ @cache_response(max_age=30, cache_type='private')
157
+ @compress_response
158
+ def get_redis_performance():
159
+ """
160
+ Get Redis performance metrics.
161
+
162
+ Returns:
163
+ JSON response with Redis performance data
164
+ """
165
+ try:
166
+ connection_pool_manager = get_connection_pool_manager()
167
+
168
+ if not connection_pool_manager:
169
+ return jsonify({
170
+ 'error': 'Connection pool manager not available'
171
+ }), 503
172
+
173
+ redis_client = connection_pool_manager.get_redis_client()
174
+
175
+ if not redis_client:
176
+ return jsonify({
177
+ 'status': 'disabled',
178
+ 'message': 'Redis is not configured'
179
+ })
180
+
181
+ # Get Redis pool status
182
+ pool_status = connection_pool_manager.get_status()
183
+ redis_stats = pool_status['redis']
184
+
185
+ # Get Redis server info
186
+ try:
187
+ redis_info = redis_client.info()
188
+ server_stats = {
189
+ 'version': redis_info.get('redis_version', 'unknown'),
190
+ 'uptime_seconds': redis_info.get('uptime_in_seconds', 0),
191
+ 'connected_clients': redis_info.get('connected_clients', 0),
192
+ 'used_memory': redis_info.get('used_memory_human', 'unknown'),
193
+ 'keyspace_hits': redis_info.get('keyspace_hits', 0),
194
+ 'keyspace_misses': redis_info.get('keyspace_misses', 0),
195
+ 'total_commands_processed': redis_info.get('total_commands_processed', 0)
196
+ }
197
+
198
+ # Calculate hit rate
199
+ total_keyspace_ops = server_stats['keyspace_hits'] + server_stats['keyspace_misses']
200
+ hit_rate = (server_stats['keyspace_hits'] / total_keyspace_ops * 100) if total_keyspace_ops > 0 else 0
201
+
202
+ except Exception as e:
203
+ logger.warning(f"Failed to get Redis server info: {e}")
204
+ server_stats = {'error': 'Failed to get server info'}
205
+ hit_rate = 0
206
+
207
+ response_data = {
208
+ 'timestamp': datetime.utcnow().isoformat(),
209
+ 'pool_stats': redis_stats,
210
+ 'server_stats': server_stats,
211
+ 'performance': {
212
+ 'hit_rate_percent': round(hit_rate, 2),
213
+ 'health_status': redis_stats.get('healthy', False)
214
+ }
215
+ }
216
+
217
+ return jsonify(response_data)
218
+
219
+ except Exception as e:
220
+ logger.error(f"Error getting Redis performance: {e}")
221
+ return jsonify({
222
+ 'timestamp': datetime.utcnow().isoformat(),
223
+ 'error': str(e)
224
+ }), 500
225
+
226
+
227
+ @performance_bp.route('/cache', methods=['GET'])
228
+ @limiter.limit("20 per minute")
229
+ @require_auth
230
+ @cache_response(max_age=60, cache_type='private')
231
+ @compress_response
232
+ def get_cache_performance():
233
+ """
234
+ Get cache performance metrics.
235
+
236
+ Returns:
237
+ JSON response with cache performance data
238
+ """
239
+ try:
240
+ cache_service = get_cache_service()
241
+
242
+ if not cache_service:
243
+ return jsonify({
244
+ 'status': 'not_initialized',
245
+ 'message': 'Cache service not initialized'
246
+ })
247
+
248
+ cache_stats = cache_service.get_cache_stats()
249
+
250
+ # Add performance analysis
251
+ performance_analysis = {
252
+ 'efficiency': 'excellent' if cache_stats['hit_rate_percent'] >= 80 else
253
+ 'good' if cache_stats['hit_rate_percent'] >= 60 else
254
+ 'fair' if cache_stats['hit_rate_percent'] >= 40 else 'poor',
255
+ 'recommendations': []
256
+ }
257
+
258
+ if cache_stats['hit_rate_percent'] < 60:
259
+ performance_analysis['recommendations'].append('Consider increasing cache TTL')
260
+ performance_analysis['recommendations'].append('Review cache key strategies')
261
+
262
+ if cache_stats['cache_errors'] > 0:
263
+ performance_analysis['recommendations'].append('Investigate cache errors')
264
+
265
+ response_data = {
266
+ 'timestamp': datetime.utcnow().isoformat(),
267
+ 'cache_stats': cache_stats,
268
+ 'performance_analysis': performance_analysis
269
+ }
270
+
271
+ return jsonify(response_data)
272
+
273
+ except Exception as e:
274
+ logger.error(f"Error getting cache performance: {e}")
275
+ return jsonify({
276
+ 'timestamp': datetime.utcnow().isoformat(),
277
+ 'error': str(e)
278
+ }), 500
279
+
280
+
281
+ @performance_bp.route('/metrics', methods=['GET'])
282
+ @limiter.limit("10 per minute")
283
+ @require_auth
284
+ @cache_response(max_age=120, cache_type='private') # Cache for 2 minutes
285
+ @compress_response
286
+ def get_comprehensive_metrics():
287
+ """
288
+ Get comprehensive performance metrics.
289
+
290
+ Returns:
291
+ JSON response with all performance metrics
292
+ """
293
+ try:
294
+ metrics = {
295
+ 'timestamp': datetime.utcnow().isoformat(),
296
+ 'system': {},
297
+ 'database': {},
298
+ 'redis': {},
299
+ 'cache': {},
300
+ 'application': {}
301
+ }
302
+
303
+ # System metrics
304
+ try:
305
+ import psutil
306
+ import os
307
+
308
+ process = psutil.Process(os.getpid())
309
+ cpu_percent = process.cpu_percent()
310
+ memory_info = process.memory_info()
311
+
312
+ metrics['system'] = {
313
+ 'cpu_percent': cpu_percent,
314
+ 'memory_rss_mb': round(memory_info.rss / 1024 / 1024, 2),
315
+ 'memory_vms_mb': round(memory_info.vms / 1024 / 1024, 2),
316
+ 'num_threads': process.num_threads(),
317
+ 'num_fds': process.num_fds() if hasattr(process, 'num_fds') else 'N/A'
318
+ }
319
+ except ImportError:
320
+ metrics['system'] = {'error': 'psutil not available'}
321
+ except Exception as e:
322
+ metrics['system'] = {'error': str(e)}
323
+
324
+ # Database metrics
325
+ connection_pool_manager = get_connection_pool_manager()
326
+ if connection_pool_manager:
327
+ pool_status = connection_pool_manager.get_status()
328
+ metrics['database'] = pool_status['database']
329
+ metrics['redis'] = pool_status['redis'] or {'status': 'disabled'}
330
+
331
+ # Cache metrics
332
+ cache_service = get_cache_service()
333
+ if cache_service:
334
+ metrics['cache'] = cache_service.get_cache_stats()
335
+
336
+ # Application metrics
337
+ metrics['application'] = {
338
+ 'config': {
339
+ 'compression_enabled': current_app.config.get('ENABLE_COMPRESSION', False),
340
+ 'caching_enabled': current_app.config.get('ENABLE_CACHING', False),
341
+ 'debug_mode': current_app.config.get('DEBUG', False)
342
+ },
343
+ 'flask': {
344
+ 'testing': current_app.testing,
345
+ 'debug': current_app.debug
346
+ }
347
+ }
348
+
349
+ return jsonify(metrics)
350
+
351
+ except Exception as e:
352
+ logger.error(f"Error getting comprehensive metrics: {e}")
353
+ return jsonify({
354
+ 'timestamp': datetime.utcnow().isoformat(),
355
+ 'error': str(e)
356
+ }), 500
357
+
358
+
359
+ @performance_bp.route('/health', methods=['GET'])
360
+ @limiter.limit("60 per minute")
361
+ def performance_health_check():
362
+ """
363
+ Quick health check for performance monitoring.
364
+
365
+ Returns:
366
+ JSON response with health status
367
+ """
368
+ try:
369
+ health_status = {
370
+ 'timestamp': datetime.utcnow().isoformat(),
371
+ 'status': 'healthy',
372
+ 'checks': {}
373
+ }
374
+
375
+ # Database health
376
+ try:
377
+ from sqlalchemy import text
378
+ from ..models.base import db
379
+ db.session.execute(text('SELECT 1'))
380
+ health_status['checks']['database'] = 'healthy'
381
+ except Exception as e:
382
+ health_status['checks']['database'] = f'unhealthy: {str(e)}'
383
+ health_status['status'] = 'degraded'
384
+
385
+ # Redis health
386
+ connection_pool_manager = get_connection_pool_manager()
387
+ if connection_pool_manager:
388
+ redis_client = connection_pool_manager.get_redis_client()
389
+ if redis_client:
390
+ try:
391
+ redis_client.ping()
392
+ health_status['checks']['redis'] = 'healthy'
393
+ except Exception as e:
394
+ health_status['checks']['redis'] = f'unhealthy: {str(e)}'
395
+ health_status['status'] = 'degraded'
396
+ else:
397
+ health_status['checks']['redis'] = 'disabled'
398
+ else:
399
+ health_status['checks']['redis'] = 'no_pool_manager'
400
+
401
+ # Cache service health
402
+ cache_service = get_cache_service()
403
+ if cache_service:
404
+ cache_stats = cache_service.get_cache_stats()
405
+ if cache_stats['cache_errors'] > 100: # Arbitrary threshold
406
+ health_status['checks']['cache'] = 'degraded'
407
+ health_status['status'] = 'degraded'
408
+ else:
409
+ health_status['checks']['cache'] = 'healthy'
410
+ else:
411
+ health_status['checks']['cache'] = 'not_initialized'
412
+
413
+ return jsonify(health_status)
414
+
415
+ except Exception as e:
416
+ logger.error(f"Performance health check failed: {e}")
417
+ return jsonify({
418
+ 'timestamp': datetime.utcnow().isoformat(),
419
+ 'status': 'unhealthy',
420
+ 'error': str(e)
421
+ }), 503
422
+
423
+
424
+ # Error handlers
425
+ @performance_bp.errorhandler(429)
426
+ def handle_rate_limit_exceeded(error):
427
+ """Handle rate limit exceeded errors."""
428
+ return jsonify({
429
+ 'error': 'Rate limit exceeded',
430
+ 'message': 'Too many requests to performance endpoints. Please try again later.'
431
+ }), 429
432
+
433
+
434
+ @performance_bp.errorhandler(500)
435
+ def handle_internal_error(error):
436
+ """Handle internal server errors."""
437
+ logger.error(f"Performance endpoint internal error: {error}")
438
+ return jsonify({
439
+ 'error': 'Internal server error',
440
+ 'timestamp': datetime.utcnow().isoformat()
441
+ }), 500
chat_agent/models/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Models package for the chat agent application."""
2
+
3
+ from .base import db, BaseModel
4
+ from .message import Message
5
+ from .chat_session import ChatSession
6
+ from .language_context import LanguageContext
7
+
8
+ # Export all models and database instance
9
+ __all__ = [
10
+ 'db',
11
+ 'BaseModel',
12
+ 'Message',
13
+ 'ChatSession',
14
+ 'LanguageContext'
15
+ ]
chat_agent/models/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (474 Bytes). View file
 
chat_agent/models/__pycache__/base.cpython-312.pyc ADDED
Binary file (3.24 kB). View file
 
chat_agent/models/__pycache__/chat_session.cpython-312.pyc ADDED
Binary file (6.9 kB). View file
 
chat_agent/models/__pycache__/language_context.cpython-312.pyc ADDED
Binary file (13.2 kB). View file
 
chat_agent/models/__pycache__/message.cpython-312.pyc ADDED
Binary file (3.41 kB). View file
 
chat_agent/models/base.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base model classes and database setup for the chat agent."""
2
+
3
+ from datetime import datetime
4
+ from uuid import uuid4
5
+ from sqlalchemy import Column, DateTime, String, TypeDecorator
6
+ from sqlalchemy.dialects.postgresql import UUID
7
+ from sqlalchemy.ext.declarative import declarative_base
8
+ from sqlalchemy.orm import sessionmaker
9
+ from flask_sqlalchemy import SQLAlchemy
10
+
11
+ # Create SQLAlchemy instance
12
+ db = SQLAlchemy()
13
+
14
+ # Custom UUID type that works with both PostgreSQL and SQLite
15
+ class GUID(TypeDecorator):
16
+ """Platform-independent GUID type.
17
+ Uses PostgreSQL's UUID type, otherwise uses String(36).
18
+ """
19
+ impl = String
20
+ cache_ok = True
21
+
22
+ def load_dialect_impl(self, dialect):
23
+ if dialect.name == 'postgresql':
24
+ return dialect.type_descriptor(UUID(as_uuid=True))
25
+ else:
26
+ return dialect.type_descriptor(String(36))
27
+
28
+ def process_bind_param(self, value, dialect):
29
+ if value is None:
30
+ return value
31
+ elif dialect.name == 'postgresql':
32
+ return value
33
+ else:
34
+ return str(value)
35
+
36
+ def process_result_value(self, value, dialect):
37
+ if value is None:
38
+ return value
39
+ else:
40
+ return value
41
+
42
+ # Base model class with common fields
43
+ class BaseModel(db.Model):
44
+ """Base model class with common fields and methods."""
45
+
46
+ __abstract__ = True
47
+
48
+ id = Column(GUID(), primary_key=True, default=lambda: str(uuid4()))
49
+ created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
50
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
51
+
52
+ def to_dict(self):
53
+ """Convert model instance to dictionary."""
54
+ return {
55
+ column.name: getattr(self, column.name)
56
+ for column in self.__table__.columns
57
+ }
58
+
59
+ def __repr__(self):
60
+ """String representation of the model."""
61
+ return f"<{self.__class__.__name__}(id={self.id})>"
chat_agent/models/chat_session.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ChatSession model for managing user chat sessions."""
2
+
3
+ from datetime import datetime, timedelta
4
+ from sqlalchemy import Column, String, DateTime, Integer, Boolean, JSON
5
+ from sqlalchemy.orm import relationship
6
+ from .base import BaseModel, db, GUID
7
+
8
+
9
+ class ChatSession(BaseModel):
10
+ """Model for managing user chat sessions."""
11
+
12
+ __tablename__ = 'chat_sessions'
13
+
14
+ # Core session fields
15
+ user_id = Column(GUID(), nullable=False, index=True)
16
+ language = Column(String(50), nullable=False, default='python')
17
+
18
+ # Activity tracking
19
+ last_active = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
20
+ message_count = Column(Integer, default=0, nullable=False)
21
+ is_active = Column(Boolean, default=True, nullable=False, index=True)
22
+
23
+ # Session metadata
24
+ session_metadata = Column(JSON, default=dict) # Additional session context, preferences, etc.
25
+
26
+ # Relationships
27
+ messages = relationship("Message", back_populates="session", cascade="all, delete-orphan")
28
+ language_context = relationship("LanguageContext", back_populates="session", uselist=False, cascade="all, delete-orphan")
29
+
30
+ def __init__(self, user_id, language='python', session_metadata=None):
31
+ """Initialize a new chat session."""
32
+ super().__init__()
33
+ self.user_id = user_id
34
+ self.language = language
35
+ self.last_active = datetime.utcnow()
36
+ self.message_count = 0
37
+ self.is_active = True
38
+ self.session_metadata = session_metadata or {}
39
+
40
+ def update_activity(self):
41
+ """Update the last active timestamp."""
42
+ self.last_active = datetime.utcnow()
43
+ db.session.commit()
44
+
45
+ def increment_message_count(self):
46
+ """Increment the message count for this session."""
47
+ self.message_count += 1
48
+ self.update_activity()
49
+
50
+ def set_language(self, language):
51
+ """Set the programming language for this session."""
52
+ self.language = language
53
+ self.update_activity()
54
+
55
+ def deactivate(self):
56
+ """Mark the session as inactive."""
57
+ self.is_active = False
58
+ db.session.commit()
59
+
60
+ def is_expired(self, timeout_seconds=3600):
61
+ """Check if the session has expired based on last activity."""
62
+ if not self.last_active:
63
+ return True
64
+
65
+ expiry_time = self.last_active + timedelta(seconds=timeout_seconds)
66
+ return datetime.utcnow() > expiry_time
67
+
68
+ def get_recent_messages(self, limit=10):
69
+ """Get recent messages for this session."""
70
+ # Import here to avoid circular imports
71
+ from .message import Message
72
+ return (db.session.query(Message)
73
+ .filter(Message.session_id == self.id)
74
+ .order_by(Message.timestamp.desc())
75
+ .limit(limit)
76
+ .all())
77
+
78
+ def to_dict(self):
79
+ """Convert session to dictionary with formatted timestamps."""
80
+ data = super().to_dict()
81
+ # Format timestamps as ISO strings for JSON serialization
82
+ if self.last_active:
83
+ data['last_active'] = self.last_active.isoformat()
84
+ return data
85
+
86
+ @classmethod
87
+ def create_session(cls, user_id, language='python', session_metadata=None):
88
+ """Create a new chat session."""
89
+ session = cls(user_id=user_id, language=language, session_metadata=session_metadata)
90
+ db.session.add(session)
91
+ db.session.commit()
92
+ return session
93
+
94
+ @classmethod
95
+ def get_active_sessions(cls, user_id=None):
96
+ """Get all active sessions, optionally filtered by user."""
97
+ query = db.session.query(cls).filter(cls.is_active == True)
98
+ if user_id:
99
+ query = query.filter(cls.user_id == user_id)
100
+ return query.all()
101
+
102
+ @classmethod
103
+ def cleanup_expired_sessions(cls, timeout_seconds=3600):
104
+ """Clean up expired sessions."""
105
+ cutoff_time = datetime.utcnow() - timedelta(seconds=timeout_seconds)
106
+ expired_sessions = (db.session.query(cls)
107
+ .filter(cls.last_active < cutoff_time)
108
+ .filter(cls.is_active == True)
109
+ .all())
110
+
111
+ for session in expired_sessions:
112
+ session.deactivate()
113
+
114
+ return len(expired_sessions)
115
+
116
+ def __repr__(self):
117
+ """String representation of the session."""
118
+ return f"<ChatSession(id={self.id}, user_id={self.user_id}, language={self.language}, active={self.is_active})>"
chat_agent/models/language_context.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LanguageContext model for session-specific language settings."""
2
+
3
+ from datetime import datetime
4
+ from sqlalchemy import Column, String, Text, DateTime
5
+ from sqlalchemy import ForeignKey
6
+ from sqlalchemy.orm import relationship
7
+ from .base import BaseModel, db, GUID
8
+
9
+
10
+ class LanguageContext(BaseModel):
11
+ """Model for storing session-specific language settings and context."""
12
+
13
+ __tablename__ = 'language_contexts'
14
+
15
+ # Core context fields
16
+ session_id = Column(GUID(), ForeignKey('chat_sessions.id'), nullable=False, unique=True, index=True)
17
+ language = Column(String(50), nullable=False, default='python')
18
+
19
+ # Language-specific settings
20
+ prompt_template = Column(Text, nullable=True) # Custom prompt template for this language
21
+ syntax_highlighting = Column(String(50), nullable=True) # Syntax highlighting scheme
22
+
23
+ # Context metadata
24
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
25
+
26
+ # Relationships
27
+ session = relationship("ChatSession", back_populates="language_context")
28
+
29
+ # Supported programming languages with enhanced prompt templates
30
+ SUPPORTED_LANGUAGES = {
31
+ 'python': {
32
+ 'name': 'Python',
33
+ 'syntax_highlighting': 'python',
34
+ 'file_extensions': ['.py', '.pyw'],
35
+ 'prompt_template': '''You are an expert Python programming tutor and assistant. Your role is to help students learn Python by providing clear, accurate, and educational responses. Always:
36
+
37
+ 1. Use simple, beginner-friendly language
38
+ 2. Provide practical examples with detailed explanations
39
+ 3. Explain the 'why' behind concepts, not just the 'how'
40
+ 4. Encourage Python best practices and PEP 8 style guidelines
41
+ 5. Be patient and supportive, especially with beginners
42
+ 6. When explaining errors, provide step-by-step debugging guidance
43
+ 7. Use code comments to explain complex parts
44
+ 8. Suggest improvements and alternative approaches when appropriate
45
+
46
+ Focus on helping students understand Python concepts deeply rather than just providing quick fixes.''',
47
+ 'assistance_features': {
48
+ 'code_explanation': True,
49
+ 'debugging': True,
50
+ 'error_analysis': True,
51
+ 'code_review': True,
52
+ 'concept_clarification': True,
53
+ 'beginner_help': True
54
+ }
55
+ },
56
+ 'javascript': {
57
+ 'name': 'JavaScript',
58
+ 'syntax_highlighting': 'javascript',
59
+ 'file_extensions': ['.js', '.mjs'],
60
+ 'prompt_template': '''You are an expert JavaScript programming tutor and assistant. Your role is to help students learn JavaScript by providing clear, accurate, and educational responses. Always:
61
+
62
+ 1. Use simple, beginner-friendly language
63
+ 2. Provide practical examples with detailed explanations
64
+ 3. Explain modern JavaScript (ES6+) features and best practices
65
+ 4. Help with both frontend and backend JavaScript concepts
66
+ 5. Be patient and supportive, especially with beginners
67
+ 6. When explaining errors, provide step-by-step debugging guidance
68
+ 7. Use code comments to explain complex parts
69
+ 8. Suggest improvements and modern JavaScript patterns
70
+
71
+ Focus on helping students understand JavaScript concepts deeply, including asynchronous programming, DOM manipulation, and modern frameworks.''',
72
+ 'assistance_features': {
73
+ 'code_explanation': True,
74
+ 'debugging': True,
75
+ 'error_analysis': True,
76
+ 'code_review': True,
77
+ 'concept_clarification': True,
78
+ 'beginner_help': True
79
+ }
80
+ },
81
+ 'typescript': {
82
+ 'name': 'TypeScript',
83
+ 'syntax_highlighting': 'typescript',
84
+ 'file_extensions': ['.ts', '.tsx'],
85
+ 'prompt_template': '''You are an expert TypeScript programming tutor and assistant. Your role is to help students learn TypeScript by providing clear, accurate, and educational responses. Always:
86
+
87
+ 1. Use simple, beginner-friendly language
88
+ 2. Provide practical examples with detailed type annotations
89
+ 3. Explain TypeScript's type system and its benefits over JavaScript
90
+ 4. Help with both basic and advanced TypeScript features
91
+ 5. Be patient and supportive, especially with beginners
92
+ 6. When explaining errors, focus on type-related issues and solutions
93
+ 7. Use code comments to explain complex type definitions
94
+ 8. Suggest improvements using TypeScript's powerful type features
95
+
96
+ Focus on helping students understand TypeScript's type system and how it enhances JavaScript development.''',
97
+ 'assistance_features': {
98
+ 'code_explanation': True,
99
+ 'debugging': True,
100
+ 'error_analysis': True,
101
+ 'code_review': True,
102
+ 'concept_clarification': True,
103
+ 'beginner_help': True
104
+ }
105
+ },
106
+ 'java': {
107
+ 'name': 'Java',
108
+ 'syntax_highlighting': 'java',
109
+ 'file_extensions': ['.java'],
110
+ 'prompt_template': '''You are an expert Java programming tutor and assistant. Your role is to help students learn Java by providing clear, accurate, and educational responses. Always:
111
+
112
+ 1. Use simple, beginner-friendly language
113
+ 2. Provide practical examples with detailed explanations
114
+ 3. Explain object-oriented programming concepts clearly
115
+ 4. Help with Java syntax, conventions, and best practices
116
+ 5. Be patient and supportive, especially with beginners
117
+ 6. When explaining errors, provide step-by-step debugging guidance
118
+ 7. Use code comments to explain complex parts
119
+ 8. Suggest improvements following Java conventions
120
+
121
+ Focus on helping students understand Java's object-oriented nature, strong typing, and enterprise development patterns.''',
122
+ 'assistance_features': {
123
+ 'code_explanation': True,
124
+ 'debugging': True,
125
+ 'error_analysis': True,
126
+ 'code_review': True,
127
+ 'concept_clarification': True,
128
+ 'beginner_help': True
129
+ }
130
+ },
131
+ 'cpp': {
132
+ 'name': 'C++',
133
+ 'syntax_highlighting': 'cpp',
134
+ 'file_extensions': ['.cpp', '.cc', '.cxx', '.h', '.hpp'],
135
+ 'prompt_template': '''You are an expert C++ programming tutor and assistant. Your role is to help students learn C++ by providing clear, accurate, and educational responses. Always:
136
+
137
+ 1. Use simple, beginner-friendly language
138
+ 2. Provide practical examples with detailed explanations
139
+ 3. Explain memory management and pointer concepts clearly
140
+ 4. Help with both C++11/14/17/20 features and classic C++
141
+ 5. Be patient and supportive, especially with beginners
142
+ 6. When explaining errors, focus on compilation and runtime issues
143
+ 7. Use code comments to explain complex parts
144
+ 8. Suggest improvements following modern C++ best practices
145
+
146
+ Focus on helping students understand C++'s power and complexity, including memory management, templates, and modern C++ features.''',
147
+ 'assistance_features': {
148
+ 'code_explanation': True,
149
+ 'debugging': True,
150
+ 'error_analysis': True,
151
+ 'code_review': True,
152
+ 'concept_clarification': True,
153
+ 'beginner_help': True
154
+ }
155
+ },
156
+ 'csharp': {
157
+ 'name': 'C#',
158
+ 'syntax_highlighting': 'csharp',
159
+ 'file_extensions': ['.cs'],
160
+ 'prompt_template': '''You are an expert C# programming tutor and assistant. Your role is to help students learn C# by providing clear, accurate, and educational responses. Always:
161
+
162
+ 1. Use simple, beginner-friendly language
163
+ 2. Provide practical examples with detailed explanations
164
+ 3. Explain .NET framework and C# language features
165
+ 4. Help with object-oriented programming in C#
166
+ 5. Be patient and supportive, especially with beginners
167
+ 6. When explaining errors, provide step-by-step debugging guidance
168
+ 7. Use code comments to explain complex parts
169
+ 8. Suggest improvements following C# conventions and best practices
170
+
171
+ Focus on helping students understand C#'s integration with .NET, strong typing, and enterprise development patterns.''',
172
+ 'assistance_features': {
173
+ 'code_explanation': True,
174
+ 'debugging': True,
175
+ 'error_analysis': True,
176
+ 'code_review': True,
177
+ 'concept_clarification': True,
178
+ 'beginner_help': True
179
+ }
180
+ },
181
+ 'go': {
182
+ 'name': 'Go',
183
+ 'syntax_highlighting': 'go',
184
+ 'file_extensions': ['.go'],
185
+ 'prompt_template': '''You are an expert Go programming tutor and assistant. Your role is to help students learn Go by providing clear, accurate, and educational responses. Always:
186
+
187
+ 1. Use simple, beginner-friendly language
188
+ 2. Provide practical examples with detailed explanations
189
+ 3. Explain Go's simplicity and concurrency features
190
+ 4. Help with Go idioms and best practices
191
+ 5. Be patient and supportive, especially with beginners
192
+ 6. When explaining errors, provide step-by-step debugging guidance
193
+ 7. Use code comments to explain complex parts
194
+ 8. Suggest improvements following Go conventions
195
+
196
+ Focus on helping students understand Go's philosophy of simplicity, its powerful concurrency model, and systems programming concepts.''',
197
+ 'assistance_features': {
198
+ 'code_explanation': True,
199
+ 'debugging': True,
200
+ 'error_analysis': True,
201
+ 'code_review': True,
202
+ 'concept_clarification': True,
203
+ 'beginner_help': True
204
+ }
205
+ },
206
+ 'rust': {
207
+ 'name': 'Rust',
208
+ 'syntax_highlighting': 'rust',
209
+ 'file_extensions': ['.rs'],
210
+ 'prompt_template': '''You are an expert Rust programming tutor and assistant. Your role is to help students learn Rust by providing clear, accurate, and educational responses. Always:
211
+
212
+ 1. Use simple, beginner-friendly language
213
+ 2. Provide practical examples with detailed explanations
214
+ 3. Explain Rust's ownership system and memory safety features
215
+ 4. Help with Rust's unique concepts like borrowing and lifetimes
216
+ 5. Be patient and supportive, especially with beginners
217
+ 6. When explaining errors, focus on the borrow checker and compiler messages
218
+ 7. Use code comments to explain complex parts
219
+ 8. Suggest improvements following Rust idioms and best practices
220
+
221
+ Focus on helping students understand Rust's ownership model, memory safety guarantees, and systems programming concepts.''',
222
+ 'assistance_features': {
223
+ 'code_explanation': True,
224
+ 'debugging': True,
225
+ 'error_analysis': True,
226
+ 'code_review': True,
227
+ 'concept_clarification': True,
228
+ 'beginner_help': True
229
+ }
230
+ }
231
+ }
232
+
233
+ def __init__(self, session_id, language='python'):
234
+ """Initialize a new language context."""
235
+ super().__init__()
236
+ self.session_id = session_id
237
+ self.set_language(language)
238
+
239
+ def set_language(self, language):
240
+ """Set the programming language and update related settings."""
241
+ if not self.is_supported_language(language):
242
+ raise ValueError(f"Unsupported language: {language}")
243
+
244
+ self.language = language
245
+ language_config = self.SUPPORTED_LANGUAGES[language]
246
+ self.syntax_highlighting = language_config['syntax_highlighting']
247
+ self.prompt_template = language_config['prompt_template']
248
+ self.updated_at = datetime.utcnow()
249
+
250
+ def get_prompt_template(self):
251
+ """Get the prompt template for the current language."""
252
+ return self.prompt_template or self.SUPPORTED_LANGUAGES.get(self.language, {}).get('prompt_template', '')
253
+
254
+ def get_syntax_highlighting(self):
255
+ """Get the syntax highlighting scheme for the current language."""
256
+ return self.syntax_highlighting or self.SUPPORTED_LANGUAGES.get(self.language, {}).get('syntax_highlighting', 'text')
257
+
258
+ def get_language_info(self):
259
+ """Get complete language information."""
260
+ return self.SUPPORTED_LANGUAGES.get(self.language, {})
261
+
262
+ @classmethod
263
+ def is_supported_language(cls, language):
264
+ """Check if a programming language is supported."""
265
+ return language.lower() in cls.SUPPORTED_LANGUAGES
266
+
267
+ @classmethod
268
+ def get_supported_languages(cls):
269
+ """Get list of all supported programming languages."""
270
+ return list(cls.SUPPORTED_LANGUAGES.keys())
271
+
272
+ @classmethod
273
+ def get_language_display_names(cls):
274
+ """Get mapping of language codes to display names."""
275
+ return {
276
+ code: config['name']
277
+ for code, config in cls.SUPPORTED_LANGUAGES.items()
278
+ }
279
+
280
+ @classmethod
281
+ def create_context(cls, session_id, language='python'):
282
+ """Create a new language context for a session."""
283
+ context = cls(session_id=session_id, language=language)
284
+ db.session.add(context)
285
+ db.session.commit()
286
+ return context
287
+
288
+ @classmethod
289
+ def get_or_create_context(cls, session_id, language='python'):
290
+ """Get existing context or create a new one."""
291
+ context = db.session.query(cls).filter(cls.session_id == session_id).first()
292
+ if not context:
293
+ context = cls.create_context(session_id, language)
294
+ return context
295
+
296
+ def to_dict(self):
297
+ """Convert context to dictionary."""
298
+ data = super().to_dict()
299
+ data['language_info'] = self.get_language_info()
300
+ return data
301
+
302
+ def __repr__(self):
303
+ """String representation of the language context."""
304
+ return f"<LanguageContext(session_id={self.session_id}, language={self.language})>"