acecalisto3 commited on
Commit
b58e2e0
·
verified ·
1 Parent(s): 4bc55d3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +283 -201
app.py CHANGED
@@ -1,254 +1,336 @@
1
  import gradio as gr
2
  import os
3
- import requests
 
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
- HF_SPACE_API = "https://huggingface.co/api/spaces" # Hypothetical HF Space API
22
-
23
- # Setup logging
24
  logging.basicConfig(level=logging.INFO)
25
  logger = logging.getLogger(__name__)
26
 
27
- # ========== Enhanced Theme ==========
 
 
 
 
 
 
 
 
28
  theme = gr.themes.Default(
29
- primary_hue="emerald",
30
- secondary_hue="zinc",
31
  radius_size="lg",
32
  ).set(
33
- button_primary_background_fill="*primary_500",
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="gray_100",
39
  )
40
 
41
- # ========== State Management with Spaces Integration ==========
42
- class EnhancedIssueManager:
43
  def __init__(self):
44
  self.issues: Dict[int, dict] = {}
45
- self.last_fetched: Optional[datetime] = None
46
- self.current_issue: Optional[int] = None
47
  self.repo: Optional[Repo] = None
48
- self.space_status: Dict = {}
49
- self.progress_tracker = {}
 
 
 
 
50
 
51
- async def fetch_issues(self) -> Tuple[bool, str]:
52
- if self.last_fetched and (datetime.now() - self.last_fetched).seconds < 300:
53
- return True, "Using cached issues"
 
 
 
 
54
 
 
 
 
 
 
55
  try:
56
- headers = {"Authorization": f"Bearer {os.environ.get('GITHUB_TOKEN', '')}"}
57
  async with aiohttp.ClientSession() as session:
58
- async with session.get(f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/issues",
59
- headers=headers) as response:
60
- response.raise_for_status()
61
- self.issues = {issue['number']: issue for issue in await response.json()}
62
- self.last_fetched = datetime.now()
63
- return True, "Successfully fetched issues"
64
- except Exception as e:
65
- logger.error(f"Error fetching issues: {e}")
66
- return False, f"Failed to fetch issues: {str(e)}"
 
 
 
67
 
68
- async def check_space_status(self, issue_number: int) -> Dict:
69
- """Check if issue relates to a Hugging Face Space"""
70
- try:
71
- headers = {"Authorization": f"Bearer {os.environ.get('HF_TOKEN', '')}"}
72
- async with aiohttp.ClientSession() as session:
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"Space check failed: {e}")
82
- return {"space_found": False, "error": str(e)}
83
-
84
- issue_manager = EnhancedIssueManager()
85
-
86
- # ========== Core Functions with Space Handling ==========
87
- async def replicate_issue(issue_number: str) -> Tuple[Dict, str, gr.update]:
88
- try:
89
- issue_number = int(issue_number)
90
- success, fetch_message = await issue_manager.fetch_issues()
91
- if not success:
92
- return {"error": f"⚠️ {fetch_message}"}, None, None
93
-
94
- if issue_number not in issue_manager.issues:
95
- return {"error": f"❌ Issue #{issue_number} not found"}, None, None
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
- issue_manager.progress_tracker[issue_number] = {"status": "cloning"}
107
- repo = Repo.clone_from(MIAGI_FORK, repo_path, branch="main",
108
- progress=lambda x: issue_manager.progress_tracker[issue_number].update({"progress": x}))
109
- except GitCommandError:
110
- repo = Repo(repo_path)
 
 
 
111
 
112
- branch_name = f"issue-{issue_number}"
113
- repo.git.checkout('-b', branch_name)
114
-
115
- # Handle Space replication if applicable
116
- issues_dir = repo_path / "issues" / str(issue_number)
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 resolve_issue(resolution_notes: str, space_config: Optional[str] = None) -> Tuple[Dict, gr.update]:
139
- if not issue_manager.current_issue:
140
- return {"error": "⚠️ No active issue selected"}, None
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
- timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
148
- resolution_data = {
149
- "notes": resolution_notes,
150
- "space_config": space_config,
151
- "resolved_at": timestamp
152
- }
153
-
154
- (resolution_dir / f"resolution_{timestamp}.json").write_text(json.dumps(resolution_data))
155
- (resolution_dir / "RESOLVED").touch()
156
-
157
- return {"success": "🔧 Resolution saved"}, gr.update(visible=True)
158
- except Exception as e:
159
- return {"error": f"⛔ Resolution failed: {str(e)}"}, None
 
 
 
160
 
161
- async def submit_resolution() -> Dict:
162
- if not issue_manager.current_issue:
163
- return {"error": "⚠️ No active issue selected"}
164
-
165
- try:
166
- repo = issue_manager.repo
167
- issue_number = issue_manager.current_issue
168
-
169
- repo.git.add(all=True)
170
- commit_message = f"Resolve #{issue_number}: {issue_manager.issues[issue_number]['title']}"
171
- repo.index.commit(commit_message)
172
-
173
- origin = repo.remote(name='origin')
174
- branch_name = f"issue-{issue_number}"
175
- origin.push(refspec=f"{branch_name}:{branch_name}")
 
 
176
 
177
- # Space-specific submission if applicable
178
- if issue_manager.space_status.get(issue_number, {}).get("space_found"):
179
- await self._update_space_config(issue_number)
 
 
 
180
 
181
- pr_url = f"https://github.com/Ig0tU/miagi/compare/{branch_name}?expand=1"
182
- return {
183
- "success": f"🚀 Resolution submitted!\n\n[Review PR]({pr_url})",
184
- "pr_url": pr_url
185
- }
186
- except Exception as e:
187
- return {"error": f"⛔ Submission failed: {str(e)}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- async def _update_space_config(self, issue_number: int):
190
- """Update Hugging Face Space configuration"""
 
191
  try:
192
- headers = {"Authorization": f"Bearer {os.environ.get('HF_TOKEN', '')}"}
193
- config = {"issue_number": issue_number, "status": "resolved"}
194
- async with aiohttp.ClientSession() as session:
195
- await session.post(
196
- f"{HF_SPACE_API}/{REPO_OWNER}/{REPO_NAME}/config",
197
- json=config,
198
- headers=headers
 
 
 
199
  )
 
 
 
 
 
200
  except Exception as e:
201
- logger.error(f"Space config update failed: {e}")
202
 
203
- # ========== Enhanced UI ==========
204
- def create_enhanced_ui():
205
- with gr.Blocks(theme=theme, css="footer {visibility: hidden}") as app:
206
- gr.Markdown("# 🚀 Enhanced Repository Manager\n### With Hugging Face Spaces Integration")
207
-
208
- with gr.Tabs() as tabs:
209
- with gr.Tab("🔍 Investigate"):
210
- with gr.Row():
211
- issue_input = gr.Number(label="Issue Number")
212
- replicate_btn = gr.Button("⚡ Replicate", variant="primary")
213
- status_output = gr.JSON(label="Replication Status")
214
- progress_bar = gr.Progress()
 
 
215
 
216
- with gr.Tab("🔧 Resolve"):
217
- with gr.Row():
218
- with gr.Column():
219
- issue_desc = gr.Markdown()
220
- resolution_input = gr.Textbox(lines=8, label="Resolution Notes")
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
- resolve_btn.click(
239
- fn=resolve_issue,
240
- inputs=[resolution_input, space_config],
241
- outputs=[status_output, deploy_btn]
242
- )
 
243
 
244
- deploy_btn.click(
245
- fn=submit_resolution,
246
- outputs=[status_output, pr_output, space_status]
247
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
  return app
250
 
251
  # ========== Execution ==========
252
  if __name__ == "__main__":
253
- app = create_enhanced_ui()
254
- app.launch(server_port=7860, share=True)
 
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)