Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
c4b5da1
1
Parent(s):
024cccc
Add AI block creation
Browse files- project/blocks.txt +26 -0
- project/chat.py +199 -15
- project/src/index.js +202 -0
- project/src/toolbox.js +0 -4
project/blocks.txt
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI
|
| 2 |
+
llm_call(inputs(MODEL: "gpt-3.5-turbo-0125/gpt-4o-2024-08-06/gpt-5-mini-2025-08-07/gpt-5-2025-08-07/gpt-4o-search-preview-2025-03-11", PROMPT: value))
|
| 3 |
+
|
| 4 |
+
# JSON and API
|
| 5 |
+
in_json(inputs(NAME: value, JSON: value)) // Name is the value you want to extract from the JSON
|
| 6 |
+
|
| 7 |
+
# Logic
|
| 8 |
+
logic_negate(inputs())
|
| 9 |
+
logic_boolean(inputs(BOOL: "TRUE/FALSE"))
|
| 10 |
+
logic_null(inputs())
|
| 11 |
+
logic_compare(inputs(OP: "EQ/NEQ/LT/LTE/GT/GTE", A: value, B: value))
|
| 12 |
+
logic_operation(inputs(OP: "AND/OR", A: value, B: value))
|
| 13 |
+
|
| 14 |
+
# Math
|
| 15 |
+
math_number(inputs(NUM: value))
|
| 16 |
+
math_arithmetic(inputs(OP: "ADD/MINUS/MULTIPLY/DIVIDE/POWER", A: value, B: value))
|
| 17 |
+
|
| 18 |
+
# Text
|
| 19 |
+
text(inputs(TEXT: value))
|
| 20 |
+
text_length(VALUE: value)
|
| 21 |
+
text_join(inputs(ADDN: value)) // N starts at 0; you can make as many N as you want
|
| 22 |
+
|
| 23 |
+
# Lists
|
| 24 |
+
lists_length(inputs(VALUE: value))
|
| 25 |
+
lists_isEmpty(inputs(VALUE: value))
|
| 26 |
+
lists_reverse(inputs(LIST: value))
|
project/chat.py
CHANGED
|
@@ -24,6 +24,19 @@ latest_blockly_chat_code = ""
|
|
| 24 |
deletion_queue = queue.Queue()
|
| 25 |
deletion_results = {}
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
# FastAPI App
|
| 28 |
app = FastAPI()
|
| 29 |
|
|
@@ -200,9 +213,9 @@ def delete_block(block_id):
|
|
| 200 |
result = deletion_results.pop(block_id)
|
| 201 |
print(f"[DELETE RESULT] Received result for {block_id}: success={result.get('success')}, error={result.get('error')}")
|
| 202 |
if result["success"]:
|
| 203 |
-
return f"Successfully deleted block {block_id}"
|
| 204 |
else:
|
| 205 |
-
return f"Failed to delete block {block_id}: {result.get('error', 'Unknown error')}"
|
| 206 |
time.sleep(check_interval)
|
| 207 |
|
| 208 |
print(f"[DELETE TIMEOUT] No response received for block {block_id} after {timeout} seconds")
|
|
@@ -214,6 +227,124 @@ def delete_block(block_id):
|
|
| 214 |
traceback.print_exc()
|
| 215 |
return f"Error deleting block: {str(e)}"
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
# Server-Sent Events endpoint for deletion requests
|
| 218 |
@app.get("/delete_stream")
|
| 219 |
async def delete_stream():
|
|
@@ -291,7 +422,7 @@ async def deletion_result(request: Request):
|
|
| 291 |
|
| 292 |
def create_gradio_interface():
|
| 293 |
# Hardcoded system prompt
|
| 294 |
-
SYSTEM_PROMPT = """You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
|
| 295 |
MCP lets AI systems define tools with specific inputs and outputs that any LLM can call.
|
| 296 |
|
| 297 |
You’ll receive the workspace state in this format:
|
|
@@ -347,7 +478,44 @@ To delete one, end your message with:
|
|
| 347 |
blockId
|
| 348 |
```
|
| 349 |
|
| 350 |
-
You can delete any block except the main `create_mcp` block.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
|
| 352 |
def chat_with_context(message, history):
|
| 353 |
# Check if API key is set and create/update client
|
|
@@ -360,10 +528,12 @@ You can delete any block except the main `create_mcp` block."""
|
|
| 360 |
try:
|
| 361 |
client = OpenAI(api_key=api_key)
|
| 362 |
except Exception as e:
|
| 363 |
-
|
|
|
|
| 364 |
|
| 365 |
if not client or not api_key:
|
| 366 |
-
|
|
|
|
| 367 |
|
| 368 |
# Get the chat context from the global variable
|
| 369 |
global latest_blockly_chat_code
|
|
@@ -416,14 +586,21 @@ You can delete any block except the main `create_mcp` block."""
|
|
| 416 |
'label': 'MCP',
|
| 417 |
'result_label': 'MCP Execution Result',
|
| 418 |
'handler': lambda content: execute_mcp(content),
|
| 419 |
-
'next_prompt': "Please respond to the MCP execution result above and provide any relevant information to the user. If you need to run another MCP or
|
| 420 |
},
|
| 421 |
'delete': {
|
| 422 |
'pattern': r'```delete\n(.+?)\n```',
|
| 423 |
'label': 'DELETE',
|
| 424 |
'result_label': 'Delete Operation',
|
| 425 |
'handler': lambda content: delete_block(content.strip()),
|
| 426 |
-
'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
}
|
| 428 |
}
|
| 429 |
|
|
@@ -440,17 +617,23 @@ You can delete any block except the main `create_mcp` block."""
|
|
| 440 |
|
| 441 |
print(f"[{config['label']} DETECTED] Processing: {action_content}")
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
# Execute the action
|
| 444 |
action_result = config['handler'](action_content)
|
| 445 |
|
| 446 |
print(f"[{config['label']} RESULT] {action_result}")
|
| 447 |
|
| 448 |
-
#
|
| 449 |
if accumulated_response:
|
| 450 |
accumulated_response += "\n\n"
|
| 451 |
-
if displayed_response:
|
| 452 |
-
accumulated_response += displayed_response + "\n\n"
|
| 453 |
accumulated_response += f"**{config['result_label']}:** {action_result}"
|
|
|
|
| 454 |
|
| 455 |
# Update history for next iteration
|
| 456 |
temp_history.append({"role": "user", "content": current_prompt})
|
|
@@ -468,19 +651,20 @@ You can delete any block except the main `create_mcp` block."""
|
|
| 468 |
if accumulated_response:
|
| 469 |
accumulated_response += "\n\n"
|
| 470 |
accumulated_response += ai_response
|
|
|
|
| 471 |
break
|
| 472 |
|
| 473 |
except Exception as e:
|
| 474 |
if accumulated_response:
|
| 475 |
-
|
| 476 |
else:
|
| 477 |
-
|
|
|
|
| 478 |
|
| 479 |
# If we hit max iterations, add a note
|
| 480 |
if current_iteration >= max_iterations:
|
| 481 |
accumulated_response += f"\n\n*(Reached maximum of {max_iterations} consecutive responses)*"
|
| 482 |
-
|
| 483 |
-
return accumulated_response if accumulated_response else "No response generated"
|
| 484 |
|
| 485 |
# Create the standard ChatInterface
|
| 486 |
demo = gr.ChatInterface(
|
|
|
|
| 24 |
deletion_queue = queue.Queue()
|
| 25 |
deletion_results = {}
|
| 26 |
|
| 27 |
+
# Queue for creation requests and results storage
|
| 28 |
+
creation_queue = queue.Queue()
|
| 29 |
+
creation_results = {}
|
| 30 |
+
|
| 31 |
+
blocks_context = ""
|
| 32 |
+
try:
|
| 33 |
+
file_path = os.path.join(os.path.dirname(__file__), "blocks.txt")
|
| 34 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 35 |
+
blocks_context = f.read().strip()
|
| 36 |
+
except Exception as e:
|
| 37 |
+
print(f"[WARN] Could not read blocks.txt: {e}")
|
| 38 |
+
blocks_context = "(No external block data available.)"
|
| 39 |
+
|
| 40 |
# FastAPI App
|
| 41 |
app = FastAPI()
|
| 42 |
|
|
|
|
| 213 |
result = deletion_results.pop(block_id)
|
| 214 |
print(f"[DELETE RESULT] Received result for {block_id}: success={result.get('success')}, error={result.get('error')}")
|
| 215 |
if result["success"]:
|
| 216 |
+
return f"[TOOL] Successfully deleted block {block_id}"
|
| 217 |
else:
|
| 218 |
+
return f"[TOOL] Failed to delete block {block_id}: {result.get('error', 'Unknown error')}"
|
| 219 |
time.sleep(check_interval)
|
| 220 |
|
| 221 |
print(f"[DELETE TIMEOUT] No response received for block {block_id} after {timeout} seconds")
|
|
|
|
| 227 |
traceback.print_exc()
|
| 228 |
return f"Error deleting block: {str(e)}"
|
| 229 |
|
| 230 |
+
def create_block(block_spec):
|
| 231 |
+
"""Create a block in the Blockly workspace"""
|
| 232 |
+
try:
|
| 233 |
+
print(f"[CREATE REQUEST] Attempting to create block: {block_spec}")
|
| 234 |
+
|
| 235 |
+
# Generate a unique request ID
|
| 236 |
+
import uuid
|
| 237 |
+
request_id = str(uuid.uuid4())
|
| 238 |
+
|
| 239 |
+
# Clear any old results for this request ID first
|
| 240 |
+
if request_id in creation_results:
|
| 241 |
+
creation_results.pop(request_id)
|
| 242 |
+
|
| 243 |
+
# Add to creation queue
|
| 244 |
+
creation_queue.put({"request_id": request_id, "block_spec": block_spec})
|
| 245 |
+
print(f"[CREATE REQUEST] Added to queue with ID: {request_id}")
|
| 246 |
+
|
| 247 |
+
# Wait for result with timeout
|
| 248 |
+
import time
|
| 249 |
+
timeout = 8 # 8 seconds timeout
|
| 250 |
+
start_time = time.time()
|
| 251 |
+
check_interval = 0.05 # Check more frequently
|
| 252 |
+
|
| 253 |
+
while time.time() - start_time < timeout:
|
| 254 |
+
if request_id in creation_results:
|
| 255 |
+
result = creation_results.pop(request_id)
|
| 256 |
+
print(f"[CREATE RESULT] Received result for {request_id}: success={result.get('success')}, error={result.get('error')}")
|
| 257 |
+
if result["success"]:
|
| 258 |
+
return f"[TOOL] Successfully created block: {result.get('block_id', 'unknown')}"
|
| 259 |
+
else:
|
| 260 |
+
return f"[TOOL] Failed to create block: {result.get('error', 'Unknown error')}"
|
| 261 |
+
time.sleep(check_interval)
|
| 262 |
+
|
| 263 |
+
print(f"[CREATE TIMEOUT] No response received for request {request_id} after {timeout} seconds")
|
| 264 |
+
return f"Timeout waiting for block creation confirmation"
|
| 265 |
+
|
| 266 |
+
except Exception as e:
|
| 267 |
+
print(f"[CREATE ERROR] {e}")
|
| 268 |
+
import traceback
|
| 269 |
+
traceback.print_exc()
|
| 270 |
+
return f"Error creating block: {str(e)}"
|
| 271 |
+
|
| 272 |
+
# Server-Sent Events endpoint for creation requests
|
| 273 |
+
@app.get("/create_stream")
|
| 274 |
+
async def create_stream():
|
| 275 |
+
"""Stream creation requests to the frontend using Server-Sent Events"""
|
| 276 |
+
|
| 277 |
+
async def clear_sent_request(sent_requests, request_id, delay):
|
| 278 |
+
"""Clear request_id from sent_requests after delay seconds"""
|
| 279 |
+
await asyncio.sleep(delay)
|
| 280 |
+
if request_id in sent_requests:
|
| 281 |
+
sent_requests.discard(request_id)
|
| 282 |
+
|
| 283 |
+
async def event_generator():
|
| 284 |
+
sent_requests = set() # Track sent requests to avoid duplicates
|
| 285 |
+
heartbeat_counter = 0
|
| 286 |
+
|
| 287 |
+
while True:
|
| 288 |
+
try:
|
| 289 |
+
# Check for creation requests (non-blocking)
|
| 290 |
+
if not creation_queue.empty():
|
| 291 |
+
creation_request = creation_queue.get_nowait()
|
| 292 |
+
request_id = creation_request.get("request_id")
|
| 293 |
+
|
| 294 |
+
# Avoid sending duplicate requests too quickly
|
| 295 |
+
if request_id not in sent_requests:
|
| 296 |
+
sent_requests.add(request_id)
|
| 297 |
+
print(f"[SSE CREATE SEND] Sending creation request with ID: {request_id}")
|
| 298 |
+
yield f"data: {json.dumps(creation_request)}\n\n"
|
| 299 |
+
|
| 300 |
+
# Clear from sent_requests after 10 seconds
|
| 301 |
+
asyncio.create_task(clear_sent_request(sent_requests, request_id, 10))
|
| 302 |
+
else:
|
| 303 |
+
print(f"[SSE CREATE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 304 |
+
|
| 305 |
+
await asyncio.sleep(0.1) # Small delay between messages
|
| 306 |
+
else:
|
| 307 |
+
# Send a heartbeat every 30 seconds to keep connection alive
|
| 308 |
+
heartbeat_counter += 1
|
| 309 |
+
if heartbeat_counter >= 300: # 300 * 0.1 = 30 seconds
|
| 310 |
+
yield f"data: {json.dumps({'heartbeat': True})}\n\n"
|
| 311 |
+
heartbeat_counter = 0
|
| 312 |
+
await asyncio.sleep(0.1)
|
| 313 |
+
except queue.Empty:
|
| 314 |
+
await asyncio.sleep(0.1)
|
| 315 |
+
except Exception as e:
|
| 316 |
+
print(f"[SSE CREATE ERROR] {e}")
|
| 317 |
+
await asyncio.sleep(1)
|
| 318 |
+
|
| 319 |
+
return StreamingResponse(
|
| 320 |
+
event_generator(),
|
| 321 |
+
media_type="text/event-stream",
|
| 322 |
+
headers={
|
| 323 |
+
"Cache-Control": "no-cache",
|
| 324 |
+
"Connection": "keep-alive",
|
| 325 |
+
"X-Accel-Buffering": "no",
|
| 326 |
+
}
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
# Endpoint to receive creation results from frontend
|
| 330 |
+
@app.post("/creation_result")
|
| 331 |
+
async def creation_result(request: Request):
|
| 332 |
+
"""Receive creation results from the frontend"""
|
| 333 |
+
data = await request.json()
|
| 334 |
+
request_id = data.get("request_id")
|
| 335 |
+
success = data.get("success")
|
| 336 |
+
error = data.get("error")
|
| 337 |
+
block_id = data.get("block_id")
|
| 338 |
+
|
| 339 |
+
print(f"[CREATION RESULT RECEIVED] request_id={request_id}, success={success}, error={error}, block_id={block_id}")
|
| 340 |
+
|
| 341 |
+
if request_id:
|
| 342 |
+
# Store the result for the create_block function to retrieve
|
| 343 |
+
creation_results[request_id] = data
|
| 344 |
+
print(f"[CREATION RESULT STORED] Results dict now has {len(creation_results)} items")
|
| 345 |
+
|
| 346 |
+
return {"received": True}
|
| 347 |
+
|
| 348 |
# Server-Sent Events endpoint for deletion requests
|
| 349 |
@app.get("/delete_stream")
|
| 350 |
async def delete_stream():
|
|
|
|
| 422 |
|
| 423 |
def create_gradio_interface():
|
| 424 |
# Hardcoded system prompt
|
| 425 |
+
SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
|
| 426 |
MCP lets AI systems define tools with specific inputs and outputs that any LLM can call.
|
| 427 |
|
| 428 |
You’ll receive the workspace state in this format:
|
|
|
|
| 478 |
blockId
|
| 479 |
```
|
| 480 |
|
| 481 |
+
You can delete any block except the main `create_mcp` block.
|
| 482 |
+
|
| 483 |
+
---
|
| 484 |
+
|
| 485 |
+
### Creating Blocks
|
| 486 |
+
You can create new blocks in the workspace.
|
| 487 |
+
To create a block, specify its type and parameters (if any).
|
| 488 |
+
End your message (and say nothing after) with:
|
| 489 |
+
|
| 490 |
+
```create
|
| 491 |
+
block_type(parameters)
|
| 492 |
+
```
|
| 493 |
+
|
| 494 |
+
Examples:
|
| 495 |
+
- `print_text("Hello World")` - creates a print block with text
|
| 496 |
+
- `text("some text")` - creates a text block
|
| 497 |
+
- `math_number(42)` - creates a number block
|
| 498 |
+
- `logic_boolean(true)` - creates a boolean block
|
| 499 |
+
- `mcp_tool("tool_name")` - creates an MCP tool block
|
| 500 |
+
- `controls_if()` - creates an if block
|
| 501 |
+
- `lists_create_empty()` - creates an empty list block
|
| 502 |
+
|
| 503 |
+
List of blocks:
|
| 504 |
+
|
| 505 |
+
{blocks_context}
|
| 506 |
+
|
| 507 |
+
---
|
| 508 |
+
|
| 509 |
+
Additionally, if you ever send a message without a tool call, your response will end. So, if you want to
|
| 510 |
+
call more tools after something you have to keep calling them. Any pause in tool callings ends the loop.
|
| 511 |
+
|
| 512 |
+
REMEMBER, AS YOU SEE BELOW, THE NAME MUST BE DIRECTLY AFTER THE ``` AND CANNOT HAVE A NEW LINE IN BETWEEN
|
| 513 |
+
THIS IS A REQUIREMENT. NAME MUST BE ON THE EXACT SAME LINE AS THE BACKTICKS.
|
| 514 |
+
|
| 515 |
+
```name
|
| 516 |
+
(arguments_here)
|
| 517 |
+
```
|
| 518 |
+
"""
|
| 519 |
|
| 520 |
def chat_with_context(message, history):
|
| 521 |
# Check if API key is set and create/update client
|
|
|
|
| 528 |
try:
|
| 529 |
client = OpenAI(api_key=api_key)
|
| 530 |
except Exception as e:
|
| 531 |
+
yield f"Error initializing OpenAI client: {str(e)}"
|
| 532 |
+
return
|
| 533 |
|
| 534 |
if not client or not api_key:
|
| 535 |
+
yield "OpenAI API key not configured. Please set it in File > Settings in the Blockly interface."
|
| 536 |
+
return
|
| 537 |
|
| 538 |
# Get the chat context from the global variable
|
| 539 |
global latest_blockly_chat_code
|
|
|
|
| 586 |
'label': 'MCP',
|
| 587 |
'result_label': 'MCP Execution Result',
|
| 588 |
'handler': lambda content: execute_mcp(content),
|
| 589 |
+
'next_prompt': "Please respond to the MCP execution result above and provide any relevant information to the user. If you need to run another MCP, delete, or create blocks, you can do so."
|
| 590 |
},
|
| 591 |
'delete': {
|
| 592 |
'pattern': r'```delete\n(.+?)\n```',
|
| 593 |
'label': 'DELETE',
|
| 594 |
'result_label': 'Delete Operation',
|
| 595 |
'handler': lambda content: delete_block(content.strip()),
|
| 596 |
+
'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP, delete more code, or create blocks, you can do so."
|
| 597 |
+
},
|
| 598 |
+
'create': {
|
| 599 |
+
'pattern': r'```create\n(.+?)\n```',
|
| 600 |
+
'label': 'CREATE',
|
| 601 |
+
'result_label': 'Create Operation',
|
| 602 |
+
'handler': lambda content: create_block(content.strip()),
|
| 603 |
+
'next_prompt': "Please respond to the create operation result above. If you need to run an MCP, delete code, or create more blocks, you can do so."
|
| 604 |
}
|
| 605 |
}
|
| 606 |
|
|
|
|
| 617 |
|
| 618 |
print(f"[{config['label']} DETECTED] Processing: {action_content}")
|
| 619 |
|
| 620 |
+
# Yield the displayed response first if it exists
|
| 621 |
+
if displayed_response:
|
| 622 |
+
if accumulated_response:
|
| 623 |
+
accumulated_response += "\n\n"
|
| 624 |
+
accumulated_response += displayed_response
|
| 625 |
+
yield accumulated_response
|
| 626 |
+
|
| 627 |
# Execute the action
|
| 628 |
action_result = config['handler'](action_content)
|
| 629 |
|
| 630 |
print(f"[{config['label']} RESULT] {action_result}")
|
| 631 |
|
| 632 |
+
# Yield the action result
|
| 633 |
if accumulated_response:
|
| 634 |
accumulated_response += "\n\n"
|
|
|
|
|
|
|
| 635 |
accumulated_response += f"**{config['result_label']}:** {action_result}"
|
| 636 |
+
yield accumulated_response
|
| 637 |
|
| 638 |
# Update history for next iteration
|
| 639 |
temp_history.append({"role": "user", "content": current_prompt})
|
|
|
|
| 651 |
if accumulated_response:
|
| 652 |
accumulated_response += "\n\n"
|
| 653 |
accumulated_response += ai_response
|
| 654 |
+
yield accumulated_response
|
| 655 |
break
|
| 656 |
|
| 657 |
except Exception as e:
|
| 658 |
if accumulated_response:
|
| 659 |
+
yield f"{accumulated_response}\n\nError in iteration {current_iteration}: {str(e)}"
|
| 660 |
else:
|
| 661 |
+
yield f"Error: {str(e)}"
|
| 662 |
+
return
|
| 663 |
|
| 664 |
# If we hit max iterations, add a note
|
| 665 |
if current_iteration >= max_iterations:
|
| 666 |
accumulated_response += f"\n\n*(Reached maximum of {max_iterations} consecutive responses)*"
|
| 667 |
+
yield accumulated_response
|
|
|
|
| 668 |
|
| 669 |
# Create the standard ChatInterface
|
| 670 |
demo = gr.ChatInterface(
|
project/src/index.js
CHANGED
|
@@ -299,6 +299,208 @@ const setupDeletionStream = () => {
|
|
| 299 |
// Start the SSE connection
|
| 300 |
setupDeletionStream();
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
// Observe any size change to the blockly container
|
| 303 |
const observer = new ResizeObserver(() => {
|
| 304 |
Blockly.svgResize(ws);
|
|
|
|
| 299 |
// Start the SSE connection
|
| 300 |
setupDeletionStream();
|
| 301 |
|
| 302 |
+
// Set up SSE connection for creation requests
|
| 303 |
+
const setupCreationStream = () => {
|
| 304 |
+
const eventSource = new EventSource('http://127.0.0.1:7861/create_stream');
|
| 305 |
+
const processedRequests = new Set(); // Track processed creation requests
|
| 306 |
+
|
| 307 |
+
eventSource.onmessage = (event) => {
|
| 308 |
+
try {
|
| 309 |
+
const data = JSON.parse(event.data);
|
| 310 |
+
|
| 311 |
+
// Skip heartbeat messages
|
| 312 |
+
if (data.heartbeat) return;
|
| 313 |
+
|
| 314 |
+
// Skip if we've already processed this request
|
| 315 |
+
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 316 |
+
console.log('[SSE CREATE] Skipping duplicate creation request:', data.request_id);
|
| 317 |
+
return;
|
| 318 |
+
}
|
| 319 |
+
if (data.request_id) {
|
| 320 |
+
processedRequests.add(data.request_id);
|
| 321 |
+
// Clear after 10 seconds to allow retries if needed
|
| 322 |
+
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
if (data.block_spec && data.request_id) {
|
| 326 |
+
console.log('[SSE CREATE] Received creation request:', data.request_id, data.block_spec);
|
| 327 |
+
|
| 328 |
+
let success = false;
|
| 329 |
+
let error = null;
|
| 330 |
+
let blockId = null;
|
| 331 |
+
|
| 332 |
+
try {
|
| 333 |
+
// Parse the block specification
|
| 334 |
+
// Expected format: "block_type(param1, param2, ...)"
|
| 335 |
+
const match = data.block_spec.match(/^(\w+)\s*\((.*)\)$/);
|
| 336 |
+
|
| 337 |
+
if (!match) {
|
| 338 |
+
throw new Error(`Invalid block specification format: ${data.block_spec}`);
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
const blockType = match[1];
|
| 342 |
+
const paramsStr = match[2].trim();
|
| 343 |
+
|
| 344 |
+
// Create the block based on its type
|
| 345 |
+
let newBlock = null;
|
| 346 |
+
|
| 347 |
+
switch (blockType) {
|
| 348 |
+
case 'print_text':
|
| 349 |
+
newBlock = ws.newBlock('print_text');
|
| 350 |
+
// Set the text value if provided
|
| 351 |
+
if (paramsStr) {
|
| 352 |
+
// Remove quotes from string parameters
|
| 353 |
+
const text = paramsStr.replace(/^["']|["']$/g, '');
|
| 354 |
+
newBlock.setFieldValue(text, 'TEXT');
|
| 355 |
+
}
|
| 356 |
+
break;
|
| 357 |
+
|
| 358 |
+
case 'mcp_tool':
|
| 359 |
+
newBlock = ws.newBlock('mcp_tool');
|
| 360 |
+
// Parse tool name if provided
|
| 361 |
+
if (paramsStr) {
|
| 362 |
+
const toolName = paramsStr.replace(/^["']|["']$/g, '');
|
| 363 |
+
newBlock.setFieldValue(toolName, 'TOOL');
|
| 364 |
+
}
|
| 365 |
+
break;
|
| 366 |
+
|
| 367 |
+
case 'text':
|
| 368 |
+
newBlock = ws.newBlock('text');
|
| 369 |
+
if (paramsStr) {
|
| 370 |
+
const text = paramsStr.replace(/^["']|["']$/g, '');
|
| 371 |
+
newBlock.setFieldValue(text, 'TEXT');
|
| 372 |
+
}
|
| 373 |
+
break;
|
| 374 |
+
|
| 375 |
+
case 'math_number':
|
| 376 |
+
newBlock = ws.newBlock('math_number');
|
| 377 |
+
if (paramsStr) {
|
| 378 |
+
const num = parseFloat(paramsStr);
|
| 379 |
+
if (!isNaN(num)) {
|
| 380 |
+
newBlock.setFieldValue(num, 'NUM');
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
break;
|
| 384 |
+
|
| 385 |
+
case 'logic_boolean':
|
| 386 |
+
newBlock = ws.newBlock('logic_boolean');
|
| 387 |
+
if (paramsStr) {
|
| 388 |
+
const bool = paramsStr.toLowerCase() === 'true' ? 'TRUE' : 'FALSE';
|
| 389 |
+
newBlock.setFieldValue(bool, 'BOOL');
|
| 390 |
+
}
|
| 391 |
+
break;
|
| 392 |
+
|
| 393 |
+
case 'logic_compare':
|
| 394 |
+
newBlock = ws.newBlock('logic_compare');
|
| 395 |
+
// Could parse operator if provided
|
| 396 |
+
break;
|
| 397 |
+
|
| 398 |
+
case 'logic_operation':
|
| 399 |
+
newBlock = ws.newBlock('logic_operation');
|
| 400 |
+
// Could parse operator if provided
|
| 401 |
+
break;
|
| 402 |
+
|
| 403 |
+
case 'controls_if':
|
| 404 |
+
newBlock = ws.newBlock('controls_if');
|
| 405 |
+
break;
|
| 406 |
+
|
| 407 |
+
case 'controls_whileUntil':
|
| 408 |
+
newBlock = ws.newBlock('controls_whileUntil');
|
| 409 |
+
break;
|
| 410 |
+
|
| 411 |
+
case 'lists_create_with':
|
| 412 |
+
newBlock = ws.newBlock('lists_create_with');
|
| 413 |
+
break;
|
| 414 |
+
|
| 415 |
+
case 'lists_create_empty':
|
| 416 |
+
newBlock = ws.newBlock('lists_create_empty');
|
| 417 |
+
break;
|
| 418 |
+
|
| 419 |
+
default:
|
| 420 |
+
// Try to create as a generic block type
|
| 421 |
+
newBlock = ws.newBlock(blockType);
|
| 422 |
+
break;
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
if (newBlock) {
|
| 426 |
+
// Initialize the block (renders it)
|
| 427 |
+
newBlock.initSvg();
|
| 428 |
+
|
| 429 |
+
// Position it in a visible area
|
| 430 |
+
// Find a good position that doesn't overlap existing blocks
|
| 431 |
+
const existingBlocks = ws.getAllBlocks();
|
| 432 |
+
let x = 50;
|
| 433 |
+
let y = 50;
|
| 434 |
+
|
| 435 |
+
// Simple positioning: stack new blocks vertically
|
| 436 |
+
if (existingBlocks.length > 0) {
|
| 437 |
+
const lastBlock = existingBlocks[existingBlocks.length - 1];
|
| 438 |
+
const lastPos = lastBlock.getRelativeToSurfaceXY();
|
| 439 |
+
y = lastPos.y + lastBlock.height + 20;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
newBlock.moveBy(x, y);
|
| 443 |
+
|
| 444 |
+
// Render the block
|
| 445 |
+
newBlock.render();
|
| 446 |
+
|
| 447 |
+
blockId = newBlock.id;
|
| 448 |
+
success = true;
|
| 449 |
+
console.log('[SSE CREATE] Successfully created block:', blockId, blockType);
|
| 450 |
+
} else {
|
| 451 |
+
throw new Error(`Failed to create block of type: ${blockType}`);
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
} catch (e) {
|
| 455 |
+
error = e.toString();
|
| 456 |
+
console.error('[SSE CREATE] Error creating block:', e);
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
// Send result back to backend immediately
|
| 460 |
+
console.log('[SSE CREATE] Sending creation result:', {
|
| 461 |
+
request_id: data.request_id,
|
| 462 |
+
success,
|
| 463 |
+
error,
|
| 464 |
+
block_id: blockId
|
| 465 |
+
});
|
| 466 |
+
|
| 467 |
+
fetch('http://127.0.0.1:7861/creation_result', {
|
| 468 |
+
method: 'POST',
|
| 469 |
+
headers: { 'Content-Type': 'application/json' },
|
| 470 |
+
body: JSON.stringify({
|
| 471 |
+
request_id: data.request_id,
|
| 472 |
+
success: success,
|
| 473 |
+
error: error,
|
| 474 |
+
block_id: blockId
|
| 475 |
+
})
|
| 476 |
+
}).then(response => {
|
| 477 |
+
console.log('[SSE CREATE] Creation result sent successfully');
|
| 478 |
+
}).catch(err => {
|
| 479 |
+
console.error('[SSE CREATE] Error sending creation result:', err);
|
| 480 |
+
});
|
| 481 |
+
}
|
| 482 |
+
} catch (err) {
|
| 483 |
+
console.error('[SSE CREATE] Error processing message:', err);
|
| 484 |
+
}
|
| 485 |
+
};
|
| 486 |
+
|
| 487 |
+
eventSource.onerror = (error) => {
|
| 488 |
+
console.error('[SSE CREATE] Connection error:', error);
|
| 489 |
+
// Reconnect after 5 seconds
|
| 490 |
+
setTimeout(() => {
|
| 491 |
+
console.log('[SSE CREATE] Attempting to reconnect...');
|
| 492 |
+
setupCreationStream();
|
| 493 |
+
}, 5000);
|
| 494 |
+
};
|
| 495 |
+
|
| 496 |
+
eventSource.onopen = () => {
|
| 497 |
+
console.log('[SSE CREATE] Connected to creation stream');
|
| 498 |
+
};
|
| 499 |
+
};
|
| 500 |
+
|
| 501 |
+
// Start the creation SSE connection
|
| 502 |
+
setupCreationStream();
|
| 503 |
+
|
| 504 |
// Observe any size change to the blockly container
|
| 505 |
const observer = new ResizeObserver(() => {
|
| 506 |
Blockly.svgResize(ws);
|
project/src/toolbox.js
CHANGED
|
@@ -96,10 +96,6 @@ export const toolbox = {
|
|
| 96 |
kind: 'block',
|
| 97 |
type: 'logic_null',
|
| 98 |
},
|
| 99 |
-
{
|
| 100 |
-
kind: 'block',
|
| 101 |
-
type: 'logic_ternary',
|
| 102 |
-
},
|
| 103 |
],
|
| 104 |
},
|
| 105 |
{
|
|
|
|
| 96 |
kind: 'block',
|
| 97 |
type: 'logic_null',
|
| 98 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
],
|
| 100 |
},
|
| 101 |
{
|