GitBot / app.py
acecalisto3's picture
Update app.py
14133ef verified
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)
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/scan_issues', methods=['POST'])
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
@app.route('/ai_suggestions', methods=['POST'])
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
@app.route('/ai_patch', methods=['POST'])
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)