acecalisto3 commited on
Commit
25f6052
·
verified ·
1 Parent(s): 886f2ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -111
app.py CHANGED
@@ -48,7 +48,7 @@ logger = logging.getLogger(__name__)
48
  executor = ThreadPoolExecutor(max_workers=4)
49
 
50
  # Example HF models (replace with your actual models)
51
- HF_MODELS = {{{{
52
  "Mistral-8x7B": "mistralai/Mixtral-8x7B-Instruct-v0.1",
53
  "Llama-2-7B-chat": "huggingface/llama-2-7b-chat-hf",
54
  "CodeLlama-34B": "codellama/CodeLlama-34b-Instruct-hf",
@@ -77,12 +77,12 @@ class OTCodeEditor:
77
  def __init__(self, initial_value: Dict[str, str]):
78
  self.files: Dict[str, str] = initial_value.copy()
79
  self.revision = 0 # Basic revision counter, not used for OT logic
80
- logger.debug(f"OTCodeEditor initialized with files: {{{{list(self.files.keys())}}")
81
 
82
  def apply_delta(self, delta: Dict[str, Any]):
83
  # VERY basic placeholder: This logs the delta but does NOT perform OT.
84
  # It does NOT handle concurrent edits safely.
85
- logger.warning(f"Placeholder apply_delta called. Delta: {{{{str(delta)[:200]}}. "
86
  "WARNING: Full Operational Transformation is NOT implemented. Concurrent edits are UNSAFE.")
87
  # Increment revision regardless for basic tracking
88
  self.revision += 1
@@ -108,7 +108,7 @@ try:
108
  block_shadow="*shadow_drop_lg",
109
  )
110
  except AttributeError as e:
111
- logger.warning(f"Could not apply all theme settings (might be Gradio version difference): {{{{e}}. Using default Soft theme.")
112
  theme = gr.themes.Soft()
113
 
114
 
@@ -123,25 +123,25 @@ class WebhookHandler(BaseHTTPRequestHandler):
123
  payload_bytes = self.rfile.read(content_length)
124
  payload = json.loads(payload_bytes.decode('utf-8'))
125
  except json.JSONDecodeError:
126
- logger.error(f"Invalid JSON payload received: {{{{payload_bytes[:500]}}")
127
  self.send_response(400)
128
  self.send_header("Content-type", "text/plain")
129
  self.end_headers()
130
  self.wfile.write(b"Invalid JSON payload")
131
  return
132
  except Exception as e:
133
- logger.error(f"Error reading webhook payload: {{{{e}}")
134
  self.send_response(500)
135
  self.end_headers()
136
  return
137
 
138
  event = self.headers.get('X-GitHub-Event')
139
  delivery_id = self.headers.get('X-GitHub-Delivery')
140
- logger.info(f"Received GitHub webhook event: {{{{}event}} (Delivery ID: {{{{delivery_id}})")
141
 
142
  if event == 'issues' and WebhookHandler.manager_instance and WebhookHandler.main_loop:
143
  action = payload.get('action')
144
- logger.info(f"Issue action: {{{{action}}")
145
  # Handle common actions that affect issue state or content
146
  if action in ['opened', 'reopened', 'closed', 'assigned', 'unassigned', 'edited', 'labeled', 'unlabeled', 'milestoned', 'demilestoned']:
147
  if WebhookHandler.main_loop.is_running():
@@ -150,15 +150,15 @@ class WebhookHandler(BaseHTTPRequestHandler):
150
  WebhookHandler.manager_instance.handle_webhook_event(event, action, payload),
151
  WebhookHandler.main_loop
152
  )
153
- logger.debug(f"Scheduled webhook processing for action '{{{{action}}' in main loop.")
154
  else:
155
  logger.error("Asyncio event loop is not running in the target thread for webhook.")
156
  else:
157
- logger.info(f"Webhook action '{{{{action}}' received but not actively handled by current logic.")
158
  elif event == 'ping':
159
  logger.info("Received GitHub webhook ping.")
160
  else:
161
- logger.warning(f"Unhandled event type: {{{{event}} or manager/loop not initialized.")
162
 
163
  self.send_response(200)
164
  self.send_header("Content-type", "text/plain")
@@ -2158,10 +2158,10 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2158
  logger.info(f"Generated Client ID for WebSocket: {{client_id}}")
2159
  return f"""
2160
  <script>
2161
- (function() {{{{ // IIFE
2162
- if (window.collabWsInitialized) {{{{
2163
  console.log('WebSocket script already initialized. Skipping setup.');
2164
- if (!window.collabWs || window.collabWs.readyState === WebSocket.CLOSED) {{{{
2165
  console.log('Attempting reconnect from existing script...');
2166
  connectWebSocket();
2167
  }}
@@ -2176,12 +2176,12 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2176
  const wsPort = {{ws_port};
2177
  const hostname = window.location.hostname;
2178
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
2179
- if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.gradio.live')) {{{{
2180
- wsUrl = `${{{{protocol}}//${{{{hostname}}:${{{{wsPort}}`;
2181
  console.log('Detected local/gradio.live environment, using direct WebSocket URL:', wsUrl);
2182
- }} else {{{{
2183
  const wsPath = '/ws';
2184
- wsUrl = `${{{{protocol}}//${{{{window.location.host}}${{{{wsPath}}`;
2185
  console.log('Detected non-local environment, assuming proxied WebSocket URL:', wsUrl);
2186
  }}
2187
  let collabWs = null;
@@ -2196,42 +2196,42 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2196
  let editorChangeListenerAttached = false;
2197
  let lastSentDeltaTimestamp = 0;
2198
  const deltaSendDebounceMs = 250;
2199
- function updateCollabList(collaborators) {{{{
2200
  const collabListDiv = document.getElementById('collab-list');
2201
  if (!collabListDiv) return;
2202
- const activeCollaborators = Object.entries(collaborators || {{{{}})
2203
  .filter(([id, info]) => id !== clientId);
2204
- if (activeCollaborators.length > 0) {{{{
2205
  collabListDiv.innerHTML = activeCollaborators
2206
  .map(([id, info]) => `
2207
- <div class="collab-item" title="ID: ${{{{id}}">
2208
- <span style="font-weight: 500;">${{{{info.name || id.substring(0, 8)}}:</span>
2209
- <span style="color: #555;">${{{{info.status || 'Idle'}}</span>
2210
  </div>`)
2211
  .join('');
2212
- }} else {{{{
2213
  collabListDiv.innerHTML = '<span style="color: #6b7280;">You are the only active user.</span>';
2214
  }}
2215
  }}
2216
- function updateStatusBar(message, isError = false) {{{{
2217
  const statusBar = document.querySelector('#status_output_txt textarea');
2218
- if (statusBar) {{{{
2219
  const timestamp = new Date().toLocaleTimeString();
2220
- statusBar.value = `[${{{{timestamp}}] ${{{{message}}`;
2221
  statusBar.style.color = isError ? '#D32F2F' : '#333';
2222
  statusBar.style.fontWeight = isError ? 'bold' : 'normal';
2223
  }}
2224
  }}
2225
- function connectWebSocket() {{{{
2226
- if (collabWs && (collabWs.readyState === WebSocket.OPEN || collabWs.readyState === WebSocket.CONNECTING)) {{{{
2227
- console.log(`WebSocket already ${{{{(collabWs.readyState === WebSocket.OPEN) ? 'open' : 'connecting'}}. State: ${{{{collabWs.readyState}}`);
2228
  return;
2229
  }}
2230
- console.log(`Attempting WebSocket connection to ${{{{wsUrl}} (Attempt ${{{{reconnectAttempts + 1}}/${{{{maxReconnectAttempts}})...`);
2231
- updateStatusBar(`Connecting collaboration service (Attempt ${{{{reconnectAttempts + 1}})...`);
2232
- try {{{{
2233
  collabWs = new WebSocket(wsUrl);
2234
- }} catch (e) {{{{
2235
  console.error("WebSocket constructor failed:", e);
2236
  updateStatusBar("Collaboration connection failed (init error).", true);
2237
  // Handle reconnection attempt here as well
@@ -2239,121 +2239,121 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2239
  return; // Prevent setting up listeners on failed constructor
2240
  }}
2241
  window.collabWs = collabWs;
2242
- collabWs.onopen = function(event) {{{{
2243
  console.log('WebSocket connection established.');
2244
  updateStatusBar('Collaboration connected.');
2245
  reconnectAttempts = 0;
2246
- if(connectInterval) {{{{ clearInterval(connectInterval); connectInterval = null; }}
2247
- sendWsMessage({{{{ type: 'join', clientId: clientId, name: `User_${{{{clientId.substring(0,4)}}` }});
2248
  setupCodeEditorListener();
2249
  }};
2250
- collabWs.onmessage = function(event) {{{{
2251
- try {{{{
2252
  const data = JSON.parse(event.data);
2253
- if (data.type === 'collaboration_status') {{{{
2254
  updateCollabList(data.collaborators);
2255
- }} else if (data.type === 'code_update') {{{{
2256
  const receivedIssueNum = parseInt(data.issue_num, 10);
2257
- if (aceEditorInstance && receivedIssueNum === currentIssueId && data.senderId !== clientId) {{{{
2258
- console.warn(`Applying remote delta for issue ${{{{receivedIssueNum}} from ${{{{data.senderId}}. WARNING: NO OT!`);
2259
- try {{{{
2260
  const delta = JSON.parse(data.delta);
2261
  // Add ignore flag for local change listener
2262
- aceEditorInstance.getSession().getDocument().applyDeltas([{{{{...delta, ignore: true}}]);
2263
- }} catch (e) {{{{ console.error('Failed to parse or apply remote delta:', e, data.delta); }}
2264
  }}
2265
- }} else if (data.type === 'issues_updated') {{{{
2266
  console.log('Received notification: Issue list updated on server.');
2267
  updateStatusBar('Issue list updated on server. Refreshing the page or re-scanning is recommended.');
2268
  const crawlButton = document.getElementById('crawl_btn');
2269
- if (crawlButton) {{{{
2270
  crawlButton.style.backgroundColor = '#fef08a';
2271
- setTimeout(() => {{{{ crawlButton.style.backgroundColor = '' }}, 2000);
2272
  }}
2273
- }} else {{{{
2274
  console.warn("Received unknown WebSocket message type:", data.type, data);
2275
  }}
2276
- }} catch (e) {{{{
2277
  console.error('Failed to parse WebSocket message or update UI:', e, event.data);
2278
  }}
2279
  }};
2280
- collabWs.onclose = function(event) {{{{
2281
- console.warn(`WebSocket connection closed: Code=${{{{event.code}}, Reason='${{{{event.reason || 'N/A'}}', Clean=${{{{event.wasClean}}`);
2282
  handleWsClose(); // Centralize close handling
2283
  }};
2284
- collabWs.onerror = function(error) {{{{
2285
  console.error('WebSocket error event:', error);
2286
  updateStatusBar('Collaboration connection error.', true);
2287
  // onclose will likely fire after onerror.
2288
  }};
2289
  }}
2290
- function handleWsClose() {{{{
2291
  const collabListDiv = document.getElementById('collab-list');
2292
  if (collabListDiv) collabListDiv.innerHTML = '<span style="color: #D32F2F; font-weight: bold;">Collaboration Disconnected</span>';
2293
  updateStatusBar('Collaboration disconnected.', true);
2294
  collabWs = null; window.collabWs = null;
2295
  aceEditorInstance = null; // Clear editor instance on disconnect
2296
  editorChangeListenerAttached = false; // Allow re-attaching on reconnect
2297
- if (reconnectAttempts < maxReconnectAttempts) {{{{
2298
  const delay = Math.pow(2, reconnectAttempts) * 1500 + Math.random() * 1000;
2299
- console.log(`Attempting to reconnect WebSocket in approx. ${{{{Math.round(delay / 1000)}} seconds...`);
2300
  setTimeout(connectWebSocket, delay);
2301
  reconnectAttempts++;
2302
- }} else {{{{
2303
  console.error('Max WebSocket reconnection attempts reached.');
2304
  updateStatusBar('Collaboration failed - Max reconnect attempts reached.', true);
2305
  }}
2306
  }}
2307
- function sendWsMessage(message) {{{{
2308
- if (collabWs && collabWs.readyState === WebSocket.OPEN) {{{{
2309
- try {{{{
2310
  collabWs.send(JSON.stringify(message));
2311
- }} catch (e) {{{{
2312
  console.error("Failed to stringify or send WebSocket message:", e, message);
2313
  }}
2314
- }} else {{{{
2315
  console.warn('WebSocket not connected. Cannot send message:', message);
2316
  }}
2317
  }}
2318
  window.sendWsMessage = sendWsMessage;
2319
- function setupCodeEditorListener() {{{{
2320
  // Find the ACE editor instance managed by the Gradio component
2321
  // This relies on the internal structure of gradio_code_editor
2322
  const editorElement = document.querySelector('#code_editor_component .ace_editor');
2323
- if (typeof ace === 'undefined' || !editorElement) {{{{
2324
- if (editorSetupAttempts < maxEditorSetupAttempts) {{{{
2325
- console.warn(`Ace library or editor element not found yet. Retrying editor setup (${{{{editorSetupAttempts + 1}}/${{{{maxEditorSetupAttempts}})...`);
2326
  editorSetupAttempts++;
2327
  setTimeout(setupCodeEditorListener, 1500 + Math.random() * 500);
2328
- }} else {{{{
2329
  console.error("Ace library or editor element not found after multiple attempts. Code editor collaboration disabled.");
2330
  updateStatusBar("Code editor library or UI element failed to load.", true);
2331
  }}
2332
  return;
2333
  }}
2334
  // Prevent double initialization
2335
- if (editorElement.dataset.collabInitialized === 'true' && aceEditorInstance) {{{{
2336
  // Editor is already set up, just ensure the correct issue ID is tracked
2337
  updateTrackedIssueId();
2338
  return;
2339
  }}
2340
- try {{{{
2341
  // Get the ACE editor instance
2342
  aceEditorInstance = ace.edit(editorElement);
2343
- if (!aceEditorInstance) {{{{ throw new Error("ace.edit(element) returned null or undefined."); }}
2344
  console.log('Ace Editor instance acquired successfully:', aceEditorInstance);
2345
  editorElement.dataset.collabInitialized = 'true'; // Mark as initialized
2346
- if (!editorChangeListenerAttached) {{{{
2347
  console.log("Attaching Ace editor 'change' listener...");
2348
- aceEditorInstance.getSession().on('change', function(delta) {{{{
2349
  // Check for the custom 'ignore' flag added when applying remote deltas
2350
- if (delta.ignore) {{{{ return; }} // Ignore remote changes
2351
- if (currentIssueId !== null) {{{{
2352
  const now = Date.now();
2353
  // Simple debounce to avoid flooding server
2354
- if (now - lastSentDeltaTimestamp > deltaSendDebounceMs) {{{{
2355
- console.debug(`User change detected on issue ${{{{currentIssueId}}. Sending delta:`, delta);
2356
- sendWsMessage({{{{
2357
  type: 'code_update',
2358
  issue_num: currentIssueId,
2359
  delta: JSON.stringify(delta),
@@ -2364,37 +2364,37 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2364
  }}
2365
  }});
2366
  // Add focus/blur listeners for status updates
2367
- aceEditorInstance.on('focus', () => {{{{
2368
- if(currentIssueId !== null) {{{{
2369
- sendWsMessage({{{{ type: 'status_update', clientId: clientId, status: `Editing Issue #${{{{currentIssueId}}`}});
2370
  }}
2371
  }});
2372
- aceEditorInstance.on('blur', () => {{{{
2373
- const status = currentIssueId !== null ? `Viewing Issue #${{{{currentIssueId}}` : 'Idle';
2374
- sendWsMessage({{{{ type: 'status_update', clientId: clientId, status: status}});
2375
  }});
2376
  editorChangeListenerAttached = true;
2377
  console.log('Ace editor listeners attached successfully.');
2378
  }}
2379
  editorSetupAttempts = 0; // Reset attempts on success
2380
  updateTrackedIssueId(); // Ensure correct issue ID is tracked after setup
2381
- }} catch (e) {{{{
2382
  console.error('Failed to initialize Ace editor instance or attach listeners:', e);
2383
  if (editorElement) delete editorElement.dataset.collabInitialized; // Allow retry
2384
  aceEditorInstance = null;
2385
  editorChangeListenerAttached = false;
2386
- if (editorSetupAttempts < maxEditorSetupAttempts) {{{{
2387
- console.warn(`Retrying editor setup after error (${{{{editorSetupAttempts + 1}}/${{{{maxEditorSetupAttempts}})...`);
2388
  editorSetupAttempts++;
2389
  setTimeout(setupCodeEditorListener, 2000 + Math.random() * 500);
2390
- }} else {{{{
2391
  console.error("Max editor setup attempts failed after error. Collaboration disabled.");
2392
  updateStatusBar("Code editor setup failed repeatedly.", true);
2393
  }}
2394
  }}
2395
  }}
2396
  // Function to update the internally tracked issue ID
2397
- function updateTrackedIssueId() {{{{
2398
  // This is the most fragile part, relying on Gradio's internal structure.
2399
  // We need to find the hidden input element that Gradio uses to store the
2400
  // state variable 'selected_issue_id_state'. Its exact location/attributes
@@ -2404,32 +2404,32 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2404
  // This is heuristic and might need adjustment if Gradio changes significantly.
2405
  let newIssueId = null;
2406
  const hiddenInputs = document.querySelectorAll('input[type="hidden"]');
2407
- for (const input of hiddenInputs) {{{{
2408
  const value = input.value;
2409
- if (value && value !== 'null' && /^\d+$/.test(value)) {{{{
2410
  // Found a hidden input with an integer value.
2411
  // Is it *our* hidden state input? Hard to tell definitively.
2412
  // We'll assume the most recently updated one with an integer value
2413
  // *might* be it, or just the first one we find.
2414
  // A more robust way would require Gradio to expose component IDs to JS.
2415
- try {{{{
2416
  // Check if it's likely the state variable by looking at nearby elements or parent structure if possible
2417
  // This is still very hacky. Let's just parse the first plausible one for now.
2418
  newIssueId = parseInt(value, 10);
2419
  // Found a potential ID, stop searching
2420
  break;
2421
- }} catch (e) {{{{
2422
  console.debug("Could not parse hidden input value as int:", value, e);
2423
  }}
2424
  }}
2425
  }}
2426
- if (newIssueId !== currentIssueId) {{{{
2427
- console.log(`Updating tracked issue ID: from ${{{{currentIssueId}} to ${{{{newIssueId}}`);
2428
  currentIssueId = newIssueId;
2429
- const status = currentIssueId !== null ? `Viewing Issue #${{{{currentIssueId}}` : 'Idle';
2430
- sendWsMessage({{{{ type: 'status_update', clientId: clientId, status: status}});
2431
  // If we just got a new issue ID and the editor wasn't set up, try setting it up
2432
- if (currentIssueId !== null && !aceEditorInstance) {{{{
2433
  clearTimeout(window.setupEditorTimeout);
2434
  window.setupEditorTimeout = setTimeout(setupCodeEditorListener, 250);
2435
  }}
@@ -2441,18 +2441,18 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2441
  // for changes that might indicate a new issue has been selected (which
2442
  // updates the hidden state variable and potentially the editor component).
2443
  const observerTargetNode = document.body; // Observe the entire body
2444
- const observerConfig = {{{{ childList: true, subtree: true, attributes: true, attributeFilter: ['value'] }};
2445
- const observerCallback = (mutationsList, observer) => {{{{
2446
  let editorNeedsCheck = false;
2447
  let issueIdCheckNeeded = false;
2448
- for (const mutation of mutationsList) {{{{
2449
  // Check if the code editor component or its contents changed
2450
  if (mutation.target.closest('#code_editor_component') ||
2451
- (mutation.target.id === 'code_editor_component' && mutation.type === 'childList')) {{{{
2452
  editorNeedsCheck = true;
2453
  }}
2454
  // Check if any hidden input's value changed
2455
- if (mutation.type === 'attributes' && mutation.attributeName === 'value' && mutation.target.type === 'hidden') {{{{
2456
  // We can't be sure *which* hidden input changed is the one we care about
2457
  // without more specific info, so we'll just trigger the check
2458
  // of all hidden inputs whenever any hidden input value changes.
@@ -2460,27 +2460,27 @@ def create_ui(manager: IssueManager) -> gr.Blocks:
2460
  }}
2461
  // Also check childList changes on the main Gradio container, as this
2462
  // might indicate components being added/removed or updated.
2463
- if (mutation.type === 'childList' && mutation.target.classList && mutation.target.classList.contains('gradio-container')) {{{{
2464
  issueIdCheckNeeded = true; // Assume issue ID might have changed if main container children change
2465
  editorNeedsCheck = true; // Editor might have been re-rendered
2466
  }}
2467
  }}
2468
- if (editorNeedsCheck) {{{{
2469
  // Debounce editor setup check
2470
  clearTimeout(window.setupEditorTimeout);
2471
  window.setupEditorTimeout = setTimeout(setupCodeEditorListener, 250);
2472
  }}
2473
- if (issueIdCheckNeeded) {{{{
2474
  // Debounce issue ID check
2475
  clearTimeout(window.issueIdCheckTimeout);
2476
  window.issueIdCheckTimeout = setTimeout(updateTrackedIssueId, 100); // Shorter debounce for responsiveness
2477
  }}
2478
  }};
2479
  const observer = new MutationObserver(observerCallback);
2480
- if (observerTargetNode) {{{{
2481
  console.log("Starting MutationObserver to detect Gradio UI changes (incl. hidden state).");
2482
  observer.observe(observerTargetNode, observerConfig);
2483
- }} else {{{{
2484
  console.error("Could not find observer target node (document.body).");
2485
  }}
2486
  // Initial setup attempts
@@ -2662,7 +2662,7 @@ if __name__ == "__main__":
2662
  except KeyboardInterrupt:
2663
  logger.info("Gradio app interrupted. Initiating shutdown...")
2664
  except Exception as e:
2665
- logger.exception(f"An unexpected error occurred: {{{{e}}")
2666
  finally:
2667
  # Ensure graceful shutdown of tasks
2668
  logger.info("Cancelling WebSocket server task...")
@@ -2673,7 +2673,7 @@ if __name__ == "__main__":
2673
  except asyncio.CancelledError:
2674
  pass
2675
  except Exception as e:
2676
- logger.error(f"Error during WebSocket cancellation: {{{{e}}")
2677
 
2678
  # Stop the main asyncio loop
2679
  if main_loop.is_running():
 
48
  executor = ThreadPoolExecutor(max_workers=4)
49
 
50
  # Example HF models (replace with your actual models)
51
+ HF_MODELS = {{
52
  "Mistral-8x7B": "mistralai/Mixtral-8x7B-Instruct-v0.1",
53
  "Llama-2-7B-chat": "huggingface/llama-2-7b-chat-hf",
54
  "CodeLlama-34B": "codellama/CodeLlama-34b-Instruct-hf",
 
77
  def __init__(self, initial_value: Dict[str, str]):
78
  self.files: Dict[str, str] = initial_value.copy()
79
  self.revision = 0 # Basic revision counter, not used for OT logic
80
+ logger.debug(f"OTCodeEditor initialized with files: {{list(self.files.keys())}}")
81
 
82
  def apply_delta(self, delta: Dict[str, Any]):
83
  # VERY basic placeholder: This logs the delta but does NOT perform OT.
84
  # It does NOT handle concurrent edits safely.
85
+ logger.warning(f"Placeholder apply_delta called. Delta: {{str(delta)[:200]}}. "
86
  "WARNING: Full Operational Transformation is NOT implemented. Concurrent edits are UNSAFE.")
87
  # Increment revision regardless for basic tracking
88
  self.revision += 1
 
108
  block_shadow="*shadow_drop_lg",
109
  )
110
  except AttributeError as e:
111
+ logger.warning(f"Could not apply all theme settings (might be Gradio version difference): {{e}}. Using default Soft theme.")
112
  theme = gr.themes.Soft()
113
 
114
 
 
123
  payload_bytes = self.rfile.read(content_length)
124
  payload = json.loads(payload_bytes.decode('utf-8'))
125
  except json.JSONDecodeError:
126
+ logger.error(f"Invalid JSON payload received: {{payload_bytes[:500]}}")
127
  self.send_response(400)
128
  self.send_header("Content-type", "text/plain")
129
  self.end_headers()
130
  self.wfile.write(b"Invalid JSON payload")
131
  return
132
  except Exception as e:
133
+ logger.error(f"Error reading webhook payload: {{e}}")
134
  self.send_response(500)
135
  self.end_headers()
136
  return
137
 
138
  event = self.headers.get('X-GitHub-Event')
139
  delivery_id = self.headers.get('X-GitHub-Delivery')
140
+ logger.info(f"Received GitHub webhook event: {{}event}} (Delivery ID: {{delivery_id}})")
141
 
142
  if event == 'issues' and WebhookHandler.manager_instance and WebhookHandler.main_loop:
143
  action = payload.get('action')
144
+ logger.info(f"Issue action: {{action}}")
145
  # Handle common actions that affect issue state or content
146
  if action in ['opened', 'reopened', 'closed', 'assigned', 'unassigned', 'edited', 'labeled', 'unlabeled', 'milestoned', 'demilestoned']:
147
  if WebhookHandler.main_loop.is_running():
 
150
  WebhookHandler.manager_instance.handle_webhook_event(event, action, payload),
151
  WebhookHandler.main_loop
152
  )
153
+ logger.debug(f"Scheduled webhook processing for action '{{action}}' in main loop.")
154
  else:
155
  logger.error("Asyncio event loop is not running in the target thread for webhook.")
156
  else:
157
+ logger.info(f"Webhook action '{{action}}' received but not actively handled by current logic.")
158
  elif event == 'ping':
159
  logger.info("Received GitHub webhook ping.")
160
  else:
161
+ logger.warning(f"Unhandled event type: {{event}} or manager/loop not initialized.")
162
 
163
  self.send_response(200)
164
  self.send_header("Content-type", "text/plain")
 
2158
  logger.info(f"Generated Client ID for WebSocket: {{client_id}}")
2159
  return f"""
2160
  <script>
2161
+ (function() {{ // IIFE
2162
+ if (window.collabWsInitialized) {{
2163
  console.log('WebSocket script already initialized. Skipping setup.');
2164
+ if (!window.collabWs || window.collabWs.readyState === WebSocket.CLOSED) {{
2165
  console.log('Attempting reconnect from existing script...');
2166
  connectWebSocket();
2167
  }}
 
2176
  const wsPort = {{ws_port};
2177
  const hostname = window.location.hostname;
2178
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
2179
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.gradio.live')) {{
2180
+ wsUrl = `${{protocol}}//${{hostname}}:${{wsPort}}`;
2181
  console.log('Detected local/gradio.live environment, using direct WebSocket URL:', wsUrl);
2182
+ }} else {{
2183
  const wsPath = '/ws';
2184
+ wsUrl = `${{protocol}}//${{window.location.host}}${{wsPath}}`;
2185
  console.log('Detected non-local environment, assuming proxied WebSocket URL:', wsUrl);
2186
  }}
2187
  let collabWs = null;
 
2196
  let editorChangeListenerAttached = false;
2197
  let lastSentDeltaTimestamp = 0;
2198
  const deltaSendDebounceMs = 250;
2199
+ function updateCollabList(collaborators) {{
2200
  const collabListDiv = document.getElementById('collab-list');
2201
  if (!collabListDiv) return;
2202
+ const activeCollaborators = Object.entries(collaborators || {{}})
2203
  .filter(([id, info]) => id !== clientId);
2204
+ if (activeCollaborators.length > 0) {{
2205
  collabListDiv.innerHTML = activeCollaborators
2206
  .map(([id, info]) => `
2207
+ <div class="collab-item" title="ID: ${{id}}">
2208
+ <span style="font-weight: 500;">${{info.name || id.substring(0, 8)}}:</span>
2209
+ <span style="color: #555;">${{info.status || 'Idle'}}</span>
2210
  </div>`)
2211
  .join('');
2212
+ }} else {{
2213
  collabListDiv.innerHTML = '<span style="color: #6b7280;">You are the only active user.</span>';
2214
  }}
2215
  }}
2216
+ function updateStatusBar(message, isError = false) {{
2217
  const statusBar = document.querySelector('#status_output_txt textarea');
2218
+ if (statusBar) {{
2219
  const timestamp = new Date().toLocaleTimeString();
2220
+ statusBar.value = `[${{timestamp}}] ${{message}}`;
2221
  statusBar.style.color = isError ? '#D32F2F' : '#333';
2222
  statusBar.style.fontWeight = isError ? 'bold' : 'normal';
2223
  }}
2224
  }}
2225
+ function connectWebSocket() {{
2226
+ if (collabWs && (collabWs.readyState === WebSocket.OPEN || collabWs.readyState === WebSocket.CONNECTING)) {{
2227
+ console.log(`WebSocket already ${{(collabWs.readyState === WebSocket.OPEN) ? 'open' : 'connecting'}}. State: ${{collabWs.readyState}}`);
2228
  return;
2229
  }}
2230
+ console.log(`Attempting WebSocket connection to ${{wsUrl}} (Attempt ${{reconnectAttempts + 1}}/${{maxReconnectAttempts}})...`);
2231
+ updateStatusBar(`Connecting collaboration service (Attempt ${{reconnectAttempts + 1}})...`);
2232
+ try {{
2233
  collabWs = new WebSocket(wsUrl);
2234
+ }} catch (e) {{
2235
  console.error("WebSocket constructor failed:", e);
2236
  updateStatusBar("Collaboration connection failed (init error).", true);
2237
  // Handle reconnection attempt here as well
 
2239
  return; // Prevent setting up listeners on failed constructor
2240
  }}
2241
  window.collabWs = collabWs;
2242
+ collabWs.onopen = function(event) {{
2243
  console.log('WebSocket connection established.');
2244
  updateStatusBar('Collaboration connected.');
2245
  reconnectAttempts = 0;
2246
+ if(connectInterval) {{ clearInterval(connectInterval); connectInterval = null; }}
2247
+ sendWsMessage({{ type: 'join', clientId: clientId, name: `User_${{clientId.substring(0,4)}}` }});
2248
  setupCodeEditorListener();
2249
  }};
2250
+ collabWs.onmessage = function(event) {{
2251
+ try {{
2252
  const data = JSON.parse(event.data);
2253
+ if (data.type === 'collaboration_status') {{
2254
  updateCollabList(data.collaborators);
2255
+ }} else if (data.type === 'code_update') {{
2256
  const receivedIssueNum = parseInt(data.issue_num, 10);
2257
+ if (aceEditorInstance && receivedIssueNum === currentIssueId && data.senderId !== clientId) {{
2258
+ console.warn(`Applying remote delta for issue ${{receivedIssueNum}} from ${{data.senderId}}. WARNING: NO OT!`);
2259
+ try {{
2260
  const delta = JSON.parse(data.delta);
2261
  // Add ignore flag for local change listener
2262
+ aceEditorInstance.getSession().getDocument().applyDeltas([{{...delta, ignore: true}}]);
2263
+ }} catch (e) {{ console.error('Failed to parse or apply remote delta:', e, data.delta); }}
2264
  }}
2265
+ }} else if (data.type === 'issues_updated') {{
2266
  console.log('Received notification: Issue list updated on server.');
2267
  updateStatusBar('Issue list updated on server. Refreshing the page or re-scanning is recommended.');
2268
  const crawlButton = document.getElementById('crawl_btn');
2269
+ if (crawlButton) {{
2270
  crawlButton.style.backgroundColor = '#fef08a';
2271
+ setTimeout(() => {{ crawlButton.style.backgroundColor = '' }}, 2000);
2272
  }}
2273
+ }} else {{
2274
  console.warn("Received unknown WebSocket message type:", data.type, data);
2275
  }}
2276
+ }} catch (e) {{
2277
  console.error('Failed to parse WebSocket message or update UI:', e, event.data);
2278
  }}
2279
  }};
2280
+ collabWs.onclose = function(event) {{
2281
+ console.warn(`WebSocket connection closed: Code=${{event.code}}, Reason='${{event.reason || 'N/A'}}', Clean=${{event.wasClean}}`);
2282
  handleWsClose(); // Centralize close handling
2283
  }};
2284
+ collabWs.onerror = function(error) {{
2285
  console.error('WebSocket error event:', error);
2286
  updateStatusBar('Collaboration connection error.', true);
2287
  // onclose will likely fire after onerror.
2288
  }};
2289
  }}
2290
+ function handleWsClose() {{
2291
  const collabListDiv = document.getElementById('collab-list');
2292
  if (collabListDiv) collabListDiv.innerHTML = '<span style="color: #D32F2F; font-weight: bold;">Collaboration Disconnected</span>';
2293
  updateStatusBar('Collaboration disconnected.', true);
2294
  collabWs = null; window.collabWs = null;
2295
  aceEditorInstance = null; // Clear editor instance on disconnect
2296
  editorChangeListenerAttached = false; // Allow re-attaching on reconnect
2297
+ if (reconnectAttempts < maxReconnectAttempts) {{
2298
  const delay = Math.pow(2, reconnectAttempts) * 1500 + Math.random() * 1000;
2299
+ console.log(`Attempting to reconnect WebSocket in approx. ${{Math.round(delay / 1000)}} seconds...`);
2300
  setTimeout(connectWebSocket, delay);
2301
  reconnectAttempts++;
2302
+ }} else {{
2303
  console.error('Max WebSocket reconnection attempts reached.');
2304
  updateStatusBar('Collaboration failed - Max reconnect attempts reached.', true);
2305
  }}
2306
  }}
2307
+ function sendWsMessage(message) {{
2308
+ if (collabWs && collabWs.readyState === WebSocket.OPEN) {{
2309
+ try {{
2310
  collabWs.send(JSON.stringify(message));
2311
+ }} catch (e) {{
2312
  console.error("Failed to stringify or send WebSocket message:", e, message);
2313
  }}
2314
+ }} else {{
2315
  console.warn('WebSocket not connected. Cannot send message:', message);
2316
  }}
2317
  }}
2318
  window.sendWsMessage = sendWsMessage;
2319
+ function setupCodeEditorListener() {{
2320
  // Find the ACE editor instance managed by the Gradio component
2321
  // This relies on the internal structure of gradio_code_editor
2322
  const editorElement = document.querySelector('#code_editor_component .ace_editor');
2323
+ if (typeof ace === 'undefined' || !editorElement) {{
2324
+ if (editorSetupAttempts < maxEditorSetupAttempts) {{
2325
+ console.warn(`Ace library or editor element not found yet. Retrying editor setup (${{editorSetupAttempts + 1}}/${{maxEditorSetupAttempts}})...`);
2326
  editorSetupAttempts++;
2327
  setTimeout(setupCodeEditorListener, 1500 + Math.random() * 500);
2328
+ }} else {{
2329
  console.error("Ace library or editor element not found after multiple attempts. Code editor collaboration disabled.");
2330
  updateStatusBar("Code editor library or UI element failed to load.", true);
2331
  }}
2332
  return;
2333
  }}
2334
  // Prevent double initialization
2335
+ if (editorElement.dataset.collabInitialized === 'true' && aceEditorInstance) {{
2336
  // Editor is already set up, just ensure the correct issue ID is tracked
2337
  updateTrackedIssueId();
2338
  return;
2339
  }}
2340
+ try {{
2341
  // Get the ACE editor instance
2342
  aceEditorInstance = ace.edit(editorElement);
2343
+ if (!aceEditorInstance) {{ throw new Error("ace.edit(element) returned null or undefined."); }}
2344
  console.log('Ace Editor instance acquired successfully:', aceEditorInstance);
2345
  editorElement.dataset.collabInitialized = 'true'; // Mark as initialized
2346
+ if (!editorChangeListenerAttached) {{
2347
  console.log("Attaching Ace editor 'change' listener...");
2348
+ aceEditorInstance.getSession().on('change', function(delta) {{
2349
  // Check for the custom 'ignore' flag added when applying remote deltas
2350
+ if (delta.ignore) {{ return; }} // Ignore remote changes
2351
+ if (currentIssueId !== null) {{
2352
  const now = Date.now();
2353
  // Simple debounce to avoid flooding server
2354
+ if (now - lastSentDeltaTimestamp > deltaSendDebounceMs) {{
2355
+ console.debug(`User change detected on issue ${{currentIssueId}}. Sending delta:`, delta);
2356
+ sendWsMessage({{
2357
  type: 'code_update',
2358
  issue_num: currentIssueId,
2359
  delta: JSON.stringify(delta),
 
2364
  }}
2365
  }});
2366
  // Add focus/blur listeners for status updates
2367
+ aceEditorInstance.on('focus', () => {{
2368
+ if(currentIssueId !== null) {{
2369
+ sendWsMessage({{ type: 'status_update', clientId: clientId, status: `Editing Issue #${{currentIssueId}}`}});
2370
  }}
2371
  }});
2372
+ aceEditorInstance.on('blur', () => {{
2373
+ const status = currentIssueId !== null ? `Viewing Issue #${{currentIssueId}}` : 'Idle';
2374
+ sendWsMessage({{ type: 'status_update', clientId: clientId, status: status}});
2375
  }});
2376
  editorChangeListenerAttached = true;
2377
  console.log('Ace editor listeners attached successfully.');
2378
  }}
2379
  editorSetupAttempts = 0; // Reset attempts on success
2380
  updateTrackedIssueId(); // Ensure correct issue ID is tracked after setup
2381
+ }} catch (e) {{
2382
  console.error('Failed to initialize Ace editor instance or attach listeners:', e);
2383
  if (editorElement) delete editorElement.dataset.collabInitialized; // Allow retry
2384
  aceEditorInstance = null;
2385
  editorChangeListenerAttached = false;
2386
+ if (editorSetupAttempts < maxEditorSetupAttempts) {{
2387
+ console.warn(`Retrying editor setup after error (${{editorSetupAttempts + 1}}/${{maxEditorSetupAttempts}})...`);
2388
  editorSetupAttempts++;
2389
  setTimeout(setupCodeEditorListener, 2000 + Math.random() * 500);
2390
+ }} else {{
2391
  console.error("Max editor setup attempts failed after error. Collaboration disabled.");
2392
  updateStatusBar("Code editor setup failed repeatedly.", true);
2393
  }}
2394
  }}
2395
  }}
2396
  // Function to update the internally tracked issue ID
2397
+ function updateTrackedIssueId() {{
2398
  // This is the most fragile part, relying on Gradio's internal structure.
2399
  // We need to find the hidden input element that Gradio uses to store the
2400
  // state variable 'selected_issue_id_state'. Its exact location/attributes
 
2404
  // This is heuristic and might need adjustment if Gradio changes significantly.
2405
  let newIssueId = null;
2406
  const hiddenInputs = document.querySelectorAll('input[type="hidden"]');
2407
+ for (const input of hiddenInputs) {{
2408
  const value = input.value;
2409
+ if (value && value !== 'null' && /^\d+$/.test(value)) {{
2410
  // Found a hidden input with an integer value.
2411
  // Is it *our* hidden state input? Hard to tell definitively.
2412
  // We'll assume the most recently updated one with an integer value
2413
  // *might* be it, or just the first one we find.
2414
  // A more robust way would require Gradio to expose component IDs to JS.
2415
+ try {{
2416
  // Check if it's likely the state variable by looking at nearby elements or parent structure if possible
2417
  // This is still very hacky. Let's just parse the first plausible one for now.
2418
  newIssueId = parseInt(value, 10);
2419
  // Found a potential ID, stop searching
2420
  break;
2421
+ }} catch (e) {{
2422
  console.debug("Could not parse hidden input value as int:", value, e);
2423
  }}
2424
  }}
2425
  }}
2426
+ if (newIssueId !== currentIssueId) {{
2427
+ console.log(`Updating tracked issue ID: from ${{currentIssueId}} to ${{newIssueId}}`);
2428
  currentIssueId = newIssueId;
2429
+ const status = currentIssueId !== null ? `Viewing Issue #${{currentIssueId}}` : 'Idle';
2430
+ sendWsMessage({{ type: 'status_update', clientId: clientId, status: status}});
2431
  // If we just got a new issue ID and the editor wasn't set up, try setting it up
2432
+ if (currentIssueId !== null && !aceEditorInstance) {{
2433
  clearTimeout(window.setupEditorTimeout);
2434
  window.setupEditorTimeout = setTimeout(setupCodeEditorListener, 250);
2435
  }}
 
2441
  // for changes that might indicate a new issue has been selected (which
2442
  // updates the hidden state variable and potentially the editor component).
2443
  const observerTargetNode = document.body; // Observe the entire body
2444
+ const observerConfig = {{ childList: true, subtree: true, attributes: true, attributeFilter: ['value'] }};
2445
+ const observerCallback = (mutationsList, observer) => {{
2446
  let editorNeedsCheck = false;
2447
  let issueIdCheckNeeded = false;
2448
+ for (const mutation of mutationsList) {{
2449
  // Check if the code editor component or its contents changed
2450
  if (mutation.target.closest('#code_editor_component') ||
2451
+ (mutation.target.id === 'code_editor_component' && mutation.type === 'childList')) {{
2452
  editorNeedsCheck = true;
2453
  }}
2454
  // Check if any hidden input's value changed
2455
+ if (mutation.type === 'attributes' && mutation.attributeName === 'value' && mutation.target.type === 'hidden') {{
2456
  // We can't be sure *which* hidden input changed is the one we care about
2457
  // without more specific info, so we'll just trigger the check
2458
  // of all hidden inputs whenever any hidden input value changes.
 
2460
  }}
2461
  // Also check childList changes on the main Gradio container, as this
2462
  // might indicate components being added/removed or updated.
2463
+ if (mutation.type === 'childList' && mutation.target.classList && mutation.target.classList.contains('gradio-container')) {{
2464
  issueIdCheckNeeded = true; // Assume issue ID might have changed if main container children change
2465
  editorNeedsCheck = true; // Editor might have been re-rendered
2466
  }}
2467
  }}
2468
+ if (editorNeedsCheck) {{
2469
  // Debounce editor setup check
2470
  clearTimeout(window.setupEditorTimeout);
2471
  window.setupEditorTimeout = setTimeout(setupCodeEditorListener, 250);
2472
  }}
2473
+ if (issueIdCheckNeeded) {{
2474
  // Debounce issue ID check
2475
  clearTimeout(window.issueIdCheckTimeout);
2476
  window.issueIdCheckTimeout = setTimeout(updateTrackedIssueId, 100); // Shorter debounce for responsiveness
2477
  }}
2478
  }};
2479
  const observer = new MutationObserver(observerCallback);
2480
+ if (observerTargetNode) {{
2481
  console.log("Starting MutationObserver to detect Gradio UI changes (incl. hidden state).");
2482
  observer.observe(observerTargetNode, observerConfig);
2483
+ }} else {{
2484
  console.error("Could not find observer target node (document.body).");
2485
  }}
2486
  // Initial setup attempts
 
2662
  except KeyboardInterrupt:
2663
  logger.info("Gradio app interrupted. Initiating shutdown...")
2664
  except Exception as e:
2665
+ logger.exception(f"An unexpected error occurred: {{e}}")
2666
  finally:
2667
  # Ensure graceful shutdown of tasks
2668
  logger.info("Cancelling WebSocket server task...")
 
2673
  except asyncio.CancelledError:
2674
  pass
2675
  except Exception as e:
2676
+ logger.error(f"Error during WebSocket cancellation: {{e}}")
2677
 
2678
  # Stop the main asyncio loop
2679
  if main_loop.is_running():