Spaces:
Runtime error
Runtime error
from flask import Flask, render_template_string, request, jsonify | |
import requests | |
from collections import Counter | |
import re | |
app = Flask(__name__) | |
# HTML template (simplified version of your provided HTML) | |
HTML_TEMPLATE = ''' | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Issue Resolver Pro</title> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<style> | |
:root { | |
--primary-gradient: linear-gradient(90deg, #8B5CF6 0%, #EC4899 100%); | |
--primary-color: #8B5CF6; | |
--secondary-color: #10B981; | |
--background-color: #f9fafb; | |
--card-background: #ffffff; | |
--border-color: #e5e7eb; | |
--text-primary: #1f2937; | |
--text-secondary: #4b5563; | |
--text-tertiary: #6b7280; | |
--error-color: #ef4444; | |
--warning-color: #f59e0b; | |
--success-color: #10b981; | |
--info-color: #3b82f6; | |
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
--border-radius: 8px; | |
--transition: all 0.3s ease; | |
} | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: 'Inter', sans-serif; | |
background-color: var(--background-color); | |
color: var(--text-primary); | |
line-height: 1.6; | |
padding: 20px; | |
} | |
.container { | |
max-width: 1600px; | |
margin: 0 auto; | |
} | |
header { | |
text-align: center; | |
margin-bottom: 30px; | |
} | |
h1 { | |
font-size: 2.5rem; | |
font-weight: 800; | |
background: var(--primary-gradient); | |
-webkit-background-clip: text; | |
background-clip: text; | |
color: transparent; | |
margin-bottom: 10px; | |
} | |
.subtitle { | |
font-size: 1.1rem; | |
color: var(--text-secondary); | |
} | |
.card { | |
background: var(--card-background); | |
border-radius: var(--border-radius); | |
box-shadow: var(--shadow); | |
padding: 20px; | |
margin-bottom: 20px; | |
border: 1px solid var(--border-color); | |
} | |
.card-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 15px; | |
padding-bottom: 10px; | |
border-bottom: 1px solid var(--border-color); | |
} | |
.card-title { | |
font-size: 1.2rem; | |
font-weight: 600; | |
color: var(--text-primary); | |
} | |
.config-panel { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 20px; | |
margin-bottom: 20px; | |
} | |
.input-group { | |
flex: 1; | |
min-width: 300px; | |
} | |
.input-group label { | |
display: block; | |
margin-bottom: 8px; | |
font-weight: 500; | |
color: var(--text-secondary); | |
} | |
.input-group input, .input-group select { | |
width: 100%; | |
padding: 12px 15px; | |
border: 1px solid var(--border-color); | |
border-radius: var(--border-radius); | |
font-size: 1rem; | |
transition: var(--transition); | |
} | |
.input-group input:focus, .input-group select:focus { | |
outline: none; | |
border-color: var(--primary-color); | |
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.2); | |
} | |
.button-group { | |
display: flex; | |
gap: 10px; | |
margin-top: 15px; | |
} | |
button { | |
padding: 12px 20px; | |
border-radius: var(--border-radius); | |
border: none; | |
font-weight: 600; | |
cursor: pointer; | |
transition: var(--transition); | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
} | |
.btn-primary { | |
background: var(--primary-gradient); | |
color: white; | |
} | |
.btn-primary:hover { | |
filter: brightness(1.1); | |
} | |
.btn-secondary { | |
background-color: #f3f4f6; | |
color: var(--text-primary); | |
border: 1px solid var(--border-color); | |
} | |
.btn-secondary:hover { | |
background-color: #e5e7eb; | |
} | |
.tabs { | |
display: flex; | |
border-bottom: 1px solid var(--border-color); | |
margin-bottom: 20px; | |
} | |
.tab { | |
padding: 12px 20px; | |
cursor: pointer; | |
font-weight: 500; | |
color: var(--text-tertiary); | |
border-bottom: 3px solid transparent; | |
transition: var(--transition); | |
} | |
.tab.active { | |
color: var(--primary-color); | |
border-bottom: 3px solid var(--primary-color); | |
} | |
.tab-content { | |
display: none; | |
} | |
.tab-content.active { | |
display: block; | |
} | |
.issue-board { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 20px; | |
} | |
.issue-list-container { | |
flex: 3; | |
min-width: 300px; | |
} | |
.analytics-container { | |
flex: 2; | |
min-width: 300px; | |
} | |
.collaborators-container { | |
margin-top: 20px; | |
} | |
table { | |
width: 100%; | |
border-collapse: collapse; | |
background-color: var(--card-background); | |
border-radius: var(--border-radius); | |
overflow: hidden; | |
box-shadow: var(--shadow); | |
} | |
th, td { | |
padding: 12px 15px; | |
text-align: left; | |
border-bottom: 1px solid var(--border-color); | |
} | |
th { | |
background-color: #f3f4f6; | |
font-weight: 600; | |
color: var(--text-secondary); | |
} | |
tr:last-child td { | |
border-bottom: none; | |
} | |
tr:hover { | |
background-color: #ede9fe; | |
cursor: pointer; | |
} | |
.severity { | |
padding: 4px 8px; | |
border-radius: 4px; | |
font-size: 0.85rem; | |
font-weight: 500; | |
} | |
.severity.critical { | |
background-color: #fee2e2; | |
color: #b91c1c; | |
} | |
.severity.high { | |
background-color: #ffedd5; | |
color: #c2410c; | |
} | |
.severity.medium { | |
background-color: #fef3c7; | |
color: #92400e; | |
} | |
.severity.low { | |
background-color: #dcfce7; | |
color: #065f46; | |
} | |
.cluster { | |
background-color: #dbeafe; | |
color: #1e40af; | |
padding: 4px 8px; | |
border-radius: 4px; | |
font-size: 0.85rem; | |
} | |
.resolution-studio { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 20px; | |
} | |
.issue-preview { | |
flex: 1; | |
min-width: 300px; | |
} | |
.code-editor-container { | |
flex: 2; | |
min-width: 500px; | |
} | |
.editor-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 10px; | |
} | |
.ai-output { | |
background-color: #f0f9ff; | |
border: 1px solid #bae6fd; | |
border-radius: var(--border-radius); | |
padding: 15px; | |
margin-top: 20px; | |
max-height: 300px; | |
overflow-y: auto; | |
} | |
.ai-output h3 { | |
margin-bottom: 10px; | |
color: var(--info-color); | |
} | |
.plot-container { | |
width: 100%; | |
height: 400px; | |
margin-top: 15px; | |
} | |
.collaborators-list { | |
max-height: 150px; | |
overflow-y: auto; | |
} | |
.collaborator-item { | |
padding: 8px 0; | |
border-bottom: 1px solid var(--border-color); | |
} | |
.collaborator-item:last-child { | |
border-bottom: none; | |
} | |
.status-bar { | |
background-color: #f0fdf4; | |
border: 1px solid #bbf7d0; | |
border-radius: var(--border-radius); | |
padding: 10px 15px; | |
font-family: monospace; | |
font-size: 0.9rem; | |
color: #166534; | |
margin-top: 15px; | |
} | |
.status-bar.error { | |
background-color: #fef2f2; | |
border-color: #fecaca; | |
color: #991b1b; | |
} | |
.status-indicator { | |
display: inline-block; | |
padding: 3px 6px; | |
border-radius: 4px; | |
font-size: 0.8rem; | |
font-weight: 600; | |
margin-left: 10px; | |
} | |
.status-indicator.stale { | |
background-color: #fee2e2; | |
color: #b91c1c; | |
} | |
.status-indicator.critical { | |
background-color: #fee2e2; | |
color: #b91c1c; | |
} | |
.status-indicator.high { | |
background-color: #ffedd5; | |
color: #c2410c; | |
} | |
.accordion { | |
background-color: #f0f9ff; | |
border: 1px solid #bae6fd; | |
border-radius: var(--border-radius); | |
margin-top: 20px; | |
} | |
.accordion-header { | |
padding: 15px; | |
background-color: #e0f2fe; | |
border-radius: var(--border-radius) var(--border-radius) 0 0; | |
cursor: pointer; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
} | |
.accordion-content { | |
padding: 15px; | |
display: none; | |
} | |
.accordion-content.active { | |
display: block; | |
} | |
.ai-tools { | |
display: flex; | |
flex-direction: column; | |
gap: 10px; | |
} | |
.warning-box { | |
background-color: #fffbeb; | |
border: 1px solid #fef3c7; | |
border-radius: var(--border-radius); | |
padding: 15px; | |
margin-bottom: 20px; | |
} | |
.warning-box h4 { | |
color: var(--warning-color); | |
margin-bottom: 10px; | |
} | |
.hidden { | |
display: none; | |
} | |
@media (max-width: 768px) { | |
.config-panel { | |
flex-direction: column; | |
} | |
.issue-board, .resolution-studio { | |
flex-direction: column; | |
} | |
.input-group { | |
min-width: 100%; | |
} | |
table { | |
font-size: 0.9rem; | |
} | |
th, td { | |
padding: 8px 10px; | |
} | |
} | |
@media (max-width: 480px) { | |
body { | |
padding: 10px; | |
} | |
h1 { | |
font-size: 2rem; | |
} | |
.tabs { | |
flex-wrap: wrap; | |
} | |
.tab { | |
font-size: 0.9rem; | |
padding: 10px 15px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<header> | |
<h1><i class="fas fa-rocket"></i> AI Issue Resolver Pro</h1> | |
<p class="subtitle">Collaborative Issue Resolution Powered by AI</p> | |
</header> | |
<div class="card config-panel"> | |
<div class="input-group"> | |
<label for="repo-url">GitHub Repository URL</label> | |
<input type="text" id="repo-url" placeholder="https://github.com/owner/repo"> | |
</div> | |
<div class="input-group"> | |
<label for="github-token">GitHub Token (Optional)</label> | |
<input type="password" id="github-token" placeholder="ghp_..."> | |
</div> | |
<div class="input-group"> | |
<label for="hf-token">Hugging Face Token</label> | |
<input type="password" id="hf-token" placeholder="hf_..."> | |
</div> | |
<div class="input-group"> | |
<label for="model-select">Select AI Model</label> | |
<select id="model-select"> | |
<option value="Mistral-8x7B">Mistral-8x7B</option> | |
<option value="Llama-2-7B-chat">Llama-2-7B-chat</option> | |
<option value="CodeLlama-34B">CodeLlama-34B</option> | |
<option value="Gemma-7B-it">Gemma-7B-it</option> | |
</select> | |
</div> | |
<div class="button-group"> | |
<button class="btn-primary" id="crawl-btn"> | |
<i class="fas fa-satellite"></i> Scan Repository Issues | |
</button> | |
</div> | |
</div> | |
<div class="status-bar" id="status-bar"> | |
Status updates appear here... Idle tasks may run in background. | |
</div> | |
<div class="tabs"> | |
<div class="tab active" data-tab="board">📋 Issue Board</div> | |
<div class="tab" data-tab="studio">💻 Resolution Studio</div> | |
<div class="tab" data-tab="analytics">📈 Analytics</div> | |
</div> | |
<div class="tab-content active" id="board-content"> | |
<div class="issue-board"> | |
<div class="issue-list-container"> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">Open Issues</div> | |
</div> | |
<table id="issue-table"> | |
<thead> | |
<tr> | |
<th>ID</th> | |
<th>Title</th> | |
<th>Severity</th> | |
<th>Cluster</th> | |
</tr> | |
</thead> | |
<tbody> | |
<!-- Issues will be populated here --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<div class="analytics-container"> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">Issue Severity Distribution</div> | |
</div> | |
<div class="plot-container" id="severity-plot"></div> | |
</div> | |
<div class="card collaborators-container"> | |
<div class="card-header"> | |
<div class="card-title">👥 Active Collaborators</div> | |
</div> | |
<div class="collaborators-list"> | |
<div class="collaborator-item"> | |
<strong>User_abc1:</strong> <span>Viewing Issue #123</span> | |
</div> | |
<div class="collaborator-item"> | |
<strong>User_def2:</strong> <span>Editing Issue #124</span> | |
</div> | |
<div class="collaborator-item"> | |
<strong>User_ghi3:</strong> <span>Idle</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="tab-content" id="studio-content"> | |
<div class="resolution-studio"> | |
<div class="issue-preview"> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">Selected Issue Details</div> | |
</div> | |
<div id="issue-preview-content"> | |
<p>Select an issue from the 'Issue Board' tab.</p> | |
</div> | |
</div> | |
<div class="accordion"> | |
<div class="accordion-header" id="ai-tools-header"> | |
<div class="card-title">🛠️ AI Assistance Tools</div> | |
<i class="fas fa-chevron-down"></i> | |
</div> | |
<div class="accordion-content active" id="ai-tools-content"> | |
<div class="ai-tools"> | |
<button class="btn-secondary" id="suggest-btn"> | |
<i class="fas fa-brain"></i> Suggest Resolution Steps | |
</button> | |
<button class="btn-secondary" id="patch-btn"> | |
<i class="fas fa-file-code"></i> Generate Code Patch | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">AI Output</div> | |
</div> | |
<div class="ai-output"> | |
<p>AI suggestions and patches will appear here...</p> | |
</div> | |
</div> | |
</div> | |
<div class="code-editor-container"> | |
<div class="card"> | |
<div class="editor-header"> | |
<div class="card-title">Collaborative Code Editor (Context-Aware)</div> | |
</div> | |
<div class="warning-box"> | |
<h4><i class="fas fa-exclamation-triangle"></i> Warning</h4> | |
<p>Real-time collaborative editing is experimental and may lose data with simultaneous edits. Use with caution and save work frequently!</p> | |
</div> | |
<div id="code-editor-placeholder"> | |
<p># Select an issue to load relevant code context.</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="tab-content" id="analytics-content"> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">Repository Analytics</div> | |
</div> | |
<div class="issue-board"> | |
<div class="issue-list-container"> | |
<h3>Issue Severity Distribution</h3> | |
<div class="plot-container" id="analytics-severity-plot"></div> | |
</div> | |
<div class="analytics-container"> | |
<h3>Issue Cluster Analysis (Top Clusters)</h3> | |
<table> | |
<thead> | |
<tr> | |
<th>Cluster ID</th> | |
<th>Issue Count</th> | |
<th>Top Keywords</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>1</td> | |
<td>15</td> | |
<td>authentication, login, session</td> | |
</tr> | |
<tr> | |
<td>2</td> | |
<td>8</td> | |
<td>performance, slow, response</td> | |
</tr> | |
<tr> | |
<td>3</td> | |
<td>5</td> | |
<td>documentation, api, update</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<p style="margin-top: 20px; color: var(--text-tertiary);">Analytics update after scanning the repository. More detailed analytics could be added.</p> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Tab switching functionality | |
const tabs = document.querySelectorAll('.tab'); | |
const tabContents = document.querySelectorAll('.tab-content'); | |
tabs.forEach(tab => { | |
tab.addEventListener('click', () => { | |
// Remove active class from all tabs and contents | |
tabs.forEach(t => t.classList.remove('active')); | |
tabContents.forEach(tc => tc.classList.remove('active')); | |
// Add active class to clicked tab | |
tab.classList.add('active'); | |
// Show corresponding content | |
const tabId = tab.getAttribute('data-tab'); | |
document.getElementById(`${tabId}-content`).classList.add('active'); | |
}); | |
}); | |
// Accordion functionality | |
const accordionHeader = document.getElementById('ai-tools-header'); | |
const accordionContent = document.getElementById('ai-tools-content'); | |
const accordionIcon = accordionHeader.querySelector('i'); | |
accordionHeader.addEventListener('click', () => { | |
accordionContent.classList.toggle('active'); | |
if (accordionContent.classList.contains('active')) { | |
accordionIcon.className = 'fas fa-chevron-up'; | |
} else { | |
accordionIcon.className = 'fas fa-chevron-down'; | |
} | |
}); | |
// Crawl button functionality | |
document.getElementById('crawl-btn').addEventListener('click', async () => { | |
const repoUrl = document.getElementById('repo-url').value; | |
const token = document.getElementById('github-token').value; | |
const statusBar = document.getElementById('status-bar'); | |
if (!repoUrl) { | |
statusBar.textContent = 'Please enter a GitHub repository URL'; | |
statusBar.classList.add('error'); | |
return; | |
} | |
statusBar.textContent = 'Scanning repository issues...'; | |
statusBar.classList.remove('error'); | |
try { | |
const response = await fetch('/scan_issues', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
repo_url: repoUrl, | |
github_token: token | |
}) | |
}); | |
const data = await response.json(); | |
if (response.ok) { | |
// Update issue table | |
const tbody = document.querySelector('#issue-table tbody'); | |
tbody.innerHTML = ''; | |
data.issues.forEach(issue => { | |
const row = document.createElement('tr'); | |
row.innerHTML = ` | |
<td>${issue.number}</td> | |
<td>${issue.title}</td> | |
<td><span class="severity ${issue.severity.toLowerCase()}">${issue.severity}</span></td> | |
<td><span class="cluster">Group ${issue.cluster}</span></td> | |
`; | |
tbody.appendChild(row); | |
}); | |
// Update severity plot | |
const severityData = data.severity_distribution; | |
const plotData = [{ | |
x: Object.keys(severityData), | |
y: Object.values(severityData), | |
type: 'bar', | |
marker: { | |
color: Object.keys(severityData).map(severity => { | |
switch(severity.toLowerCase()) { | |
case 'critical': return '#DC2626'; | |
case 'high': return '#F97316'; | |
case 'medium': return '#FACC15'; | |
case 'low': return '#84CC16'; | |
default: return '#6B7280'; | |
} | |
}) | |
} | |
}]; | |
const layout = { | |
title: 'Issue Severity Distribution', | |
xaxis: {title: ''}, | |
yaxis: {title: 'Number of Issues'}, | |
plot_bgcolor: 'rgba(0,0,0,0)', | |
paper_bgcolor: 'rgba(0,0,0,0)', | |
showlegend: false | |
}; | |
Plotly.newPlot('severity-plot', plotData, layout); | |
Plotly.newPlot('analytics-severity-plot', plotData, layout); | |
// Update status bar | |
statusBar.textContent = `[${new Date().toLocaleTimeString()}] Found ${data.total_issues} open issues. Clustered into ${data.clusters} groups. Repo ready. Background analysis started.`; | |
statusBar.classList.remove('error'); | |
} else { | |
statusBar.textContent = data.error; | |
statusBar.classList.add('error'); | |
} | |
} catch (error) { | |
statusBar.textContent = 'Error scanning repository: ' + error.message; | |
statusBar.classList.add('error'); | |
} | |
}); | |
// Issue selection functionality | |
const issueTable = document.getElementById('issue-table'); | |
const issuePreview = document.getElementById('issue-preview-content'); | |
issueTable.addEventListener('click', (e) => { | |
const row = e.target.closest('tr'); | |
if (row && row.querySelector('td')) { | |
// Highlight selected row | |
issueTable.querySelectorAll('tr').forEach(r => r.classList.remove('selected')); | |
row.classList.add('selected'); | |
// Get issue data from row | |
const cells = row.querySelectorAll('td'); | |
const issueId = cells[0].textContent; | |
const issueTitle = cells[1].textContent; | |
// Update preview content | |
issuePreview.innerHTML = ` | |
<h3>#${issueId} - ${issueTitle}</h3> | |
<div style="margin: 15px 0; display: flex; gap: 10px;"> | |
<span class="status-indicator stale">[Stale]</span> | |
<span class="status-indicator critical">[Critical]</span> | |
</div> | |
<hr> | |
<div style="display: flex; flex-wrap: wrap; gap: 15px; margin: 15px 0;"> | |
<div><strong>State:</strong> <span style="background-color: #dcfce7; color: #166534; padding: 2px 6px; border-radius: 4px;">open</span></div> | |
<div><strong>Assignee:</strong> None</div> | |
<div><strong>Severity:</strong> Critical</div> | |
</div> | |
<div style="margin: 15px 0;"> | |
<strong>Labels:</strong> | |
<span style="background-color: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 2px 6px; border-radius: 4px; font-size: 0.85em; display: inline-block; margin-right: 4px;">bug</span> | |
<span style="background-color: #f3f4f6; color: #374151; border: 1px solid #d1d5db; padding: 2px 6px; border-radius: 4px; font-size: 0.85em; display: inline-block; margin-right: 4px;">authentication</span> | |
</div> | |
<div style="background-color: #f0e6ff; padding: 10px; border-radius: 4px; border: 1px solid #ddd6fe; margin: 10px 0;"> | |
<strong>🤖 AI Summary:</strong> This issue involves a critical authentication bug that prevents users from logging in to the application. | |
</div> | |
<div style="background-color: #fffbeb; padding: 10px; border-radius: 4px; border: 1px solid #fef3c7; margin: 10px 0;"> | |
<strong>🤔 AI Missing Info Analysis:</strong> Steps to reproduce, error logs | |
</div> | |
<div style="background-color: #e0f2fe; padding: 10px; border-radius: 4px; border: 1px solid #bae6fd; margin: 10px 0;"> | |
<strong>🔬 AI Preliminary Analysis:</strong> Hypothesis: Session token invalidation issue causing authentication failure | |
</div> | |
<div style="background-color: #fffbeb; padding: 10px; border-radius: 4px; border: 1px solid #fef3c7; margin: 10px 0;"> | |
<h4>⚠️ Potential Duplicate Issues:</h4> | |
<p>Issue IDs: <a href="#" onclick="selectIssue(124); return false;" title="Issue #124 has similar content">#124</a>, <a href="#" onclick="selectIssue(126); return false;" title="Issue #126 has similar content">#126</a></p> | |
</div> | |
<div style="margin-top: 15px; border-top: 1px solid #eee; padding-top: 10px;"> | |
<h4>Description:</h4> | |
<p>Users are reporting issues with the authentication system. They're unable to log in after entering correct credentials. The error message shows "Invalid session token".</p> | |
<p>This issue appears to be related to session token invalidation after a successful login. The server responds with a 401 error when trying to access authenticated resources.</p> | |
</div> | |
`; | |
// Switch to Resolution Studio tab | |
tabs.forEach(t => t.classList.remove('active')); | |
tabContents.forEach(tc => tc.classList.remove('active')); | |
document.querySelector('.tab[data-tab="studio"]').classList.add('active'); | |
document.getElementById('studio-content').classList.add('active'); | |
} | |
}); | |
// AI suggestion button | |
document.getElementById('suggest-btn').addEventListener('click', async () => { | |
const aiOutput = document.querySelector('.ai-output'); | |
aiOutput.innerHTML = '<p>Generating AI suggestions...</p>'; | |
try { | |
const response = await fetch('/ai_suggestions', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
issue_id: document.querySelector('#issue-table tr.selected td:first-child').textContent, | |
hf_token: document.getElementById('hf-token').value, | |
model: document.getElementById('model-select').value | |
}) | |
}); | |
const data = await response.json(); | |
if (response.ok) { | |
aiOutput.innerHTML = data.suggestions; | |
} else { | |
aiOutput.innerHTML = `<p class="error">Error: ${data.error}</p>`; | |
} | |
} catch (error) { | |
aiOutput.innerHTML = `<p class="error">Error generating suggestions: ${error.message}</p>`; | |
} | |
}); | |
// AI patch button | |
document.getElementById('patch-btn').addEventListener('click', async () => { | |
const aiOutput = document.querySelector('.ai-output'); | |
aiOutput.innerHTML = '<p>Generating code patch...</p>'; | |
try { | |
const response = await fetch('/ai_patch', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
issue_id: document.querySelector('#issue-table tr.selected td:first-child').textContent, | |
hf_token: document.getElementById('hf-token').value, | |
model: document.getElementById('model-select').value | |
}) | |
}); | |
const data = await response.json(); | |
if (response.ok) { | |
aiOutput.innerHTML = data.patch; | |
} else { | |
aiOutput.innerHTML = `<p class="error">Error: ${data.error}</p>`; | |
} | |
} catch (error) { | |
aiOutput.innerHTML = `<p class="error">Error generating patch: ${error.message}</p>`; | |
} | |
}); | |
</script> | |
</body> | |
</html> | |
''' | |
def extract_owner_repo(url): | |
"""Extract owner and repository name from GitHub URL""" | |
pattern = r"github\.com/([^/]+)/([^/]+)" | |
match = re.search(pattern, url) | |
if match: | |
owner, repo = match.groups() | |
# Remove .git suffix if present | |
if repo.endswith('.git'): | |
repo = repo[:-4] | |
return owner, repo | |
return None, None | |
def get_github_issues(owner, repo, token=None): | |
"""Fetch open issues from a GitHub repository""" | |
url = f"https://api.github.com/repos/{owner}/{repo}/issues" | |
headers = { | |
"Accept": "application/vnd.github.v3+json" | |
} | |
if token: | |
headers["Authorization"] = f"token {token}" | |
params = { | |
"state": "open", | |
"per_page": 100 | |
} | |
try: | |
response = requests.get(url, headers=headers, params=params) | |
response.raise_for_status() | |
return response.json() | |
except requests.exceptions.RequestException as e: | |
raise Exception(f"Failed to fetch issues: {str(e)}") | |
def classify_issue_severity(title, body): | |
"""Simple rule-based severity classification""" | |
text = (title + " " + (body or "")).lower() | |
critical_keywords = ['crash', 'critical', 'fail', 'broken', 'security', 'vulnerability'] | |
high_keywords = ['error', 'bug', 'performance', 'slow'] | |
medium_keywords = ['improve', 'enhancement', 'feature', 'request'] | |
low_keywords = ['documentation', 'typo', 'minor'] | |
for keyword in critical_keywords: | |
if keyword in text: | |
return 'Critical' | |
for keyword in high_keywords: | |
if keyword in text: | |
return 'High' | |
for keyword in medium_keywords: | |
if keyword in text: | |
return 'Medium' | |
for keyword in low_keywords: | |
if keyword in text: | |
return 'Low' | |
return 'Unknown' | |
def cluster_issues(issues): | |
"""Simple clustering based on keywords in titles""" | |
clusters = {} | |
cluster_id = 1 | |
for issue in issues: | |
title = issue['title'].lower() | |
# Extract potential cluster keywords | |
if 'auth' in title or 'login' in title or 'session' in title: | |
cluster = 1 | |
elif 'performance' in title or 'slow' in title or 'speed' in title: | |
cluster = 2 | |
elif 'doc' in title or 'readme' in title or 'documentation' in title: | |
cluster = 3 | |
else: | |
cluster = cluster_id | |
cluster_id += 1 | |
issue['cluster'] = cluster | |
if cluster not in clusters: | |
clusters[cluster] = [] | |
clusters[cluster].append(issue) | |
return issues, len(clusters) | |
def index(): | |
return render_template_string(HTML_TEMPLATE) | |
def scan_issues(): | |
try: | |
data = request.get_json() | |
repo_url = data.get('repo_url') | |
github_token = data.get('github_token') | |
if not repo_url: | |
return jsonify({'error': 'Repository URL is required'}), 400 | |
owner, repo = extract_owner_repo(repo_url) | |
if not owner or not repo: | |
return jsonify({'error': 'Invalid GitHub URL format'}), 400 | |
# Fetch issues from GitHub | |
github_issues = get_github_issues(owner, repo, github_token) | |
# Process issues | |
processed_issues = [] | |
for issue in github_issues: | |
if 'pull_request' not in issue: # Skip pull requests | |
processed_issues.append({ | |
'number': issue['number'], | |
'title': issue['title'], | |
'body': issue['body'], | |
'severity': classify_issue_severity(issue['title'], issue['body']) | |
}) | |
# Cluster issues | |
clustered_issues, cluster_count = cluster_issues(processed_issues) | |
# Calculate severity distribution | |
severity_counter = Counter(issue['severity'] for issue in clustered_issues) | |
return jsonify({ | |
'issues': clustered_issues, | |
'total_issues': len(clustered_issues), | |
'clusters': cluster_count, | |
'severity_distribution': dict(severity_counter) | |
}) | |
except Exception as e: | |
return jsonify({'error': str(e)}), 500 | |
def ai_suggestions(): | |
try: | |
data = request.get_json() | |
issue_id = data.get('issue_id') | |
hf_token = data.get('hf_token') | |
model = data.get('model') | |
if not hf_token: | |
return jsonify({'error': 'Hugging Face token is required'}), 400 | |
# In a real implementation, you would call the Hugging Face API here | |
# For this example, we'll return mock suggestions | |
suggestions = f''' | |
<h3>💡 Suggestion based on {model}:</h3> | |
<p><strong>1. Address Missing Information:</strong> Request steps to reproduce from the user and ask for server error logs.</p> | |
<p><strong>2. Refine Understanding:</strong> The issue #{issue_id} appears to be related to authentication flow problems.</p> | |
<p><strong>3. Identify Relevant Code Areas:</strong> Check authentication modules and session management code.</p> | |
<p><strong>4. Implementation Steps:</strong> | |
<ol> | |
<li>Review authentication implementation</li> | |
<li>Check session handling logic</li> | |
<li>Verify token generation and validation</li> | |
<li>Test fix in staging environment</li> | |
</ol></p> | |
<p><strong>5. Testing Recommendations:</strong> Test with different browsers and network conditions.</p> | |
''' | |
return jsonify({'suggestions': suggestions}) | |
except Exception as e: | |
return jsonify({'error': str(e)}), 500 | |
def ai_patch(): | |
try: | |
data = request.get_json() | |
issue_id = data.get('issue_id') | |
hf_token = data.get('hf_token') | |
model = data.get('model') | |
if not hf_token: | |
return jsonify({'error': 'Hugging Face token is required'}), 400 | |
# In a real implementation, you would call the Hugging Face API here | |
# For this example, we'll return a mock patch | |
patch = f''' | |
<h3>🩹 Patch Generation Result from {model}: (Patch Generated)</h3> | |
<p><strong>Explanation:</strong> This patch addresses issue #{issue_id} by improving error handling in the authentication flow.</p> | |
<hr> | |
<p><strong>Patch:</strong></p> | |
<pre style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;"> | |
--- a/src/auth/auth_service.py | |
+++ b/src/auth/auth_service.py | |
@@ -42,6 +42,10 @@ | |
user = self.get_user(credentials.username) | |
if not user: | |
raise AuthenticationError("User not found") | |
+ | |
+ # Validate account status | |
+ if not user.is_active: | |
+ raise AuthenticationError("Account is deactivated") | |
if not self.check_password(credentials.password, user.password_hash): | |
raise AuthenticationError("Invalid credentials") | |
</pre> | |
''' | |
return jsonify({'patch': patch}) | |
except Exception as e: | |
return jsonify({'error': str(e)}), 500 | |
if __name__ == '__main__': | |
app.run(debug=True) |