Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -1,254 +1,336 @@
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
-
import
|
|
|
4 |
from git import Repo, GitCommandError
|
5 |
from pathlib import Path
|
6 |
from datetime import datetime
|
7 |
import shutil
|
8 |
-
import time
|
9 |
import json
|
10 |
-
import aiohttp
|
11 |
-
import asyncio
|
12 |
-
from typing import Dict, Optional, Tuple
|
13 |
import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
# ========== Configuration ==========
|
16 |
-
REPO_OWNER = "enricoros"
|
17 |
-
REPO_NAME = "big-agi"
|
18 |
-
MIAGI_FORK = "https://github.com/Ig0tU/miagi.git"
|
19 |
WORKSPACE = Path("/tmp/issue_workspace")
|
20 |
WORKSPACE.mkdir(exist_ok=True)
|
21 |
-
|
22 |
-
|
23 |
-
# Setup logging
|
24 |
logging.basicConfig(level=logging.INFO)
|
25 |
logger = logging.getLogger(__name__)
|
26 |
|
27 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
theme = gr.themes.Default(
|
29 |
-
primary_hue="
|
30 |
-
secondary_hue="
|
31 |
radius_size="lg",
|
32 |
).set(
|
33 |
-
button_primary_background_fill="*
|
34 |
-
button_primary_background_fill_hover="*primary_400",
|
35 |
button_primary_text_color="white",
|
36 |
block_label_background_fill="*primary_500",
|
37 |
block_label_text_color="white",
|
38 |
-
body_background_fill="
|
39 |
)
|
40 |
|
41 |
-
# ==========
|
42 |
-
class
|
43 |
def __init__(self):
|
44 |
self.issues: Dict[int, dict] = {}
|
45 |
-
self.
|
46 |
-
self.current_issue: Optional[int] = None
|
47 |
self.repo: Optional[Repo] = None
|
48 |
-
self.
|
49 |
-
self.
|
|
|
|
|
|
|
|
|
50 |
|
51 |
-
async def
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
54 |
|
|
|
|
|
|
|
|
|
|
|
55 |
try:
|
56 |
-
headers = {"Authorization": f"Bearer {os.environ.get('GITHUB_TOKEN', '')}"}
|
57 |
async with aiohttp.ClientSession() as session:
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
|
|
|
|
|
|
67 |
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
async with session.get(f"{HF_SPACE_API}/{REPO_OWNER}/{REPO_NAME}",
|
74 |
-
headers=headers) as response:
|
75 |
-
if response.status == 200:
|
76 |
-
space_data = await response.json()
|
77 |
-
self.space_status[issue_number] = space_data
|
78 |
-
return {"space_found": True, "data": space_data}
|
79 |
-
return {"space_found": False}
|
80 |
except Exception as e:
|
81 |
-
logger.error(f"
|
82 |
-
return
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
return
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
issue = issue_manager.issues[issue_number]
|
98 |
-
repo_path = WORKSPACE / f"miagi-{issue_number}"
|
99 |
-
issue_manager.progress_tracker[issue_number] = {"status": "initializing"}
|
100 |
-
|
101 |
-
# Check Space status
|
102 |
-
space_status = await issue_manager.check_space_status(issue_number)
|
103 |
-
|
104 |
-
# Clone with progress
|
105 |
try:
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
repo = Repo(repo_path)
|
|
|
|
|
|
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
issues_dir.mkdir(parents=True, exist_ok=True)
|
118 |
-
|
119 |
-
replication_details = {
|
120 |
-
"timestamp": datetime.utcnow().isoformat(),
|
121 |
-
"space_status": space_status
|
122 |
-
}
|
123 |
-
(issues_dir / "replication.json").write_text(json.dumps(replication_details))
|
124 |
-
|
125 |
-
issue_manager.repo = repo
|
126 |
-
issue_manager.current_issue = issue_number
|
127 |
-
|
128 |
-
return {
|
129 |
-
"success": f"🚀 Replicated issue #{issue_number}",
|
130 |
-
"space": space_status,
|
131 |
-
"progress": issue_manager.progress_tracker[issue_number]
|
132 |
-
}, issue["body"], gr.update(selected=1)
|
133 |
-
|
134 |
-
except Exception as e:
|
135 |
-
logger.error(f"Replication error: {e}")
|
136 |
-
return {"error": f"⛔ Critical error: {str(e)}"}, None, None
|
137 |
|
138 |
-
async def
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
try:
|
143 |
-
repo = issue_manager.repo
|
144 |
-
issue_number = issue_manager.current_issue
|
145 |
-
resolution_dir = repo.working_dir / "issues" / str(issue_number)
|
146 |
|
147 |
-
|
148 |
-
|
149 |
-
"
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
|
|
|
|
|
|
160 |
|
161 |
-
async def
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
|
|
|
|
176 |
|
177 |
-
|
178 |
-
|
179 |
-
|
|
|
|
|
|
|
180 |
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
188 |
|
189 |
-
async def
|
190 |
-
|
|
|
191 |
try:
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
|
|
|
|
|
|
199 |
)
|
|
|
|
|
|
|
|
|
|
|
200 |
except Exception as e:
|
201 |
-
|
202 |
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
|
|
|
|
215 |
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
space_config = gr.Textbox(lines=2, label="Space Config (if applicable)")
|
222 |
-
resolve_btn = gr.Button("💾 Save", variant="primary")
|
223 |
-
with gr.Column():
|
224 |
-
file_explorer = gr.FileExplorer()
|
225 |
-
|
226 |
-
with gr.Tab("🚀 Deploy"):
|
227 |
-
deploy_btn = gr.Button("🔥 Submit", variant="primary")
|
228 |
-
pr_output = gr.Markdown()
|
229 |
-
space_status = gr.JSON(label="Space Status")
|
230 |
-
|
231 |
-
# Event handlers with async support
|
232 |
-
replicate_btn.click(
|
233 |
-
fn=replicate_issue,
|
234 |
-
inputs=issue_input,
|
235 |
-
outputs=[status_output, issue_desc, tabs]
|
236 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
237 |
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
|
|
243 |
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
|
249 |
return app
|
250 |
|
251 |
# ========== Execution ==========
|
252 |
if __name__ == "__main__":
|
253 |
-
app =
|
254 |
-
app.launch(
|
|
|
1 |
import gradio as gr
|
2 |
import os
|
3 |
+
import aiohttp
|
4 |
+
import asyncio
|
5 |
from git import Repo, GitCommandError
|
6 |
from pathlib import Path
|
7 |
from datetime import datetime
|
8 |
import shutil
|
|
|
9 |
import json
|
|
|
|
|
|
|
10 |
import logging
|
11 |
+
import re
|
12 |
+
from typing import Dict, List, Optional, Tuple
|
13 |
+
import subprocess
|
14 |
+
import plotly.graph_objects as go
|
15 |
+
from transformers import pipeline # For local fallback
|
16 |
+
import threading
|
17 |
|
18 |
# ========== Configuration ==========
|
|
|
|
|
|
|
19 |
WORKSPACE = Path("/tmp/issue_workspace")
|
20 |
WORKSPACE.mkdir(exist_ok=True)
|
21 |
+
GITHUB_API = "https://api.github.com/repos"
|
22 |
+
HF_INFERENCE_API = "https://api-inference.huggingface.co/models"
|
|
|
23 |
logging.basicConfig(level=logging.INFO)
|
24 |
logger = logging.getLogger(__name__)
|
25 |
|
26 |
+
# Free Hugging Face models for selection
|
27 |
+
HF_MODELS = {
|
28 |
+
"Mistral-8x7B (Powerful)": "mistralai/Mixtral-8x7B-Instruct-v0.1",
|
29 |
+
"DistilBERT (Lightweight)": "distilbert-base-uncased",
|
30 |
+
"BART (Summarization)": "facebook/bart-large"
|
31 |
+
}
|
32 |
+
DEFAULT_MODEL = "mistralai/Mixtral-8x7B-Instruct-v0.1"
|
33 |
+
|
34 |
+
# ========== Theme ==========
|
35 |
theme = gr.themes.Default(
|
36 |
+
primary_hue="indigo",
|
37 |
+
secondary_hue="slate",
|
38 |
radius_size="lg",
|
39 |
).set(
|
40 |
+
button_primary_background_fill="*primary_600",
|
|
|
41 |
button_primary_text_color="white",
|
42 |
block_label_background_fill="*primary_500",
|
43 |
block_label_text_color="white",
|
44 |
+
body_background_fill="gray_50",
|
45 |
)
|
46 |
|
47 |
+
# ========== Issue Manager ==========
|
48 |
+
class IssueManager:
|
49 |
def __init__(self):
|
50 |
self.issues: Dict[int, dict] = {}
|
51 |
+
self.repo_url: Optional[str] = None
|
|
|
52 |
self.repo: Optional[Repo] = None
|
53 |
+
self.current_issue: Optional[int] = None
|
54 |
+
self.github_token: Optional[str] = None
|
55 |
+
self.hf_token: Optional[str] = None
|
56 |
+
self.collaborators: Dict[str, str] = {}
|
57 |
+
self.local_nlp = None # Local fallback
|
58 |
+
self.webhook_active = False
|
59 |
|
60 |
+
async def crawl_issues(self, repo_url: str, github_token: str, hf_token: str) -> Tuple[bool, str]:
|
61 |
+
self.repo_url = repo_url
|
62 |
+
self.github_token = github_token
|
63 |
+
self.hf_token = hf_token
|
64 |
+
match = re.match(r"https://github.com/([^/]+)/([^/]+)", repo_url)
|
65 |
+
if not match:
|
66 |
+
return False, "Invalid GitHub URL format"
|
67 |
|
68 |
+
owner, repo_name = match.groups()
|
69 |
+
all_issues = []
|
70 |
+
page = 1
|
71 |
+
headers = {"Authorization": f"Bearer {github_token}", "Accept": "application/vnd.github+json"}
|
72 |
+
|
73 |
try:
|
|
|
74 |
async with aiohttp.ClientSession() as session:
|
75 |
+
while True:
|
76 |
+
url = f"{GITHUB_API}/{owner}/{repo_name}/issues?page={page}&state=open"
|
77 |
+
async with session.get(url, headers=headers) as response:
|
78 |
+
if response.status == 403:
|
79 |
+
return False, "GitHub rate limit exceeded or invalid token"
|
80 |
+
if response.status != 200:
|
81 |
+
return False, f"GitHub API error: {response.status}"
|
82 |
+
issues = await response.json()
|
83 |
+
if not issues:
|
84 |
+
break
|
85 |
+
all_issues.extend(issues)
|
86 |
+
page += 1
|
87 |
|
88 |
+
self.issues = {
|
89 |
+
i['number']: {**i, 'severity': self._determine_severity(i)}
|
90 |
+
for i in all_issues if not i.get('assignee')
|
91 |
+
}
|
92 |
+
return True, f"Found {len(self.issues)} unresolved/unassigned issues"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
except Exception as e:
|
94 |
+
logger.error(f"Crawl error: {e}")
|
95 |
+
return False, str(e)
|
96 |
+
|
97 |
+
def _determine_severity(self, issue: dict) -> str:
|
98 |
+
body = (issue.get('body') or '').lower()
|
99 |
+
labels = [l['name'].lower() for l in issue.get('labels', [])]
|
100 |
+
if any(l in labels for l in ['critical', 'urgent', 'security']) or 'crash' in body:
|
101 |
+
return "Critical"
|
102 |
+
elif any(l in labels for l in ['high', 'important']) or 'error' in body:
|
103 |
+
return "High"
|
104 |
+
elif any(l in labels for l in ['medium', 'bug']) or 'issue' in body:
|
105 |
+
return "Medium"
|
106 |
+
return "Low"
|
107 |
+
|
108 |
+
async def clone_and_work(self, issue_number: int) -> Tuple[Dict, str, str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
try:
|
110 |
+
repo_path = WORKSPACE / f"repo-{issue_number}"
|
111 |
+
if repo_path.exists():
|
112 |
+
shutil.rmtree(repo_path)
|
113 |
+
|
114 |
+
self.repo = Repo.clone_from(self.repo_url, repo_path)
|
115 |
+
branch = f"issue-{issue_number}"
|
116 |
+
self.repo.git.checkout('-b', branch)
|
117 |
+
self.current_issue = issue_number
|
118 |
|
119 |
+
issue = self.issues[issue_number]
|
120 |
+
diff = ""
|
121 |
+
return {"success": f"Working on #{issue_number}"}, issue['body'], diff
|
122 |
+
except GitCommandError as e:
|
123 |
+
return {"error": str(e)}, "", ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
|
125 |
+
async def suggest_resolution(self, issue_number: int, model: str) -> str:
|
126 |
+
"""Use HF Inference API or local fallback"""
|
127 |
+
issue = self.issues[issue_number]
|
128 |
+
prompt = f"Suggest a detailed resolution for this GitHub issue:\nTitle: {issue['title']}\nBody: {issue['body']}"
|
|
|
|
|
|
|
|
|
129 |
|
130 |
+
if self.hf_token:
|
131 |
+
headers = {"Authorization": f"Bearer {self.hf_token}"}
|
132 |
+
payload = {"inputs": prompt, "parameters": {"max_new_tokens": 200}}
|
133 |
+
try:
|
134 |
+
async with aiohttp.ClientSession() as session:
|
135 |
+
async with session.post(f"{HF_INFERENCE_API}/{model}", headers=headers, json=payload) as resp:
|
136 |
+
if resp.status == 200:
|
137 |
+
result = await resp.json()
|
138 |
+
return result[0].get("generated_text", "No suggestion").strip()
|
139 |
+
elif resp.status == 429:
|
140 |
+
return await self._local_suggestion(prompt)
|
141 |
+
return f"HF API error: {resp.status}"
|
142 |
+
except Exception as e:
|
143 |
+
logger.error(f"HF suggestion error: {e}")
|
144 |
+
return await self._local_suggestion(prompt)
|
145 |
+
return await self._local_suggestion(prompt)
|
146 |
|
147 |
+
async def _local_suggestion(self, prompt: str) -> str:
|
148 |
+
"""Fallback to local model"""
|
149 |
+
if not self.local_nlp:
|
150 |
+
self.local_nlp = pipeline("text-generation", model="distilgpt2", device=-1) # CPU-only, lightweight
|
151 |
+
try:
|
152 |
+
result = self.local_nlp(prompt, max_length=200, num_return_sequences=1)
|
153 |
+
return result[0]["generated_text"].strip()
|
154 |
+
except Exception as e:
|
155 |
+
logger.error(f"Local fallback error: {e}")
|
156 |
+
return "Local suggestion unavailable"
|
157 |
+
|
158 |
+
async def resolve_and_verify(self, resolution: str) -> Tuple[Dict, str]:
|
159 |
+
if not self.current_issue or not self.repo:
|
160 |
+
return {"error": "No active issue/repo"}, ""
|
161 |
+
|
162 |
+
issue_number = self.current_issue
|
163 |
+
repo_path = self.repo.working_dir
|
164 |
|
165 |
+
try:
|
166 |
+
resolution_dir = repo_path / "resolutions" / str(issue_number)
|
167 |
+
resolution_dir.mkdir(parents=True, exist_ok=True)
|
168 |
+
timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
|
169 |
+
resolution_file = resolution_dir / f"resolution_{timestamp}.txt"
|
170 |
+
resolution_file.write_text(resolution)
|
171 |
|
172 |
+
self.repo.git.add(all=True)
|
173 |
+
self.repo.index.commit(f"Resolve #{issue_number}")
|
174 |
+
diff = self.repo.git.diff('HEAD^', 'HEAD')
|
175 |
+
|
176 |
+
v1 = await self._verify_resolution(issue_number, resolution)
|
177 |
+
await asyncio.sleep(1)
|
178 |
+
v2 = await self._verify_resolution(issue_number, resolution)
|
179 |
+
|
180 |
+
if v1["status"] and v2["status"]:
|
181 |
+
self.repo.git.push('origin', f"issue-{issue_number}")
|
182 |
+
pr_url = await self._create_pr(issue_number)
|
183 |
+
return {
|
184 |
+
"success": f"Issue #{issue_number} resolved and verified",
|
185 |
+
"pr_url": pr_url,
|
186 |
+
"verification": [v1, v2]
|
187 |
+
}, diff
|
188 |
+
return {"error": "Verification failed", "details": [v1, v2]}, diff
|
189 |
+
|
190 |
+
except Exception as e:
|
191 |
+
logger.error(f"Resolve error: {e}")
|
192 |
+
return {"error": str(e)}, ""
|
193 |
|
194 |
+
async def _verify_resolution(self, issue_number: int, resolution: str) -> Dict:
|
195 |
+
issue = self.issues[issue_number]
|
196 |
+
body = issue['body'].lower()
|
197 |
try:
|
198 |
+
diff = self.repo.git.diff('HEAD^', 'HEAD')
|
199 |
+
if 'file' in body:
|
200 |
+
file_match = re.search(r'file:\s*(\S+)', body)
|
201 |
+
if file_match and file_match.group(1) not in diff:
|
202 |
+
return {"status": False, "message": "Resolution doesn’t modify mentioned file"}
|
203 |
+
|
204 |
+
if (self.repo.working_dir / "tests").exists():
|
205 |
+
proc = await asyncio.create_subprocess_exec(
|
206 |
+
"pytest", cwd=self.repo.working_dir, stdout=asyncio.subprocess.PIPE,
|
207 |
+
stderr=asyncio.subprocess.PIPE
|
208 |
)
|
209 |
+
stdout, stderr = await proc.communicate()
|
210 |
+
if proc.returncode != 0:
|
211 |
+
return {"status": False, "message": f"Tests failed: {stderr.decode()}"}
|
212 |
+
|
213 |
+
return {"status": True, "message": "Resolution verified"}
|
214 |
except Exception as e:
|
215 |
+
return {"status": False, "message": str(e)}
|
216 |
|
217 |
+
async def _create_pr(self, issue_number: int) -> str:
|
218 |
+
owner, repo_name = re.match(r"https://github.com/([^/]+)/([^/]+)", self.repo_url).groups()
|
219 |
+
headers = {"Authorization": f"Bearer {self.github_token}", "Accept": "application/vnd.github+json"}
|
220 |
+
pr_data = {
|
221 |
+
"title": f"Resolve #{issue_number}: {self.issues[issue_number]['title']}",
|
222 |
+
"body": f"Resolution for #{issue_number}\nVerification: Passed twice",
|
223 |
+
"head": f"issue-{issue_number}",
|
224 |
+
"base": "main"
|
225 |
+
}
|
226 |
+
async with aiohttp.ClientSession() as session:
|
227 |
+
resp = await session.post(f"{GITHUB_API}/{owner}/{repo_name}/pulls", headers=headers, json=pr_data)
|
228 |
+
if resp.status == 201:
|
229 |
+
return (await resp.json())['html_url']
|
230 |
+
raise Exception(f"PR creation failed: {await resp.text()}")
|
231 |
|
232 |
+
def get_stats(self) -> go.Figure:
|
233 |
+
severity_counts = {s: sum(1 for i in self.issues.values() if i['severity'] == s) for s in ["Critical", "High", "Medium", "Low"]}
|
234 |
+
return go.Figure(
|
235 |
+
data=[go.Bar(x=list(severity_counts.keys()), y=list(severity_counts.values()))],
|
236 |
+
layout={"title": "Issue Severity Distribution"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
237 |
)
|
238 |
+
|
239 |
+
async def webhook_listener(self):
|
240 |
+
"""Simulated webhook for real-time updates"""
|
241 |
+
while self.webhook_active:
|
242 |
+
await asyncio.sleep(10) # Poll every 10s (replace with real webhook in production)
|
243 |
+
await self.crawl_issues(self.repo_url, self.github_token, self.hf_token)
|
244 |
+
logger.info("Webhook: Issues refreshed")
|
245 |
+
|
246 |
+
manager = IssueManager()
|
247 |
+
|
248 |
+
# ========== UI ==========
|
249 |
+
def create_ui():
|
250 |
+
with gr.Blocks(theme=theme, title="Hugging Face Git Resolver") as app:
|
251 |
+
gr.Markdown("# 🤗 Hugging Face Git Resolver\nFree AI-powered issue resolution for all")
|
252 |
+
current_diff = gr.State(value="")
|
253 |
|
254 |
+
with gr.Row():
|
255 |
+
repo_url = gr.Textbox(label="GitHub Repository URL", placeholder="https://github.com/username/repo")
|
256 |
+
github_token = gr.Textbox(label="GitHub Token", type="password")
|
257 |
+
hf_token = gr.Textbox(label="Hugging Face Token", type="password")
|
258 |
+
model_select = gr.Dropdown(choices=list(HF_MODELS.keys()), value="Mistral-8x7B (Powerful)", label="AI Model")
|
259 |
+
crawl_btn = gr.Button("Crawl Issues", variant="primary")
|
260 |
|
261 |
+
with gr.Tabs():
|
262 |
+
with gr.Tab("Issues"):
|
263 |
+
status = gr.Textbox(label="Status")
|
264 |
+
issue_list = gr.Dataframe(
|
265 |
+
headers=["Number", "Title", "Severity"],
|
266 |
+
datatype=["number", "str", "str"],
|
267 |
+
interactive=True
|
268 |
+
)
|
269 |
+
progress = gr.Progress()
|
270 |
+
|
271 |
+
with gr.Tab("Work"):
|
272 |
+
with gr.Row():
|
273 |
+
issue_num = gr.Number(label="Issue Number")
|
274 |
+
work_btn = gr.Button("Start Work")
|
275 |
+
issue_body = gr.Markdown()
|
276 |
+
with gr.Row():
|
277 |
+
resolution = gr.Textbox(label="Resolution", lines=5)
|
278 |
+
suggest_btn = gr.Button("AI Suggest")
|
279 |
+
diff_view = gr.HTML(label="Changes Preview")
|
280 |
+
resolve_btn = gr.Button("Resolve & Verify", variant="primary")
|
281 |
+
result = gr.JSON()
|
282 |
+
|
283 |
+
with gr.Tab("Dashboard"):
|
284 |
+
stats_plot = gr.Plot(label="Issue Stats")
|
285 |
+
collab_status = gr.Textbox(label="Collaborators Online", value="No collaborators yet")
|
286 |
+
|
287 |
+
# Event Handlers
|
288 |
+
async def crawl_and_display(url, g_token, h_token, model):
|
289 |
+
success, msg = await manager.crawl_issues(url, g_token, h_token)
|
290 |
+
if success:
|
291 |
+
sorted_issues = sorted(manager.issues.values(), key=lambda x: {"Critical": 0, "High": 1, "Medium": 2, "Low": 3}[x['severity']])
|
292 |
+
df = [[i['number'], i['title'], i['severity']] for i in sorted_issues]
|
293 |
+
if not manager.webhook_active:
|
294 |
+
manager.webhook_active = True
|
295 |
+
asyncio.create_task(manager.webhook_listener())
|
296 |
+
return msg, gr.Dataframe(value=df), manager.get_stats()
|
297 |
+
return msg, gr.Dataframe(), None
|
298 |
+
|
299 |
+
async def start_work(num):
|
300 |
+
result, body, diff = await manager.clone_and_work(int(num))
|
301 |
+
diff_html = f"<pre>{diff}</pre>" if diff else "No changes yet"
|
302 |
+
return result, body, diff_html, diff
|
303 |
+
|
304 |
+
async def suggest_res(num, model):
|
305 |
+
suggestion = await manager.suggest_resolution(int(num), HF_MODELS[model])
|
306 |
+
return suggestion
|
307 |
+
|
308 |
+
async def resolve_issue(res, diff_state):
|
309 |
+
result, diff = await manager.resolve_and_verify(res)
|
310 |
+
diff_html = f"<pre>{diff}</pre>" if diff else "No changes"
|
311 |
+
return result, diff_html, diff
|
312 |
+
|
313 |
+
def select_issue(evt: gr.SelectData):
|
314 |
+
num = evt.value[0]
|
315 |
+
return num, manager.issues[num]['body']
|
316 |
+
|
317 |
+
issue_list.select(select_issue, None, [issue_num, issue_body])
|
318 |
+
crawl_btn.click(crawl_and_display, [repo_url, github_token, hf_token, model_select], [status, issue_list, stats_plot])
|
319 |
+
work_btn.click(start_work, [issue_num], [status, issue_body, diff_view, current_diff])
|
320 |
+
suggest_btn.click(suggest_res, [issue_num, model_select], [resolution])
|
321 |
+
resolve_btn.click(resolve_issue, [resolution, current_diff], [result, diff_view, current_diff])
|
322 |
+
|
323 |
+
async def update_collab():
|
324 |
+
while True:
|
325 |
+
await asyncio.sleep(5)
|
326 |
+
manager.collaborators["user1"] = "Working on #5" # Simulated
|
327 |
+
yield "\n".join(f"{k}: {v}" for k, v in manager.collaborators.items())
|
328 |
+
|
329 |
+
app.load(fn=update_collab, inputs=None, outputs=collab_status, _js="() => setInterval(() => gradioApp().dispatchEvent(new Event('update_collab')), 5000)")
|
330 |
|
331 |
return app
|
332 |
|
333 |
# ========== Execution ==========
|
334 |
if __name__ == "__main__":
|
335 |
+
app = create_ui()
|
336 |
+
app.launch(share=True)
|