Spaces:
Runtime error
Runtime error
Update app.py
Browse files
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: {{
|
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: {{
|
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): {{
|
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: {{
|
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: {{
|
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: {{
|
141 |
|
142 |
if event == 'issues' and WebhookHandler.manager_instance and WebhookHandler.main_loop:
|
143 |
action = payload.get('action')
|
144 |
-
logger.info(f"Issue 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 '{{
|
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 '{{
|
158 |
elif event == 'ping':
|
159 |
logger.info("Received GitHub webhook ping.")
|
160 |
else:
|
161 |
-
logger.warning(f"Unhandled event type: {{
|
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() {{
|
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 = `${{
|
2181 |
console.log('Detected local/gradio.live environment, using direct WebSocket URL:', wsUrl);
|
2182 |
-
}} else {{
|
2183 |
const wsPath = '/ws';
|
2184 |
-
wsUrl = `${{
|
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: ${{
|
2208 |
-
<span style="font-weight: 500;">${{
|
2209 |
-
<span style="color: #555;">${{
|
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 = `[${{
|
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 ${{
|
2228 |
return;
|
2229 |
}}
|
2230 |
-
console.log(`Attempting WebSocket connection to ${{
|
2231 |
-
updateStatusBar(`Connecting collaboration service (Attempt ${{
|
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) {{
|
2247 |
-
sendWsMessage({{
|
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 ${{
|
2259 |
-
try {{
|
2260 |
const delta = JSON.parse(data.delta);
|
2261 |
// Add ignore flag for local change listener
|
2262 |
-
aceEditorInstance.getSession().getDocument().applyDeltas([{{
|
2263 |
-
}} catch (e) {{
|
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(() => {{
|
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=${{
|
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. ${{
|
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 (${{
|
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) {{
|
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) {{
|
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 ${{
|
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({{
|
2370 |
}}
|
2371 |
}});
|
2372 |
-
aceEditorInstance.on('blur', () => {{
|
2373 |
-
const status = currentIssueId !== null ? `Viewing Issue #${{
|
2374 |
-
sendWsMessage({{
|
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 (${{
|
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 ${{
|
2428 |
currentIssueId = newIssueId;
|
2429 |
-
const status = currentIssueId !== null ? `Viewing Issue #${{
|
2430 |
-
sendWsMessage({{
|
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 = {{
|
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: {{
|
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: {{
|
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():
|