Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
67dfe8d
1
Parent(s):
ab1ed73
Use one SSE stream
Browse files- project/chat.py +55 -132
- project/src/index.js +40 -117
- project/unified_server.py +3 -11
project/chat.py
CHANGED
|
@@ -223,16 +223,16 @@ def create_variable(var_name):
|
|
| 223 |
traceback.print_exc()
|
| 224 |
return f"Error creating variable: {str(e)}"
|
| 225 |
|
| 226 |
-
# Server-Sent Events endpoint for
|
| 227 |
-
@app.get("/
|
| 228 |
-
async def
|
| 229 |
-
"""
|
| 230 |
|
| 231 |
-
async def clear_sent_request(sent_requests,
|
| 232 |
-
"""Clear
|
| 233 |
await asyncio.sleep(delay)
|
| 234 |
-
if
|
| 235 |
-
sent_requests.discard(
|
| 236 |
|
| 237 |
async def event_generator():
|
| 238 |
sent_requests = set() # Track sent requests to avoid duplicates
|
|
@@ -240,23 +240,60 @@ async def create_stream():
|
|
| 240 |
|
| 241 |
while True:
|
| 242 |
try:
|
| 243 |
-
# Check
|
| 244 |
-
if not
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
creation_request = creation_queue.get_nowait()
|
| 246 |
request_id = creation_request.get("request_id")
|
|
|
|
| 247 |
|
| 248 |
# Avoid sending duplicate requests too quickly
|
| 249 |
-
if
|
| 250 |
-
sent_requests.add(
|
| 251 |
-
|
|
|
|
| 252 |
yield f"data: {json.dumps(creation_request)}\n\n"
|
| 253 |
|
| 254 |
# Clear from sent_requests after 10 seconds
|
| 255 |
-
asyncio.create_task(clear_sent_request(sent_requests,
|
| 256 |
else:
|
| 257 |
-
print(f"[SSE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
else:
|
| 261 |
# Send a heartbeat every 30 seconds to keep connection alive
|
| 262 |
heartbeat_counter += 1
|
|
@@ -264,10 +301,11 @@ async def create_stream():
|
|
| 264 |
yield f"data: {json.dumps({'heartbeat': True})}\n\n"
|
| 265 |
heartbeat_counter = 0
|
| 266 |
await asyncio.sleep(0.1)
|
|
|
|
| 267 |
except queue.Empty:
|
| 268 |
await asyncio.sleep(0.1)
|
| 269 |
except Exception as e:
|
| 270 |
-
print(f"[SSE
|
| 271 |
await asyncio.sleep(1)
|
| 272 |
|
| 273 |
return StreamingResponse(
|
|
@@ -299,63 +337,6 @@ async def creation_result(request: Request):
|
|
| 299 |
|
| 300 |
return {"received": True}
|
| 301 |
|
| 302 |
-
# Server-Sent Events endpoint for deletion requests
|
| 303 |
-
@app.get("/delete_stream")
|
| 304 |
-
async def delete_stream():
|
| 305 |
-
"""Stream deletion requests to the frontend using Server-Sent Events"""
|
| 306 |
-
|
| 307 |
-
async def clear_sent_request(sent_requests, block_id, delay):
|
| 308 |
-
"""Clear block_id from sent_requests after delay seconds"""
|
| 309 |
-
await asyncio.sleep(delay)
|
| 310 |
-
if block_id in sent_requests:
|
| 311 |
-
sent_requests.discard(block_id)
|
| 312 |
-
|
| 313 |
-
async def event_generator():
|
| 314 |
-
sent_requests = set() # Track sent requests to avoid duplicates
|
| 315 |
-
heartbeat_counter = 0
|
| 316 |
-
|
| 317 |
-
while True:
|
| 318 |
-
try:
|
| 319 |
-
# Check for deletion requests (non-blocking)
|
| 320 |
-
if not deletion_queue.empty():
|
| 321 |
-
deletion_request = deletion_queue.get_nowait()
|
| 322 |
-
block_id = deletion_request.get("block_id")
|
| 323 |
-
|
| 324 |
-
# Avoid sending duplicate requests too quickly
|
| 325 |
-
if block_id not in sent_requests:
|
| 326 |
-
sent_requests.add(block_id)
|
| 327 |
-
print(f"[SSE SEND] Sending deletion request for block: {block_id}")
|
| 328 |
-
yield f"data: {json.dumps(deletion_request)}\n\n"
|
| 329 |
-
|
| 330 |
-
# Clear from sent_requests after 10 seconds
|
| 331 |
-
asyncio.create_task(clear_sent_request(sent_requests, block_id, 10))
|
| 332 |
-
else:
|
| 333 |
-
print(f"[SSE SKIP] Skipping duplicate request for block: {block_id}")
|
| 334 |
-
|
| 335 |
-
await asyncio.sleep(0.1) # Small delay between messages
|
| 336 |
-
else:
|
| 337 |
-
# Send a heartbeat every 30 seconds to keep connection alive
|
| 338 |
-
heartbeat_counter += 1
|
| 339 |
-
if heartbeat_counter >= 300: # 300 * 0.1 = 30 seconds
|
| 340 |
-
yield f"data: {json.dumps({'heartbeat': True})}\n\n"
|
| 341 |
-
heartbeat_counter = 0
|
| 342 |
-
await asyncio.sleep(0.1)
|
| 343 |
-
except queue.Empty:
|
| 344 |
-
await asyncio.sleep(0.1)
|
| 345 |
-
except Exception as e:
|
| 346 |
-
print(f"[SSE ERROR] {e}")
|
| 347 |
-
await asyncio.sleep(1)
|
| 348 |
-
|
| 349 |
-
return StreamingResponse(
|
| 350 |
-
event_generator(),
|
| 351 |
-
media_type="text/event-stream",
|
| 352 |
-
headers={
|
| 353 |
-
"Cache-Control": "no-cache",
|
| 354 |
-
"Connection": "keep-alive",
|
| 355 |
-
"X-Accel-Buffering": "no",
|
| 356 |
-
}
|
| 357 |
-
)
|
| 358 |
-
|
| 359 |
# Endpoint to receive deletion results from frontend
|
| 360 |
@app.post("/deletion_result")
|
| 361 |
async def deletion_result(request: Request):
|
|
@@ -374,64 +355,6 @@ async def deletion_result(request: Request):
|
|
| 374 |
|
| 375 |
return {"received": True}
|
| 376 |
|
| 377 |
-
# Server-Sent Events endpoint for variable creation requests
|
| 378 |
-
@app.get("/variable_stream")
|
| 379 |
-
async def variable_stream():
|
| 380 |
-
"""Stream variable creation requests to the frontend using Server-Sent Events"""
|
| 381 |
-
|
| 382 |
-
async def clear_sent_request(sent_requests, request_id, delay):
|
| 383 |
-
"""Clear request_id from sent_requests after delay seconds"""
|
| 384 |
-
await asyncio.sleep(delay)
|
| 385 |
-
if request_id in sent_requests:
|
| 386 |
-
sent_requests.discard(request_id)
|
| 387 |
-
|
| 388 |
-
async def event_generator():
|
| 389 |
-
sent_requests = set() # Track sent requests to avoid duplicates
|
| 390 |
-
heartbeat_counter = 0
|
| 391 |
-
|
| 392 |
-
while True:
|
| 393 |
-
try:
|
| 394 |
-
# Check for variable creation requests (non-blocking)
|
| 395 |
-
if not variable_queue.empty():
|
| 396 |
-
var_request = variable_queue.get_nowait()
|
| 397 |
-
request_id = var_request.get("request_id")
|
| 398 |
-
|
| 399 |
-
# Avoid sending duplicate requests too quickly
|
| 400 |
-
if request_id not in sent_requests:
|
| 401 |
-
sent_requests.add(request_id)
|
| 402 |
-
print(f"[SSE VARIABLE SEND] Sending variable creation request with ID: {request_id}")
|
| 403 |
-
yield f"data: {json.dumps(var_request)}\n\n"
|
| 404 |
-
|
| 405 |
-
# Clear from sent_requests after 10 seconds
|
| 406 |
-
asyncio.create_task(clear_sent_request(sent_requests, request_id, 10))
|
| 407 |
-
else:
|
| 408 |
-
print(f"[SSE VARIABLE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 409 |
-
|
| 410 |
-
await asyncio.sleep(0.1) # Small delay between messages
|
| 411 |
-
else:
|
| 412 |
-
# Send a heartbeat every 30 seconds to keep connection alive
|
| 413 |
-
heartbeat_counter += 1
|
| 414 |
-
if heartbeat_counter >= 300: # 300 * 0.1 = 30 seconds
|
| 415 |
-
yield f"data: {json.dumps({'heartbeat': True})}\n\n"
|
| 416 |
-
heartbeat_counter = 0
|
| 417 |
-
await asyncio.sleep(0.1)
|
| 418 |
-
|
| 419 |
-
except queue.Empty:
|
| 420 |
-
await asyncio.sleep(0.1)
|
| 421 |
-
except Exception as e:
|
| 422 |
-
print(f"[SSE VARIABLE ERROR] {e}")
|
| 423 |
-
await asyncio.sleep(1)
|
| 424 |
-
|
| 425 |
-
return StreamingResponse(
|
| 426 |
-
event_generator(),
|
| 427 |
-
media_type="text/event-stream",
|
| 428 |
-
headers={
|
| 429 |
-
"Cache-Control": "no-cache",
|
| 430 |
-
"Connection": "keep-alive",
|
| 431 |
-
"X-Accel-Buffering": "no",
|
| 432 |
-
}
|
| 433 |
-
)
|
| 434 |
-
|
| 435 |
# Endpoint to receive variable creation results from frontend
|
| 436 |
@app.post("/variable_result")
|
| 437 |
async def variable_result(request: Request):
|
|
|
|
| 223 |
traceback.print_exc()
|
| 224 |
return f"Error creating variable: {str(e)}"
|
| 225 |
|
| 226 |
+
# Unified Server-Sent Events endpoint for all workspace operations
|
| 227 |
+
@app.get("/unified_stream")
|
| 228 |
+
async def unified_stream():
|
| 229 |
+
"""Unified SSE endpoint for delete, create, and variable operations"""
|
| 230 |
|
| 231 |
+
async def clear_sent_request(sent_requests, request_key, delay):
|
| 232 |
+
"""Clear request_key from sent_requests after delay seconds"""
|
| 233 |
await asyncio.sleep(delay)
|
| 234 |
+
if request_key in sent_requests:
|
| 235 |
+
sent_requests.discard(request_key)
|
| 236 |
|
| 237 |
async def event_generator():
|
| 238 |
sent_requests = set() # Track sent requests to avoid duplicates
|
|
|
|
| 240 |
|
| 241 |
while True:
|
| 242 |
try:
|
| 243 |
+
# Check deletion queue
|
| 244 |
+
if not deletion_queue.empty():
|
| 245 |
+
deletion_request = deletion_queue.get_nowait()
|
| 246 |
+
block_id = deletion_request.get("block_id")
|
| 247 |
+
request_key = f"delete_{block_id}"
|
| 248 |
+
|
| 249 |
+
# Avoid sending duplicate requests too quickly
|
| 250 |
+
if request_key not in sent_requests:
|
| 251 |
+
sent_requests.add(request_key)
|
| 252 |
+
deletion_request["type"] = "delete" # Add type identifier
|
| 253 |
+
print(f"[SSE SEND] Sending deletion request for block: {block_id}")
|
| 254 |
+
yield f"data: {json.dumps(deletion_request)}\n\n"
|
| 255 |
+
|
| 256 |
+
# Clear from sent_requests after 10 seconds
|
| 257 |
+
asyncio.create_task(clear_sent_request(sent_requests, request_key, 10))
|
| 258 |
+
else:
|
| 259 |
+
print(f"[SSE SKIP] Skipping duplicate request for block: {block_id}")
|
| 260 |
+
|
| 261 |
+
# Check creation queue
|
| 262 |
+
elif not creation_queue.empty():
|
| 263 |
creation_request = creation_queue.get_nowait()
|
| 264 |
request_id = creation_request.get("request_id")
|
| 265 |
+
request_key = f"create_{request_id}"
|
| 266 |
|
| 267 |
# Avoid sending duplicate requests too quickly
|
| 268 |
+
if request_key not in sent_requests:
|
| 269 |
+
sent_requests.add(request_key)
|
| 270 |
+
creation_request["type"] = "create" # Add type identifier
|
| 271 |
+
print(f"[SSE SEND] Sending creation request with ID: {request_id}")
|
| 272 |
yield f"data: {json.dumps(creation_request)}\n\n"
|
| 273 |
|
| 274 |
# Clear from sent_requests after 10 seconds
|
| 275 |
+
asyncio.create_task(clear_sent_request(sent_requests, request_key, 10))
|
| 276 |
else:
|
| 277 |
+
print(f"[SSE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 278 |
+
|
| 279 |
+
# Check variable queue
|
| 280 |
+
elif not variable_queue.empty():
|
| 281 |
+
variable_request = variable_queue.get_nowait()
|
| 282 |
+
request_id = variable_request.get("request_id")
|
| 283 |
+
request_key = f"variable_{request_id}"
|
| 284 |
|
| 285 |
+
# Avoid sending duplicate requests too quickly
|
| 286 |
+
if request_key not in sent_requests:
|
| 287 |
+
sent_requests.add(request_key)
|
| 288 |
+
variable_request["type"] = "variable" # Add type identifier
|
| 289 |
+
print(f"[SSE SEND] Sending variable creation request with ID: {request_id}")
|
| 290 |
+
yield f"data: {json.dumps(variable_request)}\n\n"
|
| 291 |
+
|
| 292 |
+
# Clear from sent_requests after 10 seconds
|
| 293 |
+
asyncio.create_task(clear_sent_request(sent_requests, request_key, 10))
|
| 294 |
+
else:
|
| 295 |
+
print(f"[SSE SKIP] Skipping duplicate request for ID: {request_id}")
|
| 296 |
+
|
| 297 |
else:
|
| 298 |
# Send a heartbeat every 30 seconds to keep connection alive
|
| 299 |
heartbeat_counter += 1
|
|
|
|
| 301 |
yield f"data: {json.dumps({'heartbeat': True})}\n\n"
|
| 302 |
heartbeat_counter = 0
|
| 303 |
await asyncio.sleep(0.1)
|
| 304 |
+
|
| 305 |
except queue.Empty:
|
| 306 |
await asyncio.sleep(0.1)
|
| 307 |
except Exception as e:
|
| 308 |
+
print(f"[SSE ERROR] {e}")
|
| 309 |
await asyncio.sleep(1)
|
| 310 |
|
| 311 |
return StreamingResponse(
|
|
|
|
| 337 |
|
| 338 |
return {"received": True}
|
| 339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
# Endpoint to receive deletion results from frontend
|
| 341 |
@app.post("/deletion_result")
|
| 342 |
async def deletion_result(request: Request):
|
|
|
|
| 355 |
|
| 356 |
return {"received": True}
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
# Endpoint to receive variable creation results from frontend
|
| 359 |
@app.post("/variable_result")
|
| 360 |
async def variable_result(request: Request):
|
project/src/index.js
CHANGED
|
@@ -223,10 +223,10 @@ cleanWorkspace.addEventListener("click", () => {
|
|
| 223 |
ws.cleanUp();
|
| 224 |
});
|
| 225 |
|
| 226 |
-
// Set up SSE connection for
|
| 227 |
-
const
|
| 228 |
-
const eventSource = new EventSource('/
|
| 229 |
-
const processedRequests = new Set(); // Track processed
|
| 230 |
|
| 231 |
eventSource.onmessage = (event) => {
|
| 232 |
try {
|
|
@@ -235,19 +235,29 @@ const setupDeletionStream = () => {
|
|
| 235 |
// Skip heartbeat messages
|
| 236 |
if (data.heartbeat) return;
|
| 237 |
|
| 238 |
-
//
|
| 239 |
-
|
| 240 |
-
if (data.
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
return;
|
| 243 |
}
|
| 244 |
-
if (
|
| 245 |
-
processedRequests.add(
|
| 246 |
// Clear after 10 seconds to allow retries if needed
|
| 247 |
-
setTimeout(() => processedRequests.delete(
|
| 248 |
}
|
| 249 |
|
| 250 |
-
|
|
|
|
| 251 |
console.log('[SSE] Received deletion request for block:', data.block_id);
|
| 252 |
|
| 253 |
// Try to delete the block
|
|
@@ -292,53 +302,9 @@ const setupDeletionStream = () => {
|
|
| 292 |
console.error('[SSE] Error sending deletion result:', err);
|
| 293 |
});
|
| 294 |
}
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
};
|
| 299 |
-
|
| 300 |
-
eventSource.onerror = (error) => {
|
| 301 |
-
console.error('[SSE] Connection error:', error);
|
| 302 |
-
// Reconnect after 5 seconds
|
| 303 |
-
setTimeout(() => {
|
| 304 |
-
console.log('[SSE] Attempting to reconnect...');
|
| 305 |
-
setupDeletionStream();
|
| 306 |
-
}, 5000);
|
| 307 |
-
};
|
| 308 |
-
|
| 309 |
-
eventSource.onopen = () => {
|
| 310 |
-
console.log('[SSE] Connected to deletion stream');
|
| 311 |
-
};
|
| 312 |
-
};
|
| 313 |
-
|
| 314 |
-
// Start the SSE connection
|
| 315 |
-
setupDeletionStream();
|
| 316 |
-
|
| 317 |
-
// Set up SSE connection for creation requests
|
| 318 |
-
const setupCreationStream = () => {
|
| 319 |
-
const eventSource = new EventSource('/create_stream');
|
| 320 |
-
const processedRequests = new Set(); // Track processed creation requests
|
| 321 |
-
|
| 322 |
-
eventSource.onmessage = (event) => {
|
| 323 |
-
try {
|
| 324 |
-
const data = JSON.parse(event.data);
|
| 325 |
-
|
| 326 |
-
// Skip heartbeat messages
|
| 327 |
-
if (data.heartbeat) return;
|
| 328 |
-
|
| 329 |
-
// Skip if we've already processed this request
|
| 330 |
-
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 331 |
-
console.log('[SSE CREATE] Skipping duplicate creation request:', data.request_id);
|
| 332 |
-
return;
|
| 333 |
-
}
|
| 334 |
-
if (data.request_id) {
|
| 335 |
-
processedRequests.add(data.request_id);
|
| 336 |
-
// Clear after 10 seconds to allow retries if needed
|
| 337 |
-
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 338 |
-
}
|
| 339 |
-
|
| 340 |
-
if (data.block_spec && data.request_id) {
|
| 341 |
-
console.log('[SSE CREATE] Received creation request:', data.request_id, data.block_spec);
|
| 342 |
|
| 343 |
let success = false;
|
| 344 |
let error = null;
|
|
@@ -705,52 +671,9 @@ const setupCreationStream = () => {
|
|
| 705 |
console.error('[SSE CREATE] Error sending creation result:', err);
|
| 706 |
});
|
| 707 |
}
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
};
|
| 712 |
-
|
| 713 |
-
eventSource.onerror = (error) => {
|
| 714 |
-
console.error('[SSE CREATE] Connection error:', error);
|
| 715 |
-
// Reconnect after 5 seconds
|
| 716 |
-
setTimeout(() => {
|
| 717 |
-
console.log('[SSE CREATE] Attempting to reconnect...');
|
| 718 |
-
setupCreationStream();
|
| 719 |
-
}, 5000);
|
| 720 |
-
};
|
| 721 |
-
|
| 722 |
-
eventSource.onopen = () => {
|
| 723 |
-
console.log('[SSE CREATE] Connected to creation stream');
|
| 724 |
-
};
|
| 725 |
-
};
|
| 726 |
-
|
| 727 |
-
// Start the creation SSE connection
|
| 728 |
-
setupCreationStream();
|
| 729 |
-
|
| 730 |
-
const setupVariableStream = () => {
|
| 731 |
-
const eventSource = new EventSource('/variable_stream');
|
| 732 |
-
const processedRequests = new Set(); // Track processed variable requests
|
| 733 |
-
|
| 734 |
-
eventSource.onmessage = (event) => {
|
| 735 |
-
try {
|
| 736 |
-
const data = JSON.parse(event.data);
|
| 737 |
-
|
| 738 |
-
// Skip heartbeat messages
|
| 739 |
-
if (data.heartbeat) return;
|
| 740 |
-
|
| 741 |
-
// Skip if we've already processed this request
|
| 742 |
-
if (data.request_id && processedRequests.has(data.request_id)) {
|
| 743 |
-
console.log('[SSE VARIABLE] Skipping duplicate variable request:', data.request_id);
|
| 744 |
-
return;
|
| 745 |
-
}
|
| 746 |
-
if (data.request_id) {
|
| 747 |
-
processedRequests.add(data.request_id);
|
| 748 |
-
// Clear after 10 seconds to allow retries if needed
|
| 749 |
-
setTimeout(() => processedRequests.delete(data.request_id), 10000);
|
| 750 |
-
}
|
| 751 |
-
|
| 752 |
-
if (data.variable_name && data.request_id) {
|
| 753 |
-
console.log('[SSE VARIABLE] Received variable creation request:', data.request_id, data.variable_name);
|
| 754 |
|
| 755 |
let success = false;
|
| 756 |
let error = null;
|
|
@@ -766,18 +689,18 @@ const setupVariableStream = () => {
|
|
| 766 |
if (variableModel) {
|
| 767 |
variableId = variableModel.getId();
|
| 768 |
success = true;
|
| 769 |
-
console.log('[SSE
|
| 770 |
} else {
|
| 771 |
throw new Error('Failed to create variable model');
|
| 772 |
}
|
| 773 |
|
| 774 |
} catch (e) {
|
| 775 |
error = e.toString();
|
| 776 |
-
console.error('[SSE
|
| 777 |
}
|
| 778 |
|
| 779 |
// Send result back to backend immediately
|
| 780 |
-
console.log('[SSE
|
| 781 |
request_id: data.request_id,
|
| 782 |
success,
|
| 783 |
error,
|
|
@@ -794,32 +717,32 @@ const setupVariableStream = () => {
|
|
| 794 |
variable_id: variableId
|
| 795 |
})
|
| 796 |
}).then(response => {
|
| 797 |
-
console.log('[SSE
|
| 798 |
}).catch(err => {
|
| 799 |
-
console.error('[SSE
|
| 800 |
});
|
| 801 |
}
|
| 802 |
} catch (err) {
|
| 803 |
-
console.error('[SSE
|
| 804 |
}
|
| 805 |
};
|
| 806 |
|
| 807 |
eventSource.onerror = (error) => {
|
| 808 |
-
console.error('[SSE
|
| 809 |
// Reconnect after 5 seconds
|
| 810 |
setTimeout(() => {
|
| 811 |
-
console.log('[SSE
|
| 812 |
-
|
| 813 |
}, 5000);
|
| 814 |
};
|
| 815 |
|
| 816 |
eventSource.onopen = () => {
|
| 817 |
-
console.log('[SSE
|
| 818 |
};
|
| 819 |
};
|
| 820 |
|
| 821 |
-
// Start the
|
| 822 |
-
|
| 823 |
|
| 824 |
// Observe any size change to the blockly container
|
| 825 |
const observer = new ResizeObserver(() => {
|
|
|
|
| 223 |
ws.cleanUp();
|
| 224 |
});
|
| 225 |
|
| 226 |
+
// Set up unified SSE connection for all workspace operations
|
| 227 |
+
const setupUnifiedStream = () => {
|
| 228 |
+
const eventSource = new EventSource('/unified_stream');
|
| 229 |
+
const processedRequests = new Set(); // Track processed requests
|
| 230 |
|
| 231 |
eventSource.onmessage = (event) => {
|
| 232 |
try {
|
|
|
|
| 235 |
// Skip heartbeat messages
|
| 236 |
if (data.heartbeat) return;
|
| 237 |
|
| 238 |
+
// Determine request key based on type
|
| 239 |
+
let requestKey;
|
| 240 |
+
if (data.type === 'delete') {
|
| 241 |
+
requestKey = `delete_${data.block_id}`;
|
| 242 |
+
} else if (data.type === 'create') {
|
| 243 |
+
requestKey = `create_${data.request_id}`;
|
| 244 |
+
} else if (data.type === 'variable') {
|
| 245 |
+
requestKey = `variable_${data.request_id}`;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
// Skip if we've already processed this request
|
| 249 |
+
if (requestKey && processedRequests.has(requestKey)) {
|
| 250 |
+
console.log('[SSE] Skipping duplicate request:', requestKey);
|
| 251 |
return;
|
| 252 |
}
|
| 253 |
+
if (requestKey) {
|
| 254 |
+
processedRequests.add(requestKey);
|
| 255 |
// Clear after 10 seconds to allow retries if needed
|
| 256 |
+
setTimeout(() => processedRequests.delete(requestKey), 10000);
|
| 257 |
}
|
| 258 |
|
| 259 |
+
// Handle deletion requests
|
| 260 |
+
if (data.type === 'delete' && data.block_id) {
|
| 261 |
console.log('[SSE] Received deletion request for block:', data.block_id);
|
| 262 |
|
| 263 |
// Try to delete the block
|
|
|
|
| 302 |
console.error('[SSE] Error sending deletion result:', err);
|
| 303 |
});
|
| 304 |
}
|
| 305 |
+
// Handle creation requests
|
| 306 |
+
else if (data.type === 'create' && data.block_spec && data.request_id) {
|
| 307 |
+
console.log('[SSE] Received creation request:', data.request_id, data.block_spec);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
let success = false;
|
| 310 |
let error = null;
|
|
|
|
| 671 |
console.error('[SSE CREATE] Error sending creation result:', err);
|
| 672 |
});
|
| 673 |
}
|
| 674 |
+
// Handle variable creation requests
|
| 675 |
+
else if (data.type === 'variable' && data.variable_name && data.request_id) {
|
| 676 |
+
console.log('[SSE] Received variable creation request:', data.request_id, data.variable_name);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
|
| 678 |
let success = false;
|
| 679 |
let error = null;
|
|
|
|
| 689 |
if (variableModel) {
|
| 690 |
variableId = variableModel.getId();
|
| 691 |
success = true;
|
| 692 |
+
console.log('[SSE] Successfully created variable:', variableName, 'with ID:', variableId);
|
| 693 |
} else {
|
| 694 |
throw new Error('Failed to create variable model');
|
| 695 |
}
|
| 696 |
|
| 697 |
} catch (e) {
|
| 698 |
error = e.toString();
|
| 699 |
+
console.error('[SSE] Error creating variable:', e);
|
| 700 |
}
|
| 701 |
|
| 702 |
// Send result back to backend immediately
|
| 703 |
+
console.log('[SSE] Sending variable creation result:', {
|
| 704 |
request_id: data.request_id,
|
| 705 |
success,
|
| 706 |
error,
|
|
|
|
| 717 |
variable_id: variableId
|
| 718 |
})
|
| 719 |
}).then(response => {
|
| 720 |
+
console.log('[SSE] Variable creation result sent successfully');
|
| 721 |
}).catch(err => {
|
| 722 |
+
console.error('[SSE] Error sending variable creation result:', err);
|
| 723 |
});
|
| 724 |
}
|
| 725 |
} catch (err) {
|
| 726 |
+
console.error('[SSE] Error processing message:', err);
|
| 727 |
}
|
| 728 |
};
|
| 729 |
|
| 730 |
eventSource.onerror = (error) => {
|
| 731 |
+
console.error('[SSE] Connection error:', error);
|
| 732 |
// Reconnect after 5 seconds
|
| 733 |
setTimeout(() => {
|
| 734 |
+
console.log('[SSE] Attempting to reconnect...');
|
| 735 |
+
setupUnifiedStream();
|
| 736 |
}, 5000);
|
| 737 |
};
|
| 738 |
|
| 739 |
eventSource.onopen = () => {
|
| 740 |
+
console.log('[SSE] Connected to unified stream');
|
| 741 |
};
|
| 742 |
};
|
| 743 |
|
| 744 |
+
// Start the unified SSE connection
|
| 745 |
+
setupUnifiedStream();
|
| 746 |
|
| 747 |
// Observe any size change to the blockly container
|
| 748 |
const observer = new ResizeObserver(() => {
|
project/unified_server.py
CHANGED
|
@@ -34,26 +34,18 @@ async def update_chat_route(request: Request):
|
|
| 34 |
async def set_api_key_chat_route(request: Request):
|
| 35 |
return await chat.set_api_key_chat(request)
|
| 36 |
|
| 37 |
-
@app.get("/
|
| 38 |
-
async def
|
| 39 |
-
return await chat.
|
| 40 |
|
| 41 |
@app.post("/creation_result")
|
| 42 |
async def creation_result_route(request: Request):
|
| 43 |
return await chat.creation_result(request)
|
| 44 |
|
| 45 |
-
@app.get("/delete_stream")
|
| 46 |
-
async def delete_stream_route():
|
| 47 |
-
return await chat.delete_stream()
|
| 48 |
-
|
| 49 |
@app.post("/deletion_result")
|
| 50 |
async def deletion_result_route(request: Request):
|
| 51 |
return await chat.deletion_result(request)
|
| 52 |
|
| 53 |
-
@app.get("/variable_stream")
|
| 54 |
-
async def variable_stream_route():
|
| 55 |
-
return await chat.variable_stream()
|
| 56 |
-
|
| 57 |
@app.post("/variable_result")
|
| 58 |
async def variable_result_route(request: Request):
|
| 59 |
return await chat.variable_result(request)
|
|
|
|
| 34 |
async def set_api_key_chat_route(request: Request):
|
| 35 |
return await chat.set_api_key_chat(request)
|
| 36 |
|
| 37 |
+
@app.get("/unified_stream")
|
| 38 |
+
async def unified_stream_route():
|
| 39 |
+
return await chat.unified_stream()
|
| 40 |
|
| 41 |
@app.post("/creation_result")
|
| 42 |
async def creation_result_route(request: Request):
|
| 43 |
return await chat.creation_result(request)
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
@app.post("/deletion_result")
|
| 46 |
async def deletion_result_route(request: Request):
|
| 47 |
return await chat.deletion_result(request)
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
@app.post("/variable_result")
|
| 50 |
async def variable_result_route(request: Request):
|
| 51 |
return await chat.variable_result(request)
|