ayushm98 commited on
Commit
7725b81
Β·
1 Parent(s): d51e2da

UI redesign: detailed progress, numbered steps, test checks (v3.6.0)

Browse files

- parse_agent_status() now tracks specific activities (file counts, search results)
- format_progress_display() shows activity descriptions instead of generic status
- format_plan_display() extracts 7-8 numbered implementation steps
- format_final_result() shows 6 detailed test checks with pass/fail status

Files changed (2) hide show
  1. Dockerfile +1 -1
  2. chainlit_app.py +258 -95
Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
  # HuggingFace Spaces Dockerfile for CodePilot
2
- # BUILD_VERSION: 20 (v3.5.0 compact-ui - condensed plan, collapsible code, compact results)
3
  FROM python:3.11-slim
4
 
5
  # Set working directory
 
1
  # HuggingFace Spaces Dockerfile for CodePilot
2
+ # BUILD_VERSION: 21 (v3.6.0 detailed-progress - activity tracking, numbered steps, test checks)
3
  FROM python:3.11-slim
4
 
5
  # Set working directory
chainlit_app.py CHANGED
@@ -22,8 +22,8 @@ from concurrent.futures import ThreadPoolExecutor
22
  # ============================================================
23
  # STARTUP VERSION CHECK - Change this to detect if rebuild worked
24
  # ============================================================
25
- APP_VERSION = "3.5.0-compact-ui"
26
- BUILD_ID = "2024-12-20-v4"
27
  print("=" * 60)
28
  print(f"[STARTUP] CodePilot Chainlit App")
29
  print(f"[STARTUP] APP_VERSION: {APP_VERSION}")
@@ -102,7 +102,7 @@ def format_code_output(code_changes: dict) -> str:
102
 
103
 
104
  def parse_agent_status(logs: str) -> dict:
105
- """Parse logs to extract agent status information."""
106
  status = {
107
  'current_agent': None,
108
  'explorer_done': False,
@@ -110,42 +110,103 @@ def parse_agent_status(logs: str) -> dict:
110
  'coder_done': False,
111
  'reviewer_done': False,
112
  'approved': None,
113
- 'tools_called': [],
114
- 'plan_preview': None,
 
 
 
 
 
 
 
 
115
  }
116
 
117
  for line in logs.split('\n'):
 
118
  if '[EXPLORER]' in line:
119
  status['current_agent'] = 'Explorer'
120
  if 'Calling tool:' in line:
121
  tool = line.split('Calling tool:')[1].strip()
122
- status['tools_called'].append(f"Explorer: {tool}")
123
- elif '[PLANNER]' in line:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  status['current_agent'] = 'Planner'
 
125
  if 'Plan created' in line:
126
  status['planner_done'] = True
127
- elif '[CODER]' in line:
 
 
 
128
  status['current_agent'] = 'Coder'
129
  if 'Calling tool:' in line:
130
  tool = line.split('Calling tool:')[1].strip()
131
- status['tools_called'].append(f"Coder: {tool}")
 
 
 
 
 
 
132
  if 'Finished implementation' in line:
133
  status['coder_done'] = True
134
- elif '[REVIEWER]' in line:
 
 
 
135
  status['current_agent'] = 'Reviewer'
 
136
  if 'Calling tool:' in line:
137
  tool = line.split('Calling tool:')[1].strip()
138
- status['tools_called'].append(f"Reviewer: {tool}")
139
- elif 'APPROVED' in line:
 
 
 
 
 
140
  status['approved'] = True
141
  status['reviewer_done'] = True
 
142
  elif 'REJECTED' in line:
143
  status['approved'] = False
144
  status['reviewer_done'] = True
145
- elif 'Transitioning to CLARIFYING' in line:
 
 
 
146
  status['explorer_done'] = True
147
  elif 'Transitioning to PLANNING' in line:
148
  status['explorer_done'] = True
 
 
 
 
 
 
149
  elif 'Transitioning to CODING' in line:
150
  status['planner_done'] = True
151
  elif 'Transitioning to REVIEWING' in line:
@@ -157,7 +218,7 @@ def parse_agent_status(logs: str) -> dict:
157
 
158
 
159
  def format_progress_display(status: dict, total_cost: float) -> str:
160
- """Format a clean progress display."""
161
 
162
  def icon(done: bool, active: bool = False) -> str:
163
  if done:
@@ -167,21 +228,56 @@ def format_progress_display(status: dict, total_cost: float) -> str:
167
  else:
168
  return "⬜"
169
 
170
- current = status['current_agent']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
- lines = ["## Agent Progress\n"]
173
- lines.append("| Agent | Status |")
174
- lines.append("|-------|--------|")
175
- lines.append(f"| Explorer | {icon(status['explorer_done'], current == 'Explorer')} {'Searching codebase...' if current == 'Explorer' and not status['explorer_done'] else 'Done' if status['explorer_done'] else 'Waiting'} |")
176
- lines.append(f"| Planner | {icon(status['planner_done'], current == 'Planner')} {'Creating plan...' if current == 'Planner' and not status['planner_done'] else 'Done' if status['planner_done'] else 'Waiting'} |")
177
- lines.append(f"| Coder | {icon(status['coder_done'], current == 'Coder')} {'Writing code...' if current == 'Coder' and not status['coder_done'] else 'Done' if status['coder_done'] else 'Waiting'} |")
178
 
179
- reviewer_status = 'Waiting'
180
- if current == 'Reviewer' and not status['reviewer_done']:
181
- reviewer_status = 'Reviewing...'
182
- elif status['reviewer_done']:
183
- reviewer_status = '**APPROVED**' if status['approved'] else '**REJECTED**'
184
- lines.append(f"| Reviewer | {icon(status['reviewer_done'], current == 'Reviewer')} {reviewer_status} |")
 
 
 
185
 
186
  lines.append(f"\n**Cost:** ${total_cost:.4f}")
187
 
@@ -189,94 +285,161 @@ def format_progress_display(status: dict, total_cost: float) -> str:
189
 
190
 
191
  def format_final_result(result: dict, total_cost: float) -> str:
192
- """Format the final result with compact test table."""
193
- lines = []
194
 
195
- # Status header with cost inline
196
  success = result.get('success', False)
197
- status_icon = "βœ…" if success else "❌"
198
- lines.append(f"## {status_icon} {'Success' if success else 'Failed'} | Cost: ${total_cost:.4f}\n")
199
-
200
- # Compact results table
201
- lines.append("| Step | Status |")
 
 
 
202
  lines.append("|------|--------|")
203
 
204
- # Plan
205
- lines.append(f"| Plan | {'βœ…' if result.get('plan') else '❌'} |")
206
 
207
- # Code
208
- if result.get('code_changes'):
209
- lines.append(f"| Code | βœ… {len(result['code_changes'])} files |")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  else:
211
- lines.append("| Code | ❌ |")
212
 
213
- # Review
214
- if result.get('review_feedback'):
215
- lines.append(f"| Review | {'βœ… Approved' if success else '❌ Rejected'} |")
 
 
 
216
  else:
217
- lines.append("| Review | ⬜ |")
 
 
 
218
 
219
  return "\n".join(lines)
220
 
221
 
222
  def format_plan_display(plan: str) -> str:
223
- """Format a condensed version of the implementation plan."""
224
  if not plan:
225
  return ""
226
 
227
- lines = ["## Plan Summary\n"]
228
-
229
- # Extract just the overview and files from the plan
230
  plan_lines = plan.split('\n')
231
- in_overview = False
232
- in_files = False
233
- overview_text = []
234
- files_list = []
235
 
 
236
  for line in plan_lines:
237
- line_lower = line.lower().strip()
238
-
239
- # Detect sections
240
- if 'overview' in line_lower and ('#' in line or line_lower.startswith('overview')):
241
- in_overview = True
242
- in_files = False
243
- continue
244
- elif 'files to' in line_lower or 'files:' in line_lower:
245
- in_overview = False
246
- in_files = True
247
- continue
248
- elif line.startswith('#') or line.startswith('==='):
249
- in_overview = False
250
- in_files = False
251
- continue
252
-
253
- # Collect content
254
- if in_overview and line.strip():
255
- overview_text.append(line.strip())
256
- elif in_files and line.strip():
257
- # Extract file paths or names
258
- if '/' in line or '.py' in line or '.md' in line or '.html' in line:
259
- # Clean up the line to get just the filename
260
- clean_line = line.strip().lstrip('-').lstrip('*').lstrip('1234567890.').strip()
261
- if clean_line:
262
- files_list.append(clean_line)
263
-
264
- # Build condensed output
265
- if overview_text:
266
- lines.append(' '.join(overview_text[:2])) # First 2 sentences max
267
- lines.append("")
268
-
269
- if files_list:
270
- lines.append("**Files to create/modify:**")
271
- for f in files_list[:5]: # Max 5 files
272
- # Extract just filename from path
273
- filename = os.path.basename(f.split()[0]) if f else f
274
- lines.append(f"- `{filename}`")
275
- if len(files_list) > 5:
276
- lines.append(f"- ... and {len(files_list) - 5} more")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  else:
278
- # Fallback: just show first 3 lines of plan
279
- lines.append(plan_lines[0] if plan_lines else "Plan created.")
 
 
 
 
 
280
 
281
  lines.append("")
282
  return "\n".join(lines)
 
22
  # ============================================================
23
  # STARTUP VERSION CHECK - Change this to detect if rebuild worked
24
  # ============================================================
25
+ APP_VERSION = "3.6.0-detailed-progress"
26
+ BUILD_ID = "2024-12-20-v5"
27
  print("=" * 60)
28
  print(f"[STARTUP] CodePilot Chainlit App")
29
  print(f"[STARTUP] APP_VERSION: {APP_VERSION}")
 
102
 
103
 
104
  def parse_agent_status(logs: str) -> dict:
105
+ """Parse logs to extract agent status with specific activities."""
106
  status = {
107
  'current_agent': None,
108
  'explorer_done': False,
 
110
  'coder_done': False,
111
  'reviewer_done': False,
112
  'approved': None,
113
+ # Activity tracking
114
+ 'explorer_activity': None,
115
+ 'planner_activity': None,
116
+ 'coder_activity': None,
117
+ 'reviewer_activity': None,
118
+ # Specific counts
119
+ 'files_indexed': 0,
120
+ 'files_found': 0,
121
+ 'files_written': 0,
122
+ 'plan_steps': 0,
123
  }
124
 
125
  for line in logs.split('\n'):
126
+ # Explorer activity tracking
127
  if '[EXPLORER]' in line:
128
  status['current_agent'] = 'Explorer'
129
  if 'Calling tool:' in line:
130
  tool = line.split('Calling tool:')[1].strip()
131
+ if 'index' in tool.lower():
132
+ status['explorer_activity'] = 'Indexing codebase...'
133
+ elif 'search' in tool.lower():
134
+ status['explorer_activity'] = 'Searching for relevant files...'
135
+ elif 'read' in tool.lower():
136
+ status['explorer_activity'] = 'Reading source files...'
137
+ else:
138
+ status['explorer_activity'] = f'Running {tool}...'
139
+
140
+ # Count indexed files (look for indexing output)
141
+ if 'indexed' in line.lower() or 'indexing' in line.lower():
142
+ import re
143
+ match = re.search(r'(\d+)\s*files?', line.lower())
144
+ if match:
145
+ status['files_indexed'] = int(match.group(1))
146
+
147
+ # Count found/relevant files
148
+ if 'found' in line.lower() and 'file' in line.lower():
149
+ import re
150
+ match = re.search(r'found\s*(\d+)', line.lower())
151
+ if match:
152
+ status['files_found'] = int(match.group(1))
153
+
154
+ # Planner activity tracking
155
+ if '[PLANNER]' in line:
156
  status['current_agent'] = 'Planner'
157
+ status['planner_activity'] = 'Creating implementation plan...'
158
  if 'Plan created' in line:
159
  status['planner_done'] = True
160
+ status['planner_activity'] = 'Plan created'
161
+
162
+ # Coder activity tracking
163
+ if '[CODER]' in line:
164
  status['current_agent'] = 'Coder'
165
  if 'Calling tool:' in line:
166
  tool = line.split('Calling tool:')[1].strip()
167
+ if 'write' in tool.lower():
168
+ status['files_written'] += 1
169
+ status['coder_activity'] = f'Writing file #{status["files_written"]}...'
170
+ elif 'run' in tool.lower() or 'command' in tool.lower():
171
+ status['coder_activity'] = 'Running tests...'
172
+ else:
173
+ status['coder_activity'] = f'Running {tool}...'
174
  if 'Finished implementation' in line:
175
  status['coder_done'] = True
176
+ status['coder_activity'] = f'Wrote {status["files_written"]} files'
177
+
178
+ # Reviewer activity tracking
179
+ if '[REVIEWER]' in line:
180
  status['current_agent'] = 'Reviewer'
181
+ status['reviewer_activity'] = 'Reviewing code...'
182
  if 'Calling tool:' in line:
183
  tool = line.split('Calling tool:')[1].strip()
184
+ if 'read' in tool.lower():
185
+ status['reviewer_activity'] = 'Reading generated code...'
186
+ else:
187
+ status['reviewer_activity'] = 'Checking code quality...'
188
+
189
+ # Approval status
190
+ if 'APPROVED' in line:
191
  status['approved'] = True
192
  status['reviewer_done'] = True
193
+ status['reviewer_activity'] = 'Approved'
194
  elif 'REJECTED' in line:
195
  status['approved'] = False
196
  status['reviewer_done'] = True
197
+ status['reviewer_activity'] = 'Rejected'
198
+
199
+ # State transitions
200
+ if 'Transitioning to CLARIFYING' in line:
201
  status['explorer_done'] = True
202
  elif 'Transitioning to PLANNING' in line:
203
  status['explorer_done'] = True
204
+ if status['files_indexed'] > 0 or status['files_found'] > 0:
205
+ status['explorer_activity'] = f'Indexed {status["files_indexed"]} files'
206
+ if status['files_found'] > 0:
207
+ status['explorer_activity'] += f', found {status["files_found"]} relevant'
208
+ else:
209
+ status['explorer_activity'] = 'Analyzed codebase'
210
  elif 'Transitioning to CODING' in line:
211
  status['planner_done'] = True
212
  elif 'Transitioning to REVIEWING' in line:
 
218
 
219
 
220
  def format_progress_display(status: dict, total_cost: float) -> str:
221
+ """Format progress display with specific agent activities."""
222
 
223
  def icon(done: bool, active: bool = False) -> str:
224
  if done:
 
228
  else:
229
  return "⬜"
230
 
231
+ def get_activity(agent: str) -> str:
232
+ """Get activity text for an agent."""
233
+ current = status['current_agent']
234
+
235
+ if agent == 'Explorer':
236
+ if status['explorer_done']:
237
+ return status.get('explorer_activity') or 'Done'
238
+ elif current == 'Explorer':
239
+ return status.get('explorer_activity') or 'Analyzing codebase...'
240
+ return 'Waiting'
241
+
242
+ elif agent == 'Planner':
243
+ if status['planner_done']:
244
+ return status.get('planner_activity') or 'Plan created'
245
+ elif current == 'Planner':
246
+ return status.get('planner_activity') or 'Creating plan...'
247
+ return 'Waiting'
248
+
249
+ elif agent == 'Coder':
250
+ if status['coder_done']:
251
+ activity = status.get('coder_activity')
252
+ if activity:
253
+ return activity
254
+ files = status.get('files_written', 0)
255
+ return f'Wrote {files} files' if files else 'Done'
256
+ elif current == 'Coder':
257
+ return status.get('coder_activity') or 'Writing code...'
258
+ return 'Waiting'
259
+
260
+ elif agent == 'Reviewer':
261
+ if status['reviewer_done']:
262
+ if status['approved']:
263
+ return '**Approved**'
264
+ else:
265
+ return '**Rejected**'
266
+ elif current == 'Reviewer':
267
+ return status.get('reviewer_activity') or 'Reviewing...'
268
+ return 'Waiting'
269
 
270
+ return 'Waiting'
 
 
 
 
 
271
 
272
+ current = status['current_agent']
273
+
274
+ lines = ["## Progress\n"]
275
+ lines.append("| Agent | Activity |")
276
+ lines.append("|-------|----------|")
277
+ lines.append(f"| Explorer | {icon(status['explorer_done'], current == 'Explorer')} {get_activity('Explorer')} |")
278
+ lines.append(f"| Planner | {icon(status['planner_done'], current == 'Planner')} {get_activity('Planner')} |")
279
+ lines.append(f"| Coder | {icon(status['coder_done'], current == 'Coder')} {get_activity('Coder')} |")
280
+ lines.append(f"| Reviewer | {icon(status['reviewer_done'], current == 'Reviewer')} {get_activity('Reviewer')} |")
281
 
282
  lines.append(f"\n**Cost:** ${total_cost:.4f}")
283
 
 
285
 
286
 
287
  def format_final_result(result: dict, total_cost: float) -> str:
288
+ """Format final result with detailed test checks."""
289
+ lines = ["## Results\n"]
290
 
 
291
  success = result.get('success', False)
292
+ has_plan = bool(result.get('plan'))
293
+ code_changes = result.get('code_changes', {})
294
+ has_code = bool(code_changes)
295
+ file_count = len(code_changes) if code_changes else 0
296
+ review_feedback = result.get('review_feedback', '')
297
+
298
+ # Detailed checks table
299
+ lines.append("| Test | Status |")
300
  lines.append("|------|--------|")
301
 
302
+ # 1. Plan created
303
+ lines.append(f"| Plan created | {'βœ… Pass' if has_plan else '❌ Fail'} |")
304
 
305
+ # 2. Files written
306
+ if has_code:
307
+ lines.append(f"| Files written | βœ… Pass ({file_count} files) |")
308
+ else:
309
+ lines.append("| Files written | ❌ Fail |")
310
+
311
+ # 3. Valid syntax (infer from review - if approved, syntax is valid)
312
+ if success:
313
+ lines.append("| Valid syntax | βœ… Pass |")
314
+ elif has_code and review_feedback:
315
+ # Check if syntax error mentioned in feedback
316
+ if 'syntax' in review_feedback.lower() or 'error' in review_feedback.lower():
317
+ lines.append("| Valid syntax | ❌ Fail |")
318
+ else:
319
+ lines.append("| Valid syntax | βœ… Pass |")
320
+ elif has_code:
321
+ lines.append("| Valid syntax | ⬜ Pending |")
322
+ else:
323
+ lines.append("| Valid syntax | ⬜ N/A |")
324
+
325
+ # 4. Follows patterns (infer from approval)
326
+ if success:
327
+ lines.append("| Follows patterns | βœ… Pass |")
328
+ elif has_code and review_feedback:
329
+ if 'pattern' in review_feedback.lower() or 'convention' in review_feedback.lower():
330
+ lines.append("| Follows patterns | ❌ Fail |")
331
+ else:
332
+ lines.append("| Follows patterns | βœ… Pass |")
333
+ elif has_code:
334
+ lines.append("| Follows patterns | ⬜ Pending |")
335
+ else:
336
+ lines.append("| Follows patterns | ⬜ N/A |")
337
+
338
+ # 5. Matches requirements (infer from approval)
339
+ if success:
340
+ lines.append("| Matches requirements | βœ… Pass |")
341
+ elif has_code and review_feedback:
342
+ if 'requirement' in review_feedback.lower() or 'missing' in review_feedback.lower():
343
+ lines.append("| Matches requirements | ❌ Fail |")
344
+ else:
345
+ lines.append("| Matches requirements | βœ… Pass |")
346
+ elif has_code:
347
+ lines.append("| Matches requirements | ⬜ Pending |")
348
  else:
349
+ lines.append("| Matches requirements | ⬜ N/A |")
350
 
351
+ # 6. Code review
352
+ if review_feedback:
353
+ if success:
354
+ lines.append("| Code review | βœ… Approved |")
355
+ else:
356
+ lines.append("| Code review | ❌ Rejected |")
357
  else:
358
+ lines.append("| Code review | ⬜ Pending |")
359
+
360
+ # Cost at bottom
361
+ lines.append(f"\n**Cost:** ${total_cost:.4f}")
362
 
363
  return "\n".join(lines)
364
 
365
 
366
  def format_plan_display(plan: str) -> str:
367
+ """Format plan as numbered implementation steps (7-8 max)."""
368
  if not plan:
369
  return ""
370
 
371
+ lines = ["## Implementation Plan\n"]
 
 
372
  plan_lines = plan.split('\n')
373
+ steps = []
 
 
 
374
 
375
+ # Strategy 1: Look for existing numbered steps (1., 2., etc.)
376
  for line in plan_lines:
377
+ stripped = line.strip()
378
+ # Match numbered items like "1.", "1)", "1:"
379
+ if stripped and len(stripped) > 2:
380
+ import re
381
+ match = re.match(r'^(\d+)[.)\]:]\s*(.+)', stripped)
382
+ if match:
383
+ step_text = match.group(2).strip()
384
+ # Skip if it's just a file path or too short
385
+ if len(step_text) > 10 and not step_text.startswith('/'):
386
+ steps.append(step_text)
387
+
388
+ # Strategy 2: Look for bullet points if no numbered steps found
389
+ if len(steps) < 3:
390
+ steps = []
391
+ for line in plan_lines:
392
+ stripped = line.strip()
393
+ # Match bullet points
394
+ if stripped.startswith(('-', '*', 'β€’')) and len(stripped) > 5:
395
+ step_text = stripped.lstrip('-*β€’ ').strip()
396
+ # Skip headers, file paths, and very short items
397
+ if (len(step_text) > 15 and
398
+ not step_text.startswith('#') and
399
+ not step_text.startswith('/') and
400
+ ':' not in step_text[:5]): # Skip "Note:" etc.
401
+ steps.append(step_text)
402
+
403
+ # Strategy 3: Extract key sentences with action verbs
404
+ if len(steps) < 3:
405
+ steps = []
406
+ action_verbs = ['create', 'add', 'implement', 'write', 'update', 'modify',
407
+ 'define', 'set up', 'configure', 'import', 'export', 'build']
408
+ for line in plan_lines:
409
+ stripped = line.strip().lower()
410
+ for verb in action_verbs:
411
+ if verb in stripped and len(line.strip()) > 20:
412
+ # Clean up the line
413
+ clean = line.strip().lstrip('-*β€’ 0123456789.):]').strip()
414
+ if clean and clean not in steps:
415
+ steps.append(clean)
416
+ break
417
+
418
+ # Deduplicate and limit to 8 steps
419
+ seen = set()
420
+ unique_steps = []
421
+ for step in steps:
422
+ step_lower = step.lower()[:30] # Compare first 30 chars
423
+ if step_lower not in seen:
424
+ seen.add(step_lower)
425
+ unique_steps.append(step)
426
+ steps = unique_steps[:8]
427
+
428
+ # Format as numbered list
429
+ if steps:
430
+ for i, step in enumerate(steps, 1):
431
+ # Truncate long steps
432
+ if len(step) > 80:
433
+ step = step[:77] + '...'
434
+ lines.append(f"{i}. {step}")
435
  else:
436
+ # Fallback: show first meaningful line
437
+ for line in plan_lines:
438
+ if line.strip() and not line.startswith('#'):
439
+ lines.append(f"1. {line.strip()[:80]}")
440
+ break
441
+ if len(lines) == 1:
442
+ lines.append("1. Implementation plan created")
443
 
444
  lines.append("")
445
  return "\n".join(lines)