owenkaplinsky commited on
Commit
67dfe8d
·
1 Parent(s): ab1ed73

Use one SSE stream

Browse files
Files changed (3) hide show
  1. project/chat.py +55 -132
  2. project/src/index.js +40 -117
  3. 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 creation requests
227
- @app.get("/create_stream")
228
- async def create_stream():
229
- """Stream creation requests to the frontend using Server-Sent Events"""
230
 
231
- async def clear_sent_request(sent_requests, request_id, delay):
232
- """Clear request_id from sent_requests after delay seconds"""
233
  await asyncio.sleep(delay)
234
- if request_id in sent_requests:
235
- sent_requests.discard(request_id)
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 for creation requests (non-blocking)
244
- if not creation_queue.empty():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 request_id not in sent_requests:
250
- sent_requests.add(request_id)
251
- print(f"[SSE CREATE SEND] Sending creation request with ID: {request_id}")
 
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, request_id, 10))
256
  else:
257
- print(f"[SSE CREATE SKIP] Skipping duplicate request for ID: {request_id}")
 
 
 
 
 
 
258
 
259
- await asyncio.sleep(0.1) # Small delay between messages
 
 
 
 
 
 
 
 
 
 
 
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 CREATE ERROR] {e}")
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 deletion requests
227
- const setupDeletionStream = () => {
228
- const eventSource = new EventSource('/delete_stream');
229
- const processedRequests = new Set(); // Track processed deletion requests
230
 
231
  eventSource.onmessage = (event) => {
232
  try {
@@ -235,19 +235,29 @@ const setupDeletionStream = () => {
235
  // Skip heartbeat messages
236
  if (data.heartbeat) return;
237
 
238
- // Skip if we've already processed this exact request
239
- const requestKey = `${data.block_id}_${Date.now()}`;
240
- if (data.block_id && processedRequests.has(data.block_id)) {
241
- console.log('[SSE] Skipping duplicate deletion request for:', data.block_id);
 
 
 
 
 
 
 
 
 
242
  return;
243
  }
244
- if (data.block_id) {
245
- processedRequests.add(data.block_id);
246
  // Clear after 10 seconds to allow retries if needed
247
- setTimeout(() => processedRequests.delete(data.block_id), 10000);
248
  }
249
 
250
- if (data.block_id) {
 
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
- } catch (err) {
296
- console.error('[SSE] Error processing message:', err);
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
- } catch (err) {
709
- console.error('[SSE CREATE] Error processing message:', err);
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 VARIABLE] Successfully created variable:', variableName, 'with ID:', variableId);
770
  } else {
771
  throw new Error('Failed to create variable model');
772
  }
773
 
774
  } catch (e) {
775
  error = e.toString();
776
- console.error('[SSE VARIABLE] Error creating variable:', e);
777
  }
778
 
779
  // Send result back to backend immediately
780
- console.log('[SSE VARIABLE] Sending variable creation result:', {
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 VARIABLE] Variable creation result sent successfully');
798
  }).catch(err => {
799
- console.error('[SSE VARIABLE] Error sending variable creation result:', err);
800
  });
801
  }
802
  } catch (err) {
803
- console.error('[SSE VARIABLE] Error processing message:', err);
804
  }
805
  };
806
 
807
  eventSource.onerror = (error) => {
808
- console.error('[SSE VARIABLE] Connection error:', error);
809
  // Reconnect after 5 seconds
810
  setTimeout(() => {
811
- console.log('[SSE VARIABLE] Attempting to reconnect...');
812
- setupVariableStream();
813
  }, 5000);
814
  };
815
 
816
  eventSource.onopen = () => {
817
- console.log('[SSE VARIABLE] Connected to variable stream');
818
  };
819
  };
820
 
821
- // Start the variable SSE connection
822
- setupVariableStream();
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("/create_stream")
38
- async def create_stream_route():
39
- return await chat.create_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.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)