diff --git a/src/pyob/autoreviewer.py b/src/pyob/autoreviewer.py index 8731c56..a854132 100644 --- a/src/pyob/autoreviewer.py +++ b/src/pyob/autoreviewer.py @@ -344,16 +344,18 @@ def run_pipeline(self, current_iteration: int): logger.info( f"Found pending {PR_FILE_NAME} and/or {FEATURE_FILE_NAME} from a previous run." ) - proposals_handled = self._handle_pending_proposals( + proposals_outcome = self._handle_pending_proposals( "Hit ENTER to PROCEED, type 'SKIP' to ignore", allow_delete=True, ) - if not proposals_handled: + if proposals_outcome == "SKIPPED": logger.info( "Pending proposals were not applied or deleted. Halting current pipeline iteration to await user action." ) return - changes_made = True + elif proposals_outcome == "APPLIED": + changes_made = True + # If proposals_outcome is "DELETED", changes_made remains False, allowing the scan to proceed. if not changes_made: logger.info("==================================================") diff --git a/src/pyob/cascade_queue_handler.py b/src/pyob/cascade_queue_handler.py index 3cffc5f..08a2fc7 100644 --- a/src/pyob/cascade_queue_handler.py +++ b/src/pyob/cascade_queue_handler.py @@ -6,6 +6,11 @@ class CascadeControllerProtocol(Protocol): def add_to_cascade_queue(self, item: str) -> None: ... def remove_cascade_queue_item(self, item_id: str) -> None: ... def move_cascade_queue_item(self, item_id: str, direction: str) -> None: ... + def process_next_cascade_item( + self, + ) -> ( + str | None + ): ... # Returns the ID of the processed item, or None if queue is empty class CascadeQueueHandler: @@ -27,6 +32,26 @@ def handle_add_to_cascade_queue(self, item: str): except Exception as e: return json.dumps({"error": f"Internal server error: {str(e)}"}).encode() + def handle_process_next_cascade_item(self): + try: + processed_item_id = self.controller.process_next_cascade_item() + if processed_item_id: + return json.dumps( + {"message": f"Item '{processed_item_id}' processed successfully"} + ).encode() + else: + return json.dumps( + {"message": "Cascade queue is empty, no item to process"} + ).encode() + except AttributeError: + return json.dumps( + { + "error": "Controller method 'process_next_cascade_item' not found. Ensure entrance.py is updated." + } + ).encode() + except Exception as e: + return json.dumps({"error": f"Internal server error: {str(e)}"}).encode() + def handle_remove_from_cascade_queue(self, item_id: str): try: self.controller.remove_cascade_queue_item(item_id) diff --git a/src/pyob/dashboard_html.py b/src/pyob/dashboard_html.py index d8a85d8..ade7d82 100644 --- a/src/pyob/dashboard_html.py +++ b/src/pyob/dashboard_html.py @@ -61,6 +61,7 @@ +

@@ -122,10 +123,22 @@ document.getElementById('iteration').innerText = data.iteration || "0"; document.getElementById('ledger').innerText = (data.ledger_stats?.definitions || 0) + " SYM"; document.getElementById('queue-count').innerText = data.cascade_queue?.length || "0"; @@ -292,10 +305,10 @@ async function reviewPatch(patchId, action) { try { if (action === 'approved') { - await fetch('/api/approve_patch', { + await fetch('/api/review_patch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ patch_id: patchId }) + body: JSON.stringify({ patch_id: patchId, action: 'approved' }) }); } else { await fetch('/api/review_patch', { diff --git a/src/pyob/dashboard_utils.js b/src/pyob/dashboard_utils.js new file mode 100644 index 0000000..77abe0d --- /dev/null +++ b/src/pyob/dashboard_utils.js @@ -0,0 +1,290 @@ +async function updateStats() { + try { + const response = await fetch('/api/status'); + const data = await response.json(); + document.getElementById('iteration').innerText = data.iteration || "0"; + document.getElementById('ledger').innerText = (data.ledger_stats?.definitions || 0) + " SYM"; + document.getElementById('queue-count').innerText = data.cascade_queue?.length || "0"; + const pill = document.getElementById('status-pill'); + const isEvolving = data.cascade_queue?.length > 0 || data.patches_count > 0; + pill.innerText = isEvolving ? "EVOLVING" : "STABLE"; + pill.className = isEvolving ? "status-pill evolving" : "status-pill"; + document.getElementById('memory').value = data.memory || "Brain empty."; + document.getElementById('history').innerText = data.history || "No logs."; + // Handle structured analysis data for interactive issues + const analysisContainer = document.getElementById('analysis'); + analysisContainer.innerHTML = ''; // Clear existing content + const filterText = document.getElementById('analysisFilter').value.toLowerCase(); + + if (Array.isArray(data.analysis) && data.analysis.length > 0) { + const filteredAnalysis = data.analysis.filter(issue => + issue.description.toLowerCase().includes(filterText) + ); + filteredAnalysis.forEach(issue => { + const issueElement = document.createElement('div'); + issueElement.style.marginBottom = '8px'; + issueElement.style.padding = '8px'; + issueElement.style.background = '#00000066'; + issueElement.style.borderRadius = '4px'; + issueElement.style.display = 'flex'; + issueElement.style.alignItems = 'center'; + issueElement.style.justifyContent = 'space-between'; + issueElement.style.fontFamily = 'JetBrains Mono'; + issueElement.style.fontSize = '0.8em'; + issueElement.style.color = '#ced4e0'; + + const statusColor = issue.status === 'acknowledged' ? 'var(--dim)' : 'var(--err)'; + const statusText = issue.status === 'acknowledged' ? 'ACKNOWLEDGED' : 'PENDING'; + + issueElement.innerHTML = ` + ${issue.description} + ${statusText} + + `; + analysisContainer.appendChild(issueElement); + }); + } else { + analysisContainer.innerText = "No issues found."; // Always display this if no issues or not an array + } + + // Update chart after fetching data + updateChart(data); + + const queueDiv = document.getElementById('queue'); + queueDiv.innerHTML = ''; + if (data.cascade_queue && data.cascade_queue.length > 0) { + data.cascade_queue.forEach((item, index) => { + const itemElement = document.createElement('div'); + itemElement.className = 'queue-item'; + + const textSpan = document.createElement('span'); + textSpan.style.flexGrow = '1'; + textSpan.innerText = item; // Use innerText for safety + + const buttonDiv = document.createElement('div'); + buttonDiv.style.display = 'flex'; + buttonDiv.style.gap = '5px'; + + const moveUpBtn = document.createElement('button'); + moveUpBtn.className = 'move-btn'; + moveUpBtn.innerHTML = '▲'; + moveUpBtn.addEventListener('click', () => moveQueueItem(item, 'up')); + + const moveDownBtn = document.createElement('button'); + moveDownBtn.className = 'move-btn'; + moveDownBtn.innerHTML = '▼'; + moveDownBtn.addEventListener('click', () => moveQueueItem(item, 'down')); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'remove-btn'; + removeBtn.innerHTML = 'X'; + removeBtn.addEventListener('click', () => removeQueueItem(item)); + + buttonDiv.appendChild(moveUpBtn); + buttonDiv.appendChild(moveDownBtn); + buttonDiv.appendChild(removeBtn); + + itemElement.appendChild(textSpan); + itemElement.appendChild(buttonDiv); + queueDiv.appendChild(itemElement); + }); + } else { + queueDiv.innerText = "EMPTY"; + } + + await updatePendingPatches(); + } catch (e) { document.getElementById('status-pill').innerText = "OFFLINE"; console.error("Error updating stats:", e); } +} + +async function acknowledgeIssue(issueId) { + try { + const response = await fetch(`/api/acknowledge-issue/${issueId}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status: 'acknowledged' }) + }); + if (response.ok) { + await updateStats(); // Refresh dashboard to show updated status + } else { + console.error(`Failed to acknowledge issue ${issueId}:`, await response.text()); + alert('Error acknowledging issue.'); + } + } catch (e) { + console.error(`Failed to acknowledge issue ${issueId}:`, e); + alert('Network error acknowledging issue.'); + } +} + +async function updatePendingPatches() { + try { + const response = await fetch('/api/pending_patches'); + const data = await response.json(); + const patchesDiv = document.getElementById('pending-patches'); + patchesDiv.innerHTML = ''; + + if (data.patches && data.patches.length > 0) { + data.patches.forEach(patch => { + const patchElement = document.createElement('div'); + patchElement.style.marginBottom = '10px'; + patchElement.innerHTML = ` + Patch ID: ${patch.id}
+ File: ${patch.file || 'N/A'}
+ Description: ${patch.description || 'No description'}
+ + + + `; + patchesDiv.appendChild(patchElement); + }); + } else { + patchesDiv.innerText = "No pending patches."; + } + } catch (e) { + console.error("Failed to fetch pending patches:", e); + document.getElementById('pending-patches').innerText = "Error loading patches."; + } +} + +async function reviewPatch(patchId, action) { + try { + if (action === 'approved') { + await fetch('/api/review_patch', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ patch_id: patchId, action: 'approved' }) + }); + } else { + await fetch('/api/review_patch', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ patch_id: patchId, action: action }) + }); + } + await updateStats(); + } catch (e) { + console.error(`Failed to ${action} patch ${patchId}:`, e); + } +} + +async function saveMemory() { + const memoryContent = document.getElementById('memory').value; + try { + const response = await fetch('/api/update_memory', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: memoryContent }) + }); + if (response.ok) { + alert('Logic Memory saved successfully!'); + await updateStats(); + } + } catch (e) { + console.error("Failed to save Logic Memory:", e); + } +} + +async function addCascadeItem() { + const item = document.getElementById('cascadeItem').value; + if (!item) return; + try { + const response = await fetch('/api/cascade_queue/add', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ item: item }) + }); + if (response.ok) { + document.getElementById('cascadeItem').value = ''; + await updateStats(); + } + } catch (e) { + console.error("Failed to add item to cascade queue:", e); + } +} + +async function moveQueueItem(itemId, direction) { + try { + await fetch('/api/cascade_queue/move', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ item_id: itemId, direction: direction }) + }); + await updateStats(); + } catch (e) { + console.error(`Failed to move item ${itemId} ${direction}:`, e); + } +} + +async function removeQueueItem(itemId) { + if (!confirm(`Are you sure you want to remove "${itemId.replace(/"/g, '\\"')}" from the queue?`)) return; + try { + await fetch('/api/cascade_queue/remove', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ item_id: itemId }) + }); + await updateStats(); + } catch (e) { + console.error(`Failed to remove item ${itemId}:`, e); + } +} + +async function viewPatchDiff(patchId) { + try { + const response = await fetch(`/api/patch_diff/${patchId}`); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + const data = await response.json(); + document.getElementById('diffModalPatchId').innerText = `(ID: ${patchId})`; + document.getElementById('diffContent').innerText = data.diff || "No diff available."; + openDiffModal(); + } catch (e) { + console.error(`Failed to fetch diff for patch ${patchId}:`, e); + alert(`Error viewing diff: ${e.message}`); + } +} + +function openDiffModal() { + document.getElementById('diffModal').style.display = 'block'; +} + +function closeDiffModal() { + document.getElementById('diffModal').style.display = 'none'; + document.getElementById('diffContent').innerText = ''; // Clear content on close + document.getElementById('diffModalPatchId').innerText = ''; +} + +function updateChart(data) { + const ctx = document.getElementById('statsChart').getContext('2d'); + const iterationData = data.iterationHistory ? data.iterationHistory.map((val, index) => { return { x: index, y: val }; }) : []; + + if (window.statsChart) { + window.statsChart.data.labels = iterationData.map(d => d.x); + window.statsChart.data.datasets[0].data = iterationData.map(d => d.y); + window.statsChart.update(); + } else { + window.statsChart = new Chart(ctx, { + type: 'line', + data: { + labels: iterationData.map(d => d.x), + datasets: [{ + label: 'Iterations Over Time', + data: iterationData.map(d => d.y), + borderColor: 'rgb(75, 192, 192)', + tension: 0.1 + }] + }, + options: { + scales: { + y: { + beginAtZero: true + } + } + } + }); + } +} \ No newline at end of file diff --git a/src/pyob/evolution_mixins.py b/src/pyob/evolution_mixins.py index ba0d0d6..efa089d 100644 --- a/src/pyob/evolution_mixins.py +++ b/src/pyob/evolution_mixins.py @@ -233,7 +233,7 @@ def build_initial_analysis(self): lambda t: len(t) > 5, context="Project Genesis", ).strip() - content = f"# Project Analysis\n\n**Project Summary:**\n{p_summary}\n\n-----n\n## File Directory\n\n" + content = f"# Project Analysis\n\n**Project Summary:**\n{p_summary}\n\n-----\n## File Directory\n\n" file_structures = {} for f_path in all_files: diff --git a/src/pyob/stats_updater.py b/src/pyob/stats_updater.py index e326836..a9bc2b7 100644 --- a/src/pyob/stats_updater.py +++ b/src/pyob/stats_updater.py @@ -27,7 +27,7 @@ async def review_patch(self, patch_id, action): await fetch_api( "/api/review_patch", method="POST", - data=json.dumps({"patch_id": patch_id, "action": action}), + data={"patch_id": patch_id, "action": action}, ) except Exception as e: print(f"Failed to {action} patch {patch_id}: {e}") @@ -69,3 +69,15 @@ async def remove_queue_item(self, item_id): ) except Exception as e: print(f"Failed to remove item {item_id}: {e}") + + async def fetch_cascade_queue(self): + """ + Fetches the current list of items in the cascade queue. + """ + try: + response = await fetch_api("/api/cascade_queue") + data = await response.json() + return data + except Exception as e: + print(f"Failed to fetch cascade queue: {e}") + return None