Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
5f90a0c
1
Parent(s):
596096e
Add variable creation
Browse files- project/chat.py +150 -3
- project/src/index.js +94 -0
project/chat.py
CHANGED
|
@@ -10,6 +10,8 @@ import gradio as gr
|
|
| 10 |
import asyncio
|
| 11 |
import queue
|
| 12 |
import json
|
|
|
|
|
|
|
| 13 |
from colorama import Fore, Style
|
| 14 |
|
| 15 |
# Initialize OpenAI client (will be updated when API key is set)
|
|
@@ -29,6 +31,10 @@ deletion_results = {}
|
|
| 29 |
creation_queue = queue.Queue()
|
| 30 |
creation_results = {}
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
blocks_context = ""
|
| 33 |
try:
|
| 34 |
file_path = os.path.join(os.path.dirname(__file__), "blocks.txt")
|
|
@@ -275,6 +281,47 @@ def create_block(block_spec, under_block_id=None):
|
|
| 275 |
traceback.print_exc()
|
| 276 |
return f"Error creating block: {str(e)}"
|
| 277 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
# Server-Sent Events endpoint for creation requests
|
| 279 |
@app.get("/create_stream")
|
| 280 |
async def create_stream():
|
|
@@ -426,6 +473,83 @@ async def deletion_result(request: Request):
|
|
| 426 |
|
| 427 |
return {"received": True}
|
| 428 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
def create_gradio_interface():
|
| 430 |
# Hardcoded system prompt
|
| 431 |
SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
|
|
@@ -564,6 +688,23 @@ in one call.
|
|
| 564 |
},
|
| 565 |
}
|
| 566 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
{
|
| 568 |
"type": "function",
|
| 569 |
"function": {
|
|
@@ -665,7 +806,7 @@ in one call.
|
|
| 665 |
|
| 666 |
if function_name == "delete_block":
|
| 667 |
block_id = function_args.get("id", "")
|
| 668 |
-
print(Fore.YELLOW + f"Agent
|
| 669 |
tool_result = delete_block(block_id)
|
| 670 |
result_label = "Delete Operation"
|
| 671 |
|
|
@@ -673,11 +814,17 @@ in one call.
|
|
| 673 |
command = function_args.get("command", "")
|
| 674 |
under_block_id = function_args.get("under", None)
|
| 675 |
if under_block_id == None:
|
| 676 |
-
print(Fore.YELLOW + f"Agent
|
| 677 |
else:
|
| 678 |
-
print(Fore.YELLOW + f"Agent
|
| 679 |
tool_result = create_block(command, under_block_id)
|
| 680 |
result_label = "Create Operation"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
|
| 682 |
elif function_name == "run_mcp":
|
| 683 |
# Build the MCP call string from the arguments
|
|
|
|
| 10 |
import asyncio
|
| 11 |
import queue
|
| 12 |
import json
|
| 13 |
+
import uuid
|
| 14 |
+
import time
|
| 15 |
from colorama import Fore, Style
|
| 16 |
|
| 17 |
# Initialize OpenAI client (will be updated when API key is set)
|
|
|
|
| 31 |
creation_queue = queue.Queue()
|
| 32 |
creation_results = {}
|
| 33 |
|
| 34 |
+
# Queue for variable creation requests and results storage
|
| 35 |
+
variable_queue = queue.Queue()
|
| 36 |
+
variable_results = {}
|
| 37 |
+
|
| 38 |
blocks_context = ""
|
| 39 |
try:
|
| 40 |
file_path = os.path.join(os.path.dirname(__file__), "blocks.txt")
|
|
|
|
| 281 |
traceback.print_exc()
|
| 282 |
return f"Error creating block: {str(e)}"
|
| 283 |
|
| 284 |
+
def create_variable(var_name):
|
| 285 |
+
"""Create a variable in the Blockly workspace"""
|
| 286 |
+
try:
|
| 287 |
+
print(f"[VARIABLE REQUEST] Attempting to create variable: {var_name}")
|
| 288 |
+
|
| 289 |
+
# Generate a unique request ID
|
| 290 |
+
request_id = str(uuid.uuid4())
|
| 291 |
+
|
| 292 |
+
# Clear any old results for this request ID first
|
| 293 |
+
if request_id in variable_results:
|
| 294 |
+
variable_results.pop(request_id)
|
| 295 |
+
|
| 296 |
+
# Add to variable creation queue
|
| 297 |
+
queue_data = {"request_id": request_id, "variable_name": var_name}
|
| 298 |
+
variable_queue.put(queue_data)
|
| 299 |
+
print(f"[VARIABLE REQUEST] Added to queue with ID: {request_id}")
|
| 300 |
+
|
| 301 |
+
# Wait for result with timeout
|
| 302 |
+
timeout = 8 # 8 seconds timeout
|
| 303 |
+
start_time = time.time()
|
| 304 |
+
check_interval = 0.05 # Check more frequently
|
| 305 |
+
|
| 306 |
+
while time.time() - start_time < timeout:
|
| 307 |
+
if request_id in variable_results:
|
| 308 |
+
result = variable_results.pop(request_id)
|
| 309 |
+
print(f"[VARIABLE RESULT] Received result for {request_id}: success={result.get('success')}, error={result.get('error')}")
|
| 310 |
+
if result["success"]:
|
| 311 |
+
return f"[TOOL] Successfully created variable: {result.get('variable_id', var_name)}"
|
| 312 |
+
else:
|
| 313 |
+
return f"[TOOL] Failed to create variable: {result.get('error', 'Unknown error')}"
|
| 314 |
+
time.sleep(check_interval)
|
| 315 |
+
|
| 316 |
+
print(f"[VARIABLE TIMEOUT] No response received for request {request_id} after {timeout} seconds")
|
| 317 |
+
return f"Timeout waiting for variable creation confirmation"
|
| 318 |
+
|
| 319 |
+
except Exception as e:
|
| 320 |
+
print(f"[VARIABLE ERROR] {e}")
|
| 321 |
+
import traceback
|
| 322 |
+
traceback.print_exc()
|
| 323 |
+
return f"Error creating variable: {str(e)}"
|
| 324 |
+
|
| 325 |
# Server-Sent Events endpoint for creation requests
|
| 326 |
@app.get("/create_stream")
|
| 327 |
async def create_stream():
|
|
|
|
| 473 |
|
| 474 |
return {"received": True}
|
| 475 |
|
| 476 |
+
# Server-Sent Events endpoint for variable creation requests
|
| 477 |
+
@app.get("/variable_stream")
|
| 478 |
+
async def variable_stream():
|
| 479 |
+
"""Stream variable creation requests to the frontend using Server-Sent Events"""
|
| 480 |
+
|
| 481 |
+
async def clear_sent_request(sent_requests, request_id, delay):
|
| 482 |
+
"""Clear request_id from sent_requests after delay seconds"""
|
| 483 |
+
await asyncio.sleep(delay)
|
| 484 |
+
if request_id in sent_requests:
|
| 485 |
+
sent_requests.discard(request_id)
|
| 486 |
+
|
| 487 |
+
async def event_generator():
|
| 488 |
+
sent_requests = set() # Track sent requests to avoid duplicates
|
| 489 |
+
heartbeat_counter = 0
|
| 490 |
+
|
| 491 |
+
while True:
|
| 492 |
+
try:
|
| 493 |
+
# Check for variable creation requests (non-blocking)
|
| 494 |
+
if not variable_queue.empty():
|
| 495 |
+
var_request = variable_queue.get_nowait()
|
| 496 |
+
request_id = var_request.get("request_id")
|
| 497 |
+
|
| 498 |
+
# Avoid sending duplicate requests too quickly
|
| 499 |
+
if request_id not in sent_requests:
|
| 500 |
+
sent_requests.add(request_id)
|
| 501 |
+
print(f"[SSE VARIABLE SEND] Sending variable creation request with ID: {request_id}")
|
| 502 |
+
yield f"data: {json.dumps(var_request)}\n\n"
|
| 503 |
+
|
| 504 |
+
# Clear from sent_requests after 10 seconds
|
| 505 |
+
asyncio.create_task(clear_sent_request(sent_requests, request_id, 10))
|
| 506 |
+
else:
|
| 507 |
+
print(f"[SSE VARIABLE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 508 |
+
|
| 509 |
+
await asyncio.sleep(0.1) # Small delay between messages
|
| 510 |
+
else:
|
| 511 |
+
# Send a heartbeat every 30 seconds to keep connection alive
|
| 512 |
+
heartbeat_counter += 1
|
| 513 |
+
if heartbeat_counter >= 300: # 300 * 0.1 = 30 seconds
|
| 514 |
+
yield f"data: {json.dumps({'heartbeat': True})}\n\n"
|
| 515 |
+
heartbeat_counter = 0
|
| 516 |
+
await asyncio.sleep(0.1)
|
| 517 |
+
|
| 518 |
+
except queue.Empty:
|
| 519 |
+
await asyncio.sleep(0.1)
|
| 520 |
+
except Exception as e:
|
| 521 |
+
print(f"[SSE VARIABLE ERROR] {e}")
|
| 522 |
+
await asyncio.sleep(1)
|
| 523 |
+
|
| 524 |
+
return StreamingResponse(
|
| 525 |
+
event_generator(),
|
| 526 |
+
media_type="text/event-stream",
|
| 527 |
+
headers={
|
| 528 |
+
"Cache-Control": "no-cache",
|
| 529 |
+
"Connection": "keep-alive",
|
| 530 |
+
"X-Accel-Buffering": "no",
|
| 531 |
+
}
|
| 532 |
+
)
|
| 533 |
+
|
| 534 |
+
# Endpoint to receive variable creation results from frontend
|
| 535 |
+
@app.post("/variable_result")
|
| 536 |
+
async def variable_result(request: Request):
|
| 537 |
+
"""Receive variable creation results from the frontend"""
|
| 538 |
+
data = await request.json()
|
| 539 |
+
request_id = data.get("request_id")
|
| 540 |
+
success = data.get("success")
|
| 541 |
+
error = data.get("error")
|
| 542 |
+
variable_id = data.get("variable_id")
|
| 543 |
+
|
| 544 |
+
print(f"[VARIABLE RESULT RECEIVED] request_id={request_id}, success={success}, error={error}, variable_id={variable_id}")
|
| 545 |
+
|
| 546 |
+
if request_id:
|
| 547 |
+
# Store the result for the create_variable function to retrieve
|
| 548 |
+
variable_results[request_id] = data
|
| 549 |
+
print(f"[VARIABLE RESULT STORED] Results dict now has {len(variable_results)} items")
|
| 550 |
+
|
| 551 |
+
return {"received": True}
|
| 552 |
+
|
| 553 |
def create_gradio_interface():
|
| 554 |
# Hardcoded system prompt
|
| 555 |
SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
|
|
|
|
| 688 |
},
|
| 689 |
}
|
| 690 |
},
|
| 691 |
+
{
|
| 692 |
+
"type": "function",
|
| 693 |
+
"function": {
|
| 694 |
+
"name": "create_variable",
|
| 695 |
+
"description": "Creates a variable.",
|
| 696 |
+
"parameters": {
|
| 697 |
+
"type": "object",
|
| 698 |
+
"properties": {
|
| 699 |
+
"name": {
|
| 700 |
+
"type": "string",
|
| 701 |
+
"description": "The name of the variable you want to create.",
|
| 702 |
+
},
|
| 703 |
+
},
|
| 704 |
+
"required": ["name"],
|
| 705 |
+
},
|
| 706 |
+
}
|
| 707 |
+
},
|
| 708 |
{
|
| 709 |
"type": "function",
|
| 710 |
"function": {
|
|
|
|
| 806 |
|
| 807 |
if function_name == "delete_block":
|
| 808 |
block_id = function_args.get("id", "")
|
| 809 |
+
print(Fore.YELLOW + f"Agent deleted block with ID `{block_id}`." + Style.RESET_ALL)
|
| 810 |
tool_result = delete_block(block_id)
|
| 811 |
result_label = "Delete Operation"
|
| 812 |
|
|
|
|
| 814 |
command = function_args.get("command", "")
|
| 815 |
under_block_id = function_args.get("under", None)
|
| 816 |
if under_block_id == None:
|
| 817 |
+
print(Fore.YELLOW + f"Agent created block with command `{command}`." + Style.RESET_ALL)
|
| 818 |
else:
|
| 819 |
+
print(Fore.YELLOW + f"Agent created block with command: `{command}`, under block ID: `{under_block_id}`." + Style.RESET_ALL)
|
| 820 |
tool_result = create_block(command, under_block_id)
|
| 821 |
result_label = "Create Operation"
|
| 822 |
+
|
| 823 |
+
elif function_name == "create_variable":
|
| 824 |
+
name = function_args.get("name", "")
|
| 825 |
+
print(Fore.YELLOW + f"Agent created variable with name `{name}`." + Style.RESET_ALL)
|
| 826 |
+
tool_result = create_variable(name)
|
| 827 |
+
result_label = "Create Var Operation"
|
| 828 |
|
| 829 |
elif function_name == "run_mcp":
|
| 830 |
# Build the MCP call string from the arguments
|
project/src/index.js
CHANGED
|
@@ -749,6 +749,100 @@ const setupCreationStream = () => {
|
|
| 749 |
// Start the creation SSE connection
|
| 750 |
setupCreationStream();
|
| 751 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 752 |
// Observe any size change to the blockly container
|
| 753 |
const observer = new ResizeObserver(() => {
|
| 754 |
Blockly.svgResize(ws);
|
|
|
|
| 749 |
// Start the creation SSE connection
|
| 750 |
setupCreationStream();
|
| 751 |
|
| 752 |
+
const setupVariableStream = () => {
|
| 753 |
+
const eventSource = new EventSource('http://127.0.0.1:7861/variable_stream');
|
| 754 |
+
const processedRequests = new Set(); // Track processed variable requests
|
| 755 |
+
|
| 756 |
+
eventSource.onmessage = (event) => {
|
| 757 |
+
try {
|
| 758 |
+
const data = JSON.parse(event.data);
|
| 759 |
+
|
| 760 |
+
// Skip heartbeat messages
|
| 761 |
+
if (data.heartbeat) return;
|
| 762 |
+
|
| 763 |
+
// Skip if we've already processed this request
|
| 764 |
+
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 765 |
+
console.log('[SSE VARIABLE] Skipping duplicate variable request:', data.request_id);
|
| 766 |
+
return;
|
| 767 |
+
}
|
| 768 |
+
if (data.request_id) {
|
| 769 |
+
processedRequests.add(data.request_id);
|
| 770 |
+
// Clear after 10 seconds to allow retries if needed
|
| 771 |
+
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 772 |
+
}
|
| 773 |
+
|
| 774 |
+
if (data.variable_name && data.request_id) {
|
| 775 |
+
console.log('[SSE VARIABLE] Received variable creation request:', data.request_id, data.variable_name);
|
| 776 |
+
|
| 777 |
+
let success = false;
|
| 778 |
+
let error = null;
|
| 779 |
+
let variableId = null;
|
| 780 |
+
|
| 781 |
+
try {
|
| 782 |
+
// Create the variable using Blockly's variable map
|
| 783 |
+
const variableName = data.variable_name;
|
| 784 |
+
|
| 785 |
+
// Use the workspace's variable map to create a new variable
|
| 786 |
+
const variableModel = ws.getVariableMap().createVariable(variableName);
|
| 787 |
+
|
| 788 |
+
if (variableModel) {
|
| 789 |
+
variableId = variableModel.getId();
|
| 790 |
+
success = true;
|
| 791 |
+
console.log('[SSE VARIABLE] Successfully created variable:', variableName, 'with ID:', variableId);
|
| 792 |
+
} else {
|
| 793 |
+
throw new Error('Failed to create variable model');
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
} catch (e) {
|
| 797 |
+
error = e.toString();
|
| 798 |
+
console.error('[SSE VARIABLE] Error creating variable:', e);
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
// Send result back to backend immediately
|
| 802 |
+
console.log('[SSE VARIABLE] Sending variable creation result:', {
|
| 803 |
+
request_id: data.request_id,
|
| 804 |
+
success,
|
| 805 |
+
error,
|
| 806 |
+
variable_id: variableId
|
| 807 |
+
});
|
| 808 |
+
|
| 809 |
+
fetch('http://127.0.0.1:7861/variable_result', {
|
| 810 |
+
method: 'POST',
|
| 811 |
+
headers: { 'Content-Type': 'application/json' },
|
| 812 |
+
body: JSON.stringify({
|
| 813 |
+
request_id: data.request_id,
|
| 814 |
+
success: success,
|
| 815 |
+
error: error,
|
| 816 |
+
variable_id: variableId
|
| 817 |
+
})
|
| 818 |
+
}).then(response => {
|
| 819 |
+
console.log('[SSE VARIABLE] Variable creation result sent successfully');
|
| 820 |
+
}).catch(err => {
|
| 821 |
+
console.error('[SSE VARIABLE] Error sending variable creation result:', err);
|
| 822 |
+
});
|
| 823 |
+
}
|
| 824 |
+
} catch (err) {
|
| 825 |
+
console.error('[SSE VARIABLE] Error processing message:', err);
|
| 826 |
+
}
|
| 827 |
+
};
|
| 828 |
+
|
| 829 |
+
eventSource.onerror = (error) => {
|
| 830 |
+
console.error('[SSE VARIABLE] Connection error:', error);
|
| 831 |
+
// Reconnect after 5 seconds
|
| 832 |
+
setTimeout(() => {
|
| 833 |
+
console.log('[SSE VARIABLE] Attempting to reconnect...');
|
| 834 |
+
setupVariableStream();
|
| 835 |
+
}, 5000);
|
| 836 |
+
};
|
| 837 |
+
|
| 838 |
+
eventSource.onopen = () => {
|
| 839 |
+
console.log('[SSE VARIABLE] Connected to variable stream');
|
| 840 |
+
};
|
| 841 |
+
};
|
| 842 |
+
|
| 843 |
+
// Start the variable SSE connection
|
| 844 |
+
setupVariableStream();
|
| 845 |
+
|
| 846 |
// Observe any size change to the blockly container
|
| 847 |
const observer = new ResizeObserver(() => {
|
| 848 |
Blockly.svgResize(ws);
|