Siteshcodes commited on
Commit
8483903
Β·
1 Parent(s): 703aa57

v2.0 frontend: multi-step investigation UI with step tracker, progressive reveal, and reasoning bonus

Browse files
Files changed (1) hide show
  1. server/static/index.html +419 -548
server/static/index.html CHANGED
@@ -3,7 +3,8 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Bug Triage Environment β€” OpenEnv</title>
 
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
9
  <style>
@@ -44,532 +45,228 @@
44
  overflow-x: hidden;
45
  }
46
 
47
- /* ── Background Effects ─────────────────────────── */
48
  .bg-grid {
49
- position: fixed;
50
- inset: 0;
51
  background-image:
52
  linear-gradient(rgba(139, 92, 246, 0.03) 1px, transparent 1px),
53
  linear-gradient(90deg, rgba(139, 92, 246, 0.03) 1px, transparent 1px);
54
  background-size: 60px 60px;
55
- pointer-events: none;
56
- z-index: 0;
57
- }
58
- .bg-orb {
59
- position: fixed;
60
- border-radius: 50%;
61
- filter: blur(120px);
62
- pointer-events: none;
63
- z-index: 0;
64
  }
 
65
  .bg-orb-1 { width: 600px; height: 600px; top: -200px; right: -100px; background: rgba(139, 92, 246, 0.08); }
66
  .bg-orb-2 { width: 500px; height: 500px; bottom: -150px; left: -100px; background: rgba(6, 182, 212, 0.06); }
67
  .bg-orb-3 { width: 400px; height: 400px; top: 40%; left: 50%; transform: translateX(-50%); background: rgba(59, 130, 246, 0.04); }
68
 
69
- /* ── Layout ─────────────────────────────────────── */
70
- .container {
71
- max-width: 1100px;
72
- margin: 0 auto;
73
- padding: 0 24px;
74
- position: relative;
75
- z-index: 1;
76
- }
77
 
78
- /* ── Header ─────────────────────────────────────── */
79
- .header {
80
- padding: 48px 0 32px;
81
- text-align: center;
82
- }
83
  .header-badge {
84
- display: inline-flex;
85
- align-items: center;
86
- gap: 8px;
87
- padding: 6px 16px;
88
- background: rgba(139, 92, 246, 0.1);
89
- border: 1px solid rgba(139, 92, 246, 0.2);
90
- border-radius: 100px;
91
- font-size: 12px;
92
- font-weight: 500;
93
- color: var(--accent-purple);
94
- letter-spacing: 0.5px;
95
- text-transform: uppercase;
96
- margin-bottom: 20px;
97
  }
98
  .header-badge .dot {
99
- width: 6px; height: 6px;
100
- background: var(--accent-green);
101
- border-radius: 50%;
102
- animation: pulse-dot 2s ease-in-out infinite;
103
- }
104
- @keyframes pulse-dot {
105
- 0%, 100% { opacity: 1; }
106
- 50% { opacity: 0.3; }
107
- }
108
- .header h1 {
109
- font-size: 48px;
110
- font-weight: 800;
111
- letter-spacing: -1.5px;
112
- line-height: 1.1;
113
- margin-bottom: 12px;
114
  }
115
- .header h1 .emoji { font-size: 44px; margin-right: 4px; }
 
 
116
  .header h1 .gradient-text {
117
- background: var(--gradient-main);
118
- -webkit-background-clip: text;
119
- -webkit-text-fill-color: transparent;
120
- background-clip: text;
121
- }
122
- .header p {
123
- font-size: 17px;
124
- color: var(--text-secondary);
125
- max-width: 600px;
126
- margin: 0 auto;
127
  }
 
 
128
 
129
- /* ── Task Selector ──────────────────────────────── */
130
- .task-selector {
131
- display: grid;
132
- grid-template-columns: repeat(3, 1fr);
133
- gap: 14px;
134
- margin: 32px 0;
135
- }
136
  .task-card {
137
- position: relative;
138
- padding: 20px;
139
- background: var(--bg-card);
140
- border: 1px solid var(--border);
141
- border-radius: var(--radius);
142
- cursor: pointer;
143
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
144
- backdrop-filter: blur(12px);
145
- overflow: hidden;
146
- }
147
- .task-card::before {
148
- content: '';
149
- position: absolute;
150
- top: 0; left: 0; right: 0;
151
- height: 3px;
152
- opacity: 0;
153
- transition: opacity 0.3s;
154
  }
 
155
  .task-card[data-task="easy"]::before { background: var(--accent-green); }
156
  .task-card[data-task="medium"]::before { background: var(--accent-amber); }
157
  .task-card[data-task="hard"]::before { background: var(--accent-red); }
158
-
159
- .task-card:hover {
160
- background: var(--bg-card-hover);
161
- border-color: rgba(255, 255, 255, 0.1);
162
- transform: translateY(-2px);
163
- }
164
- .task-card.active {
165
- border-color: var(--border-active);
166
- background: var(--bg-card-hover);
167
- box-shadow: var(--shadow-glow);
168
- }
169
  .task-card.active::before { opacity: 1; }
170
-
171
- .task-difficulty {
172
- display: inline-block;
173
- padding: 3px 10px;
174
- border-radius: 100px;
175
- font-size: 11px;
176
- font-weight: 600;
177
- text-transform: uppercase;
178
- letter-spacing: 0.5px;
179
- margin-bottom: 10px;
180
- }
181
- .task-card[data-task="easy"] .task-difficulty { background: rgba(16, 185, 129, 0.1); color: var(--accent-green); }
182
- .task-card[data-task="medium"] .task-difficulty { background: rgba(245, 158, 11, 0.1); color: var(--accent-amber); }
183
- .task-card[data-task="hard"] .task-difficulty { background: rgba(239, 68, 68, 0.1); color: var(--accent-red); }
184
-
185
- .task-card h3 {
186
- font-size: 16px;
187
- font-weight: 600;
188
- margin-bottom: 6px;
189
- }
190
- .task-card p {
191
- font-size: 13px;
192
- color: var(--text-secondary);
193
- line-height: 1.5;
194
- }
195
- .task-grader {
196
- margin-top: 10px;
197
- font-family: 'JetBrains Mono', monospace;
198
- font-size: 11px;
199
- color: var(--text-muted);
200
- }
201
-
202
- /* ── Main Content ───────────────────────────────── */
203
- .content {
204
- display: grid;
205
- grid-template-columns: 1fr 1fr;
206
- gap: 20px;
207
- margin-bottom: 40px;
208
- }
209
-
210
- .panel {
211
- background: var(--bg-card);
212
- border: 1px solid var(--border);
213
- border-radius: var(--radius);
214
- backdrop-filter: blur(12px);
215
- overflow: hidden;
216
- }
217
  .panel-header {
218
- display: flex;
219
- align-items: center;
220
- justify-content: space-between;
221
- padding: 16px 20px;
222
- border-bottom: 1px solid var(--border);
223
- }
224
- .panel-header h2 {
225
- font-size: 14px;
226
- font-weight: 600;
227
- display: flex;
228
- align-items: center;
229
- gap: 8px;
230
  }
231
- .panel-body { padding: 20px; }
 
232
 
233
- /* ── Bug Report Panel ───────────────────────────── */
234
- .bug-placeholder {
235
- text-align: center;
236
- padding: 48px 20px;
237
- color: var(--text-muted);
238
- }
239
- .bug-placeholder .icon {
240
- font-size: 48px;
241
- margin-bottom: 12px;
242
- opacity: 0.5;
243
- }
244
  .bug-placeholder p { font-size: 14px; }
245
-
246
- .bug-title {
247
- font-size: 18px;
248
- font-weight: 700;
249
- margin-bottom: 4px;
250
- line-height: 1.3;
251
- }
252
- .bug-meta {
253
- font-size: 12px;
254
- color: var(--text-muted);
255
- margin-bottom: 16px;
256
- display: flex;
257
- gap: 12px;
258
- align-items: center;
259
- }
260
- .bug-meta .tag {
261
- padding: 2px 8px;
262
- background: rgba(139, 92, 246, 0.1);
263
- color: var(--accent-purple);
264
- border-radius: 4px;
265
- font-size: 11px;
266
- font-weight: 500;
267
- }
268
  .bug-body {
269
- font-size: 14px;
270
- color: var(--text-secondary);
271
- line-height: 1.7;
272
- margin-bottom: 16px;
273
- padding: 14px;
274
- background: rgba(0,0,0,0.2);
275
- border-radius: var(--radius-sm);
276
- border-left: 3px solid var(--accent-purple);
277
- }
278
- .bug-comments-title {
279
- font-size: 12px;
280
- font-weight: 600;
281
- color: var(--text-muted);
282
- text-transform: uppercase;
283
- letter-spacing: 0.5px;
284
- margin-bottom: 8px;
285
- }
286
  .bug-comment {
287
- padding: 10px 14px;
288
- background: rgba(0,0,0,0.15);
289
- border-radius: var(--radius-xs);
290
- font-size: 13px;
291
- color: var(--text-secondary);
292
- margin-bottom: 6px;
293
- border-left: 2px solid rgba(139, 92, 246, 0.3);
294
- }
295
- .bug-labels {
296
- display: flex;
297
- gap: 6px;
298
- flex-wrap: wrap;
299
- margin-top: 12px;
300
- }
301
- .bug-label {
302
- padding: 3px 10px;
303
- background: rgba(59, 130, 246, 0.1);
304
- color: var(--accent-blue);
305
- border-radius: 100px;
306
- font-size: 11px;
307
- font-weight: 500;
308
  }
 
 
 
309
 
310
- /* ── Triage Form Panel ──────────────────────────── */
311
- .form-group {
312
- margin-bottom: 16px;
313
- }
314
- .form-group label {
315
- display: block;
316
- font-size: 12px;
317
- font-weight: 600;
318
- color: var(--text-secondary);
319
- text-transform: uppercase;
320
- letter-spacing: 0.5px;
321
- margin-bottom: 6px;
322
- }
323
  .form-control {
324
- width: 100%;
325
- padding: 10px 14px;
326
- background: rgba(0, 0, 0, 0.3);
327
- border: 1px solid var(--border);
328
- border-radius: var(--radius-xs);
329
- color: var(--text-primary);
330
- font-family: 'Inter', sans-serif;
331
- font-size: 14px;
332
- transition: border-color 0.2s;
333
- outline: none;
334
- }
335
- .form-control:focus {
336
- border-color: var(--accent-purple);
337
- box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
338
  }
 
339
  .form-control option { background: var(--bg-secondary); }
340
- textarea.form-control {
341
- min-height: 60px;
342
- resize: vertical;
343
- }
344
 
345
- .checkbox-group {
346
- display: flex;
347
- flex-wrap: wrap;
348
- gap: 6px;
349
- }
350
  .label-chip {
351
- padding: 5px 12px;
352
- background: rgba(0, 0, 0, 0.3);
353
- border: 1px solid var(--border);
354
- border-radius: 100px;
355
- font-size: 12px;
356
- cursor: pointer;
357
- transition: all 0.2s;
358
- user-select: none;
359
- color: var(--text-secondary);
360
- }
361
- .label-chip:hover { border-color: rgba(255, 255, 255, 0.15); }
362
- .label-chip.selected {
363
- background: rgba(139, 92, 246, 0.15);
364
- border-color: var(--accent-purple);
365
- color: var(--accent-purple);
366
  }
 
 
367
 
368
- /* ── Buttons ────────────────────────────────────── */
369
  .btn {
370
- display: inline-flex;
371
- align-items: center;
372
- justify-content: center;
373
- gap: 8px;
374
- padding: 12px 24px;
375
- border: none;
376
- border-radius: var(--radius-sm);
377
- font-family: 'Inter', sans-serif;
378
- font-size: 14px;
379
- font-weight: 600;
380
- cursor: pointer;
381
- transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
382
- outline: none;
383
- }
384
- .btn-primary {
385
- background: var(--gradient-main);
386
- color: white;
387
- box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3);
388
- }
389
- .btn-primary:hover {
390
- transform: translateY(-1px);
391
- box-shadow: 0 6px 20px rgba(139, 92, 246, 0.4);
392
  }
 
 
393
  .btn-primary:active { transform: translateY(0); }
394
- .btn-primary:disabled {
395
- opacity: 0.4;
396
- cursor: not-allowed;
397
- transform: none;
398
- }
399
- .btn-secondary {
400
- background: rgba(255, 255, 255, 0.05);
401
- color: var(--text-secondary);
402
- border: 1px solid var(--border);
403
- }
404
- .btn-secondary:hover {
405
- background: rgba(255, 255, 255, 0.08);
406
- color: var(--text-primary);
407
- }
408
  .btn-full { width: 100%; }
409
 
410
- .action-bar {
411
- display: flex;
412
- gap: 10px;
413
- margin-top: 8px;
414
- }
415
- .action-bar .btn { flex: 1; }
416
-
417
- /* ── Results Panel ──────────────────────────────── */
418
- .results {
419
- margin-top: 20px;
420
- animation: slide-up 0.4s cubic-bezier(0.4, 0, 0.2, 1);
421
- }
422
- @keyframes slide-up {
423
- from { opacity: 0; transform: translateY(16px); }
424
- to { opacity: 1; transform: translateY(0); }
425
- }
426
-
427
- .score-display {
428
- text-align: center;
429
- padding: 28px 20px;
430
- background: var(--bg-card);
431
- border: 1px solid var(--border);
432
- border-radius: var(--radius);
433
- backdrop-filter: blur(12px);
434
- }
435
- .score-label {
436
- font-size: 12px;
437
- text-transform: uppercase;
438
- letter-spacing: 1px;
439
- color: var(--text-muted);
440
- margin-bottom: 8px;
441
- }
442
- .score-value {
443
- font-size: 56px;
444
- font-weight: 800;
445
- letter-spacing: -2px;
446
- margin-bottom: 4px;
447
- }
448
  .score-value.high { color: var(--accent-green); }
449
  .score-value.mid { color: var(--accent-amber); }
450
  .score-value.low { color: var(--accent-red); }
451
-
452
- .score-bar-wrap {
453
- width: 100%;
454
- height: 8px;
455
- background: rgba(255, 255, 255, 0.05);
456
- border-radius: 100px;
457
- overflow: hidden;
458
- margin: 12px 0;
459
- }
460
- .score-bar {
461
- height: 100%;
462
- border-radius: 100px;
463
- transition: width 1s cubic-bezier(0.4, 0, 0.2, 1);
464
- width: 0;
465
- }
466
  .score-bar.high { background: var(--gradient-cool); }
467
  .score-bar.mid { background: linear-gradient(90deg, var(--accent-amber), var(--accent-green)); }
468
  .score-bar.low { background: var(--gradient-warm); }
469
-
470
- .feedback-box {
471
- margin-top: 16px;
472
- padding: 14px 18px;
473
- background: rgba(0, 0, 0, 0.2);
474
- border-radius: var(--radius-sm);
475
- border: 1px solid var(--border);
476
- }
477
- .feedback-title {
478
- font-size: 11px;
479
- text-transform: uppercase;
480
- letter-spacing: 0.5px;
481
- color: var(--text-muted);
482
- margin-bottom: 8px;
483
- }
484
- .feedback-item {
485
- display: flex;
486
- align-items: center;
487
- gap: 8px;
488
- padding: 6px 0;
489
- font-size: 13px;
490
- color: var(--text-secondary);
491
- border-bottom: 1px solid rgba(255, 255, 255, 0.03);
492
- }
493
  .feedback-item:last-child { border-bottom: none; }
494
- .feedback-icon { font-size: 14px; }
495
-
496
- /* ── Loading ────────────────────────────────────── */
497
- .spinner {
498
- display: inline-block;
499
- width: 18px; height: 18px;
500
- border: 2px solid rgba(255, 255, 255, 0.15);
501
- border-top-color: var(--accent-purple);
502
- border-radius: 50%;
503
- animation: spin 0.7s linear infinite;
504
- }
505
  @keyframes spin { to { transform: rotate(360deg); } }
506
 
507
- /* ── How It Works ───────────────────────────────── */
508
- .how-section {
509
- margin: 48px 0;
510
- padding: 36px 0;
511
- border-top: 1px solid var(--border);
512
- }
513
- .how-section h2 {
514
- font-size: 24px;
515
- font-weight: 700;
516
- text-align: center;
517
- margin-bottom: 32px;
518
- }
519
- .how-steps {
520
- display: grid;
521
- grid-template-columns: repeat(4, 1fr);
522
- gap: 16px;
523
- }
524
- .how-step {
525
- text-align: center;
526
- padding: 24px 16px;
527
- background: var(--bg-card);
528
- border: 1px solid var(--border);
529
- border-radius: var(--radius);
530
- position: relative;
531
- }
532
- .how-step-num {
533
- width: 32px; height: 32px;
534
- background: var(--gradient-main);
535
- border-radius: 50%;
536
- display: flex;
537
- align-items: center;
538
- justify-content: center;
539
- font-size: 14px;
540
- font-weight: 700;
541
- margin: 0 auto 12px;
542
- }
543
- .how-step h3 { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
544
- .how-step p { font-size: 12px; color: var(--text-secondary); }
545
- .how-step .step-icon { font-size: 28px; margin-bottom: 10px; }
546
-
547
- /* ── Footer ─────────────────────────────────────── */
548
- .footer {
549
- text-align: center;
550
- padding: 32px 0;
551
- border-top: 1px solid var(--border);
552
- color: var(--text-muted);
553
- font-size: 13px;
554
- }
555
- .footer a {
556
- color: var(--accent-purple);
557
- text-decoration: none;
558
- }
559
  .footer a:hover { text-decoration: underline; }
560
- .footer-links {
561
- display: flex;
562
- justify-content: center;
563
- gap: 20px;
564
- margin-bottom: 12px;
565
- }
566
 
567
- /* ── Responsive ─────────────────────────────────── */
568
  @media (max-width: 768px) {
569
- .header h1 { font-size: 32px; }
570
  .task-selector { grid-template-columns: 1fr; }
571
  .content { grid-template-columns: 1fr; }
572
  .how-steps { grid-template-columns: repeat(2, 1fr); }
 
573
  }
574
  </style>
575
  </head>
@@ -580,17 +277,14 @@
580
  <div class="bg-orb bg-orb-3"></div>
581
 
582
  <div class="container">
583
- <!-- Header -->
584
  <header class="header">
585
- <div class="header-badge">
586
- <span class="dot"></span>
587
- OpenEnv Environment β€” Live
588
- </div>
589
  <h1>
590
  <span class="emoji">πŸ›</span>
591
  <span class="gradient-text">Bug Triage</span> Environment
 
592
  </h1>
593
- <p>Train and evaluate AI agents on real-world bug report triage β€” assigning priority, labels, team, and milestone.</p>
594
  </header>
595
 
596
  <!-- Task Selector -->
@@ -598,23 +292,44 @@
598
  <div class="task-card active" data-task="easy" onclick="selectTask('easy')">
599
  <span class="task-difficulty">Easy</span>
600
  <h3>Priority Assignment</h3>
601
- <p>Assign a P0–P3 priority to a bug report</p>
602
- <div class="task-grader">grader: priority_match</div>
603
  </div>
604
  <div class="task-card" data-task="medium" onclick="selectTask('medium')">
605
  <span class="task-difficulty">Medium</span>
606
  <h3>Priority + Labels + Team</h3>
607
- <p>Assign priority, labels, and route to correct team</p>
608
- <div class="task-grader">grader: priority_label_team</div>
609
  </div>
610
  <div class="task-card" data-task="hard" onclick="selectTask('hard')">
611
  <span class="task-difficulty">Hard</span>
612
  <h3>Full Triage</h3>
613
- <p>Complete triage with security escalation penalty</p>
614
- <div class="task-grader">grader: full_triage</div>
615
  </div>
616
  </div>
617
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  <!-- Main Content -->
619
  <div class="content">
620
  <!-- Bug Report Panel -->
@@ -622,13 +337,13 @@
622
  <div class="panel-header">
623
  <h2>πŸ“‹ Bug Report</h2>
624
  <button class="btn btn-secondary" onclick="fetchBug()" id="btn-reset" style="padding: 6px 14px; font-size: 12px;">
625
- Get New Bug
626
  </button>
627
  </div>
628
  <div class="panel-body" id="bug-area">
629
  <div class="bug-placeholder">
630
  <div class="icon">πŸ›</div>
631
- <p>Click <strong>"Get New Bug"</strong> to fetch a bug report</p>
632
  </div>
633
  </div>
634
  </div>
@@ -658,6 +373,7 @@
658
  <span class="label-chip" data-label="data-integrity" onclick="toggleLabel(this)">data-integrity</span>
659
  <span class="label-chip" data-label="payments" onclick="toggleLabel(this)">payments</span>
660
  <span class="label-chip" data-label="documentation" onclick="toggleLabel(this)">documentation</span>
 
661
  </div>
662
  </div>
663
  <div class="form-group">
@@ -679,11 +395,11 @@
679
  </select>
680
  </div>
681
  <div class="form-group">
682
- <label>Reasoning</label>
683
- <textarea class="form-control" id="reasoning" placeholder="Why did you choose this triage?"></textarea>
684
  </div>
685
  <button class="btn btn-primary btn-full" id="btn-submit" onclick="submitTriage()" disabled>
686
- Submit Triage
687
  </button>
688
  </div>
689
  </div>
@@ -692,65 +408,106 @@
692
  <!-- Results -->
693
  <div id="results-area"></div>
694
 
695
- <!-- How It Works -->
696
  <div class="how-section">
697
- <h2>How It Works</h2>
698
  <div class="how-steps">
699
  <div class="how-step">
700
  <div class="step-icon">πŸ“‘</div>
701
  <div class="how-step-num">1</div>
702
  <h3>Reset</h3>
703
- <p>Agent calls <code>/reset</code> to receive a bug report</p>
704
  </div>
705
  <div class="how-step">
706
- <div class="step-icon">πŸ”</div>
707
  <div class="how-step-num">2</div>
708
- <h3>Observe</h3>
709
- <p>Read title, body, comments, and labels</p>
710
  </div>
711
  <div class="how-step">
712
- <div class="step-icon">🎯</div>
713
  <div class="how-step-num">3</div>
714
  <h3>Decide</h3>
715
- <p>Choose priority, labels, team, and milestone</p>
716
  </div>
717
  <div class="how-step">
718
- <div class="step-icon">πŸ“Š</div>
719
  <div class="how-step-num">4</div>
 
 
 
 
 
 
720
  <h3>Score</h3>
721
- <p>Grader scores the triage decision (0–1)</p>
722
  </div>
723
  </div>
724
  </div>
725
 
726
- <!-- Footer -->
727
  <footer class="footer">
728
  <div class="footer-links">
729
  <a href="https://github.com/Siteshcodes/bug-triage-env" target="_blank">GitHub</a>
730
  <a href="/docs" target="_blank">API Docs</a>
731
  <a href="/tasks" target="_blank">Tasks</a>
 
732
  </div>
733
- <p>Bug Triage Environment β€” Meta PyTorch Hackathon Γ— Scaler School of Technology</p>
734
  </footer>
735
  </div>
736
 
737
  <script>
738
  let currentTask = 'easy';
 
739
  let hasBug = false;
 
 
 
 
 
740
 
741
  function selectTask(task) {
742
  currentTask = task;
 
743
  document.querySelectorAll('.task-card').forEach(c => c.classList.remove('active'));
744
  document.querySelector(`.task-card[data-task="${task}"]`).classList.add('active');
745
- hasBug = false;
 
 
 
 
746
  document.getElementById('btn-submit').disabled = true;
 
 
747
  document.getElementById('bug-area').innerHTML = `
748
  <div class="bug-placeholder">
749
  <div class="icon">πŸ›</div>
750
- <p>Click <strong>"Get New Bug"</strong> to fetch a <strong>${task}</strong> bug report</p>
751
- </div>
752
- `;
753
  document.getElementById('results-area').innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  }
755
 
756
  async function fetchBug() {
@@ -765,51 +522,160 @@
765
  body: JSON.stringify({ task_id: currentTask })
766
  });
767
  const data = await resp.json();
 
768
  const obs = data.observation || data;
769
  const bug = obs.bug_report;
 
 
770
 
771
  let html = `
772
- <div class="bug-title">${escHtml(bug.title)}</div>
773
  <div class="bug-meta">
774
- <span>by <strong>${escHtml(bug.author)}</strong></span>
775
- <span class="tag">${escHtml(bug.id)}</span>
 
 
 
776
  </div>
777
- <div class="bug-body">${escHtml(bug.body)}</div>
778
- `;
779
 
780
  if (bug.labels_hint && bug.labels_hint.length > 0) {
781
  html += `<div class="bug-labels">${bug.labels_hint.map(l =>
782
- `<span class="bug-label">${escHtml(l)}</span>`
783
- ).join('')}</div>`;
784
  }
785
 
786
- if (bug.comments && bug.comments.length > 0) {
787
- html += `<div class="bug-comments-title" style="margin-top:16px">Comments (${bug.comments.length})</div>`;
788
- html += bug.comments.map(c => `<div class="bug-comment">${escHtml(c)}</div>`).join('');
789
- }
790
 
791
  document.getElementById('bug-area').innerHTML = html;
792
  document.getElementById('btn-submit').disabled = false;
 
793
  document.getElementById('results-area').innerHTML = '';
794
  hasBug = true;
 
 
 
 
 
 
 
 
 
795
  } catch (err) {
796
  document.getElementById('bug-area').innerHTML = `
797
  <div class="bug-placeholder">
798
  <div class="icon">⚠️</div>
799
- <p>Error: ${escHtml(err.message)}</p>
800
- </div>
801
- `;
802
  }
803
-
804
- btn.innerHTML = 'Get New Bug';
805
  btn.disabled = false;
806
  }
807
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
  async function submitTriage() {
809
- if (!hasBug) return;
810
 
811
  const btn = document.getElementById('btn-submit');
812
- btn.innerHTML = '<span class="spinner"></span> Scoring...';
813
  btn.disabled = true;
814
 
815
  const selectedLabels = [];
@@ -818,6 +684,7 @@
818
  });
819
 
820
  const action = {
 
821
  priority: document.getElementById('priority').value,
822
  labels: selectedLabels,
823
  assigned_team: document.getElementById('team').value,
@@ -826,29 +693,38 @@
826
  };
827
 
828
  try {
 
 
 
829
  const resp = await fetch('/step', {
830
  method: 'POST',
831
  headers: { 'Content-Type': 'application/json' },
832
- body: JSON.stringify({ action })
833
  });
834
  const data = await resp.json();
835
  const reward = data.reward || 0;
836
  const obs = data.observation || {};
837
  const feedback = obs.feedback || '';
 
838
 
 
839
  showResults(reward, feedback);
 
840
  hasBug = false;
 
 
 
 
 
841
  } catch (err) {
842
  document.getElementById('results-area').innerHTML = `
843
  <div class="score-display">
844
  <div class="score-label">Error</div>
845
  <div class="score-value low">!</div>
846
- <p style="color: var(--text-secondary)">${escHtml(err.message)}</p>
847
- </div>
848
- `;
849
  }
850
-
851
- btn.innerHTML = 'Submit Triage';
852
  btn.disabled = true;
853
  }
854
 
@@ -858,7 +734,6 @@
858
  const emoji = reward >= 0.7 ? 'πŸŽ‰' : reward >= 0.4 ? 'πŸ‘' : '😬';
859
 
860
  const feedbackParts = feedback.split(' | ').filter(Boolean);
861
-
862
  let feedbackHtml = '';
863
  if (feedbackParts.length > 0) {
864
  feedbackHtml = `
@@ -866,49 +741,45 @@
866
  <div class="feedback-title">Grader Feedback</div>
867
  ${feedbackParts.map(part => {
868
  let icon = 'πŸ“';
869
- if (part.includes('βœ“') || part.includes('0.95')) icon = 'βœ…';
870
- else if (part.includes('βœ—') || part.includes('0.05')) icon = '❌';
871
- else if (part.includes('~') || part.includes('0.50')) icon = 'πŸ”Ά';
872
- else if (part.includes('⚠')) icon = '⚠️';
873
- return `<div class="feedback-item"><span class="feedback-icon">${icon}</span>${escHtml(part)}</div>`;
 
874
  }).join('')}
875
- </div>
876
- `;
877
  }
878
 
879
  document.getElementById('results-area').innerHTML = `
880
  <div class="results">
881
  <div class="score-display">
882
- <div class="score-label">Reward Score</div>
883
  <div class="score-value ${cls}">${emoji} ${reward.toFixed(3)}</div>
884
- <div class="score-bar-wrap">
885
- <div class="score-bar ${cls}" id="score-bar"></div>
886
- </div>
887
- <p style="color: var(--text-secondary); font-size: 13px;">
888
- ${pct}% of maximum score on <strong>${currentTask}</strong> task
889
  </p>
890
  ${feedbackHtml}
891
- <div style="margin-top: 16px;">
892
- <button class="btn btn-secondary" onclick="fetchBug()" style="padding: 8px 20px; font-size: 13px;">
893
  Try Another Bug β†’
894
  </button>
895
  </div>
896
  </div>
897
- </div>
898
- `;
899
 
900
  requestAnimationFrame(() => {
901
  requestAnimationFrame(() => {
902
- document.getElementById('score-bar').style.width = pct + '%';
 
903
  });
904
  });
905
  }
906
 
907
- function toggleLabel(el) {
908
- el.classList.toggle('selected');
909
- }
910
 
911
- function escHtml(str) {
912
  if (!str) return '';
913
  const div = document.createElement('div');
914
  div.textContent = str;
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bug Triage Environment v2.0 β€” OpenEnv</title>
7
+ <meta name="description" content="Multi-step RL environment for AI bug triage β€” investigate, decide, and learn.">
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
10
  <style>
 
45
  overflow-x: hidden;
46
  }
47
 
 
48
  .bg-grid {
49
+ position: fixed; inset: 0;
 
50
  background-image:
51
  linear-gradient(rgba(139, 92, 246, 0.03) 1px, transparent 1px),
52
  linear-gradient(90deg, rgba(139, 92, 246, 0.03) 1px, transparent 1px);
53
  background-size: 60px 60px;
54
+ pointer-events: none; z-index: 0;
 
 
 
 
 
 
 
 
55
  }
56
+ .bg-orb { position: fixed; border-radius: 50%; filter: blur(120px); pointer-events: none; z-index: 0; }
57
  .bg-orb-1 { width: 600px; height: 600px; top: -200px; right: -100px; background: rgba(139, 92, 246, 0.08); }
58
  .bg-orb-2 { width: 500px; height: 500px; bottom: -150px; left: -100px; background: rgba(6, 182, 212, 0.06); }
59
  .bg-orb-3 { width: 400px; height: 400px; top: 40%; left: 50%; transform: translateX(-50%); background: rgba(59, 130, 246, 0.04); }
60
 
61
+ .container { max-width: 1100px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 1; }
 
 
 
 
 
 
 
62
 
63
+ /* ── Header ─────────────────────── */
64
+ .header { padding: 40px 0 28px; text-align: center; }
 
 
 
65
  .header-badge {
66
+ display: inline-flex; align-items: center; gap: 8px;
67
+ padding: 6px 16px; background: rgba(139, 92, 246, 0.1);
68
+ border: 1px solid rgba(139, 92, 246, 0.2); border-radius: 100px;
69
+ font-size: 12px; font-weight: 500; color: var(--accent-purple);
70
+ letter-spacing: 0.5px; text-transform: uppercase; margin-bottom: 16px;
 
 
 
 
 
 
 
 
71
  }
72
  .header-badge .dot {
73
+ width: 6px; height: 6px; background: var(--accent-green);
74
+ border-radius: 50%; animation: pulse-dot 2s ease-in-out infinite;
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
+ @keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
77
+ .header h1 { font-size: 44px; font-weight: 800; letter-spacing: -1.5px; line-height: 1.1; margin-bottom: 10px; }
78
+ .header h1 .emoji { font-size: 40px; margin-right: 4px; }
79
  .header h1 .gradient-text {
80
+ background: var(--gradient-main); -webkit-background-clip: text;
81
+ -webkit-text-fill-color: transparent; background-clip: text;
 
 
 
 
 
 
 
 
82
  }
83
+ .header p { font-size: 16px; color: var(--text-secondary); max-width: 640px; margin: 0 auto; }
84
+ .version-tag { font-size: 11px; background: rgba(16,185,129,0.12); color: var(--accent-green); padding: 2px 8px; border-radius: 4px; font-weight: 600; }
85
 
86
+ /* ── Task Selector ──────────────── */
87
+ .task-selector { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 24px 0; }
 
 
 
 
 
88
  .task-card {
89
+ position: relative; padding: 18px; background: var(--bg-card);
90
+ border: 1px solid var(--border); border-radius: var(--radius);
91
+ cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
92
+ backdrop-filter: blur(12px); overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  }
94
+ .task-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; opacity: 0; transition: opacity 0.3s; }
95
  .task-card[data-task="easy"]::before { background: var(--accent-green); }
96
  .task-card[data-task="medium"]::before { background: var(--accent-amber); }
97
  .task-card[data-task="hard"]::before { background: var(--accent-red); }
98
+ .task-card:hover { background: var(--bg-card-hover); border-color: rgba(255,255,255,0.1); transform: translateY(-2px); }
99
+ .task-card.active { border-color: var(--border-active); background: var(--bg-card-hover); box-shadow: var(--shadow-glow); }
 
 
 
 
 
 
 
 
 
100
  .task-card.active::before { opacity: 1; }
101
+ .task-difficulty { display: inline-block; padding: 3px 10px; border-radius: 100px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }
102
+ .task-card[data-task="easy"] .task-difficulty { background: rgba(16,185,129,0.1); color: var(--accent-green); }
103
+ .task-card[data-task="medium"] .task-difficulty { background: rgba(245,158,11,0.1); color: var(--accent-amber); }
104
+ .task-card[data-task="hard"] .task-difficulty { background: rgba(239,68,68,0.1); color: var(--accent-red); }
105
+ .task-card h3 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
106
+ .task-card p { font-size: 12px; color: var(--text-secondary); line-height: 1.4; }
107
+ .task-steps { margin-top: 8px; font-size: 11px; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; }
108
+
109
+ /* ── Step Tracker ───────────────── */
110
+ .step-tracker {
111
+ display: flex; align-items: center; gap: 4px;
112
+ padding: 12px 20px; background: var(--bg-card); border: 1px solid var(--border);
113
+ border-radius: var(--radius); margin-bottom: 16px; backdrop-filter: blur(12px);
114
+ }
115
+ .step-dot {
116
+ width: 28px; height: 28px; border-radius: 50%; display: flex;
117
+ align-items: center; justify-content: center; font-size: 11px;
118
+ font-weight: 600; transition: all 0.3s;
119
+ }
120
+ .step-dot.pending { background: rgba(255,255,255,0.05); color: var(--text-muted); border: 1px solid var(--border); }
121
+ .step-dot.active { background: var(--accent-purple); color: white; border: 1px solid var(--accent-purple); box-shadow: 0 0 12px rgba(139,92,246,0.4); }
122
+ .step-dot.done { background: rgba(16,185,129,0.15); color: var(--accent-green); border: 1px solid rgba(16,185,129,0.3); }
123
+ .step-line { flex: 1; height: 2px; background: var(--border); }
124
+ .step-line.done { background: rgba(16,185,129,0.3); }
125
+ .step-label { margin-left: auto; font-size: 12px; color: var(--text-muted); font-weight: 500; }
126
+
127
+ /* ── Investigation Bar ──────────── */
128
+ .investigate-bar {
129
+ display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;
130
+ }
131
+ .investigate-btn {
132
+ display: inline-flex; align-items: center; gap: 6px;
133
+ padding: 10px 16px; background: rgba(255,255,255,0.04);
134
+ border: 1px solid var(--border); border-radius: var(--radius-sm);
135
+ color: var(--text-secondary); font-family: 'Inter', sans-serif; font-size: 13px;
136
+ font-weight: 500; cursor: pointer; transition: all 0.25s;
137
+ }
138
+ .investigate-btn:hover:not(:disabled) { background: rgba(139,92,246,0.1); border-color: rgba(139,92,246,0.3); color: var(--text-primary); }
139
+ .investigate-btn:disabled { opacity: 0.3; cursor: not-allowed; }
140
+ .investigate-btn.revealed { background: rgba(16,185,129,0.08); border-color: rgba(16,185,129,0.25); color: var(--accent-green); }
141
+ .investigate-btn .emoji { font-size: 16px; }
142
+
143
+ /* ── Content Panels ─────────────── */
144
+ .content { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 32px; }
145
+ .panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); backdrop-filter: blur(12px); overflow: hidden; }
 
 
146
  .panel-header {
147
+ display: flex; align-items: center; justify-content: space-between;
148
+ padding: 14px 20px; border-bottom: 1px solid var(--border);
 
 
 
 
 
 
 
 
 
 
149
  }
150
+ .panel-header h2 { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
151
+ .panel-body { padding: 18px; }
152
 
153
+ /* ── Bug Report ─────────────────── */
154
+ .bug-placeholder { text-align: center; padding: 48px 20px; color: var(--text-muted); }
155
+ .bug-placeholder .icon { font-size: 48px; margin-bottom: 12px; opacity: 0.5; }
 
 
 
 
 
 
 
 
156
  .bug-placeholder p { font-size: 14px; }
157
+ .bug-title { font-size: 17px; font-weight: 700; margin-bottom: 4px; line-height: 1.3; }
158
+ .bug-meta { font-size: 12px; color: var(--text-muted); margin-bottom: 14px; display: flex; gap: 10px; align-items: center; }
159
+ .bug-meta .tag { padding: 2px 8px; background: rgba(139,92,246,0.1); color: var(--accent-purple); border-radius: 4px; font-size: 11px; font-weight: 500; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  .bug-body {
161
+ font-size: 14px; color: var(--text-secondary); line-height: 1.7;
162
+ margin-bottom: 14px; padding: 12px 14px; background: rgba(0,0,0,0.2);
163
+ border-radius: var(--radius-sm); border-left: 3px solid var(--accent-purple);
164
+ }
165
+ .bug-body.truncated { position: relative; }
166
+ .bug-body.truncated::after {
167
+ content: 'πŸ‘ Use "Read Full Body" to reveal...';
168
+ display: block; margin-top: 8px; font-size: 11px; color: var(--accent-amber);
169
+ font-style: italic;
170
+ }
171
+ .bug-hidden-section {
172
+ margin-top: 12px; padding: 10px 14px; background: rgba(0,0,0,0.15);
173
+ border-radius: var(--radius-xs); border: 1px dashed rgba(255,255,255,0.06);
174
+ animation: reveal 0.4s ease;
175
+ }
176
+ @keyframes reveal { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
177
+ .bug-hidden-section .section-title { font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
178
  .bug-comment {
179
+ padding: 8px 12px; background: rgba(0,0,0,0.15); border-radius: var(--radius-xs);
180
+ font-size: 13px; color: var(--text-secondary); margin-bottom: 5px;
181
+ border-left: 2px solid rgba(139,92,246,0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  }
183
+ .bug-labels { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
184
+ .bug-label { padding: 3px 10px; background: rgba(59,130,246,0.1); color: var(--accent-blue); border-radius: 100px; font-size: 11px; font-weight: 500; }
185
+ .hidden-placeholder { padding: 16px; text-align: center; color: var(--text-muted); font-size: 12px; font-style: italic; border: 1px dashed rgba(255,255,255,0.06); border-radius: var(--radius-xs); }
186
 
187
+ /* ── Triage Form ────────────────── */
188
+ .form-group { margin-bottom: 14px; }
189
+ .form-group label { display: block; font-size: 12px; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 5px; }
 
 
 
 
 
 
 
 
 
 
190
  .form-control {
191
+ width: 100%; padding: 10px 14px; background: rgba(0,0,0,0.3);
192
+ border: 1px solid var(--border); border-radius: var(--radius-xs);
193
+ color: var(--text-primary); font-family: 'Inter', sans-serif; font-size: 14px;
194
+ transition: border-color 0.2s; outline: none;
 
 
 
 
 
 
 
 
 
 
195
  }
196
+ .form-control:focus { border-color: var(--accent-purple); box-shadow: 0 0 0 3px rgba(139,92,246,0.1); }
197
  .form-control option { background: var(--bg-secondary); }
198
+ textarea.form-control { min-height: 56px; resize: vertical; }
 
 
 
199
 
200
+ .checkbox-group { display: flex; flex-wrap: wrap; gap: 6px; }
 
 
 
 
201
  .label-chip {
202
+ padding: 5px 12px; background: rgba(0,0,0,0.3); border: 1px solid var(--border);
203
+ border-radius: 100px; font-size: 12px; cursor: pointer; transition: all 0.2s;
204
+ user-select: none; color: var(--text-secondary);
 
 
 
 
 
 
 
 
 
 
 
 
205
  }
206
+ .label-chip:hover { border-color: rgba(255,255,255,0.15); }
207
+ .label-chip.selected { background: rgba(139,92,246,0.15); border-color: var(--accent-purple); color: var(--accent-purple); }
208
 
209
+ /* ── Buttons ────────────────────── */
210
  .btn {
211
+ display: inline-flex; align-items: center; justify-content: center; gap: 8px;
212
+ padding: 11px 22px; border: none; border-radius: var(--radius-sm);
213
+ font-family: 'Inter', sans-serif; font-size: 14px; font-weight: 600;
214
+ cursor: pointer; transition: all 0.25s cubic-bezier(0.4,0,0.2,1); outline: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
+ .btn-primary { background: var(--gradient-main); color: white; box-shadow: 0 4px 15px rgba(139,92,246,0.3); }
217
+ .btn-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(139,92,246,0.4); }
218
  .btn-primary:active { transform: translateY(0); }
219
+ .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }
220
+ .btn-secondary { background: rgba(255,255,255,0.05); color: var(--text-secondary); border: 1px solid var(--border); }
221
+ .btn-secondary:hover { background: rgba(255,255,255,0.08); color: var(--text-primary); }
 
 
 
 
 
 
 
 
 
 
 
222
  .btn-full { width: 100%; }
223
 
224
+ /* ── Results ────────────────────── */
225
+ .results { margin-top: 16px; animation: slide-up 0.4s cubic-bezier(0.4,0,0.2,1); }
226
+ @keyframes slide-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
227
+ .score-display { text-align: center; padding: 24px 20px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); backdrop-filter: blur(12px); }
228
+ .score-label { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); margin-bottom: 6px; }
229
+ .score-value { font-size: 52px; font-weight: 800; letter-spacing: -2px; margin-bottom: 4px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  .score-value.high { color: var(--accent-green); }
231
  .score-value.mid { color: var(--accent-amber); }
232
  .score-value.low { color: var(--accent-red); }
233
+ .score-bar-wrap { width: 100%; height: 8px; background: rgba(255,255,255,0.05); border-radius: 100px; overflow: hidden; margin: 10px 0; }
234
+ .score-bar { height: 100%; border-radius: 100px; transition: width 1s cubic-bezier(0.4,0,0.2,1); width: 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  .score-bar.high { background: var(--gradient-cool); }
236
  .score-bar.mid { background: linear-gradient(90deg, var(--accent-amber), var(--accent-green)); }
237
  .score-bar.low { background: var(--gradient-warm); }
238
+ .feedback-box { margin-top: 14px; padding: 12px 16px; background: rgba(0,0,0,0.2); border-radius: var(--radius-sm); border: 1px solid var(--border); text-align: left; }
239
+ .feedback-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 6px; }
240
+ .feedback-item { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 13px; color: var(--text-secondary); border-bottom: 1px solid rgba(255,255,255,0.03); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  .feedback-item:last-child { border-bottom: none; }
242
+
243
+ /* ── Spinner ────────────────────── */
244
+ .spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.15); border-top-color: var(--accent-purple); border-radius: 50%; animation: spin 0.7s linear infinite; }
 
 
 
 
 
 
 
 
245
  @keyframes spin { to { transform: rotate(360deg); } }
246
 
247
+ /* ── How It Works ───────────────── */
248
+ .how-section { margin: 40px 0; padding: 32px 0; border-top: 1px solid var(--border); }
249
+ .how-section h2 { font-size: 22px; font-weight: 700; text-align: center; margin-bottom: 28px; }
250
+ .how-steps { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
251
+ .how-step { text-align: center; padding: 20px 12px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); }
252
+ .how-step-num { width: 28px; height: 28px; background: var(--gradient-main); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin: 0 auto 10px; }
253
+ .how-step h3 { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
254
+ .how-step p { font-size: 11px; color: var(--text-secondary); }
255
+ .how-step .step-icon { font-size: 24px; margin-bottom: 8px; }
256
+
257
+ /* ── Footer ─────────────────────── */
258
+ .footer { text-align: center; padding: 28px 0; border-top: 1px solid var(--border); color: var(--text-muted); font-size: 13px; }
259
+ .footer a { color: var(--accent-purple); text-decoration: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  .footer a:hover { text-decoration: underline; }
261
+ .footer-links { display: flex; justify-content: center; gap: 20px; margin-bottom: 10px; }
 
 
 
 
 
262
 
263
+ /* ── Responsive ─────────────────── */
264
  @media (max-width: 768px) {
265
+ .header h1 { font-size: 30px; }
266
  .task-selector { grid-template-columns: 1fr; }
267
  .content { grid-template-columns: 1fr; }
268
  .how-steps { grid-template-columns: repeat(2, 1fr); }
269
+ .investigate-bar { flex-direction: column; }
270
  }
271
  </style>
272
  </head>
 
277
  <div class="bg-orb bg-orb-3"></div>
278
 
279
  <div class="container">
 
280
  <header class="header">
281
+ <div class="header-badge"><span class="dot"></span> OpenEnv Environment β€” Live</div>
 
 
 
282
  <h1>
283
  <span class="emoji">πŸ›</span>
284
  <span class="gradient-text">Bug Triage</span> Environment
285
+ <span class="version-tag">v2.0</span>
286
  </h1>
287
+ <p>Multi-step RL environment β€” investigate bug reports, gather evidence, then triage. Your agent must learn <em>when</em> it knows enough to decide.</p>
288
  </header>
289
 
290
  <!-- Task Selector -->
 
292
  <div class="task-card active" data-task="easy" onclick="selectTask('easy')">
293
  <span class="task-difficulty">Easy</span>
294
  <h3>Priority Assignment</h3>
295
+ <p>Investigate and assign P0–P3 priority</p>
296
+ <div class="task-steps">4 steps max Β· priority only</div>
297
  </div>
298
  <div class="task-card" data-task="medium" onclick="selectTask('medium')">
299
  <span class="task-difficulty">Medium</span>
300
  <h3>Priority + Labels + Team</h3>
301
+ <p>Investigate and assign priority, labels, team</p>
302
+ <div class="task-steps">5 steps max Β· weighted scoring</div>
303
  </div>
304
  <div class="task-card" data-task="hard" onclick="selectTask('hard')">
305
  <span class="task-difficulty">Hard</span>
306
  <h3>Full Triage</h3>
307
+ <p>Complete triage with security penalty</p>
308
+ <div class="task-steps">6 steps max Β· security penalty</div>
309
  </div>
310
  </div>
311
 
312
+ <!-- Step Tracker -->
313
+ <div class="step-tracker" id="step-tracker" style="display:none;">
314
+ <div class="step-label" id="step-label">Step 0 / 4</div>
315
+ </div>
316
+
317
+ <!-- Investigation Bar -->
318
+ <div class="investigate-bar" id="investigate-bar" style="display:none;">
319
+ <button class="investigate-btn" id="btn-read-body" onclick="investigate('read_body')">
320
+ <span class="emoji">πŸ“–</span> Read Full Body
321
+ </button>
322
+ <button class="investigate-btn" id="btn-read-comments" onclick="investigate('read_comments')">
323
+ <span class="emoji">πŸ’¬</span> Read Comments
324
+ </button>
325
+ <button class="investigate-btn" id="btn-check-logs" onclick="investigate('check_logs')">
326
+ <span class="emoji">πŸ“Š</span> Check Logs
327
+ </button>
328
+ <button class="investigate-btn" id="btn-check-similar" onclick="investigate('check_similar')">
329
+ <span class="emoji">πŸ”—</span> Similar Bugs
330
+ </button>
331
+ </div>
332
+
333
  <!-- Main Content -->
334
  <div class="content">
335
  <!-- Bug Report Panel -->
 
337
  <div class="panel-header">
338
  <h2>πŸ“‹ Bug Report</h2>
339
  <button class="btn btn-secondary" onclick="fetchBug()" id="btn-reset" style="padding: 6px 14px; font-size: 12px;">
340
+ Start Episode
341
  </button>
342
  </div>
343
  <div class="panel-body" id="bug-area">
344
  <div class="bug-placeholder">
345
  <div class="icon">πŸ›</div>
346
+ <p>Click <strong>"Start Episode"</strong> to begin investigating a bug</p>
347
  </div>
348
  </div>
349
  </div>
 
373
  <span class="label-chip" data-label="data-integrity" onclick="toggleLabel(this)">data-integrity</span>
374
  <span class="label-chip" data-label="payments" onclick="toggleLabel(this)">payments</span>
375
  <span class="label-chip" data-label="documentation" onclick="toggleLabel(this)">documentation</span>
376
+ <span class="label-chip" data-label="api" onclick="toggleLabel(this)">api</span>
377
  </div>
378
  </div>
379
  <div class="form-group">
 
395
  </select>
396
  </div>
397
  <div class="form-group">
398
+ <label>Reasoning <span style="font-weight:400;text-transform:none;color:var(--accent-green)">(earns bonus!)</span></label>
399
+ <textarea class="form-control" id="reasoning" placeholder="Why did you choose this triage? Mentioning relevant signals earns up to +0.15 bonus."></textarea>
400
  </div>
401
  <button class="btn btn-primary btn-full" id="btn-submit" onclick="submitTriage()" disabled>
402
+ 🎯 Submit Triage Decision
403
  </button>
404
  </div>
405
  </div>
 
408
  <!-- Results -->
409
  <div id="results-area"></div>
410
 
411
+ <!-- How It Works (v2) -->
412
  <div class="how-section">
413
+ <h2>How the Multi-Step Environment Works</h2>
414
  <div class="how-steps">
415
  <div class="how-step">
416
  <div class="step-icon">πŸ“‘</div>
417
  <div class="how-step-num">1</div>
418
  <h3>Reset</h3>
419
+ <p>Agent calls /reset β€” sees title + body preview</p>
420
  </div>
421
  <div class="how-step">
422
+ <div class="step-icon">πŸ“–</div>
423
  <div class="how-step-num">2</div>
424
+ <h3>Investigate</h3>
425
+ <p>Read body, comments, logs β€” each costs a step</p>
426
  </div>
427
  <div class="how-step">
428
+ <div class="step-icon">πŸ€”</div>
429
  <div class="how-step-num">3</div>
430
  <h3>Decide</h3>
431
+ <p>Enough info? Submit triage or investigate more</p>
432
  </div>
433
  <div class="how-step">
434
+ <div class="step-icon">🎯</div>
435
  <div class="how-step-num">4</div>
436
+ <h3>Submit</h3>
437
+ <p>Priority, labels, team, milestone, reasoning</p>
438
+ </div>
439
+ <div class="how-step">
440
+ <div class="step-icon">πŸ“Š</div>
441
+ <div class="how-step-num">5</div>
442
  <h3>Score</h3>
443
+ <p>Semantic grading + efficiency bonus/penalty</p>
444
  </div>
445
  </div>
446
  </div>
447
 
 
448
  <footer class="footer">
449
  <div class="footer-links">
450
  <a href="https://github.com/Siteshcodes/bug-triage-env" target="_blank">GitHub</a>
451
  <a href="/docs" target="_blank">API Docs</a>
452
  <a href="/tasks" target="_blank">Tasks</a>
453
+ <a href="/leaderboard" target="_blank">Leaderboard</a>
454
  </div>
455
+ <p>Bug Triage Environment v2.0 β€” Meta PyTorch Hackathon Γ— Scaler School of Technology</p>
456
  </footer>
457
  </div>
458
 
459
  <script>
460
  let currentTask = 'easy';
461
+ let sessionId = null;
462
  let hasBug = false;
463
+ let stepCount = 0;
464
+ let maxSteps = 4;
465
+ let isDone = false;
466
+
467
+ const maxStepsMap = { easy: 4, medium: 5, hard: 6 };
468
 
469
  function selectTask(task) {
470
  currentTask = task;
471
+ maxSteps = maxStepsMap[task];
472
  document.querySelectorAll('.task-card').forEach(c => c.classList.remove('active'));
473
  document.querySelector(`.task-card[data-task="${task}"]`).classList.add('active');
474
+ resetUI();
475
+ }
476
+
477
+ function resetUI() {
478
+ hasBug = false; isDone = false; stepCount = 0; sessionId = null;
479
  document.getElementById('btn-submit').disabled = true;
480
+ document.getElementById('step-tracker').style.display = 'none';
481
+ document.getElementById('investigate-bar').style.display = 'none';
482
  document.getElementById('bug-area').innerHTML = `
483
  <div class="bug-placeholder">
484
  <div class="icon">πŸ›</div>
485
+ <p>Click <strong>"Start Episode"</strong> to investigate a <strong>${currentTask}</strong> bug</p>
486
+ </div>`;
 
487
  document.getElementById('results-area').innerHTML = '';
488
+ // Reset investigation buttons
489
+ ['btn-read-body', 'btn-read-comments', 'btn-check-logs', 'btn-check-similar'].forEach(id => {
490
+ const el = document.getElementById(id);
491
+ el.disabled = false;
492
+ el.classList.remove('revealed');
493
+ });
494
+ }
495
+
496
+ function updateStepTracker() {
497
+ const tracker = document.getElementById('step-tracker');
498
+ tracker.style.display = 'flex';
499
+
500
+ let dots = '';
501
+ for (let i = 1; i <= maxSteps; i++) {
502
+ const cls = i < stepCount + 1 ? 'done' : i === stepCount + 1 ? 'active' : 'pending';
503
+ dots += `<div class="step-dot ${cls}">${i}</div>`;
504
+ if (i < maxSteps) {
505
+ const lineCls = i < stepCount + 1 ? 'done' : '';
506
+ dots += `<div class="step-line ${lineCls}"></div>`;
507
+ }
508
+ }
509
+ dots += `<div class="step-label">Step ${stepCount} / ${maxSteps}</div>`;
510
+ tracker.innerHTML = dots;
511
  }
512
 
513
  async function fetchBug() {
 
522
  body: JSON.stringify({ task_id: currentTask })
523
  });
524
  const data = await resp.json();
525
+ sessionId = data.session_id;
526
  const obs = data.observation || data;
527
  const bug = obs.bug_report;
528
+ stepCount = 0;
529
+ isDone = false;
530
 
531
  let html = `
532
+ <div class="bug-title">${esc(bug.title)}</div>
533
  <div class="bug-meta">
534
+ <span>by <strong>${esc(bug.author)}</strong></span>
535
+ <span class="tag">${esc(bug.id)}</span>
536
+ <span class="tag" style="background:rgba(245,158,11,0.1);color:var(--accent-amber)">
537
+ ${maxSteps} steps max
538
+ </span>
539
  </div>
540
+ <div class="bug-body truncated" id="bug-body-text">${esc(bug.body)}</div>`;
 
541
 
542
  if (bug.labels_hint && bug.labels_hint.length > 0) {
543
  html += `<div class="bug-labels">${bug.labels_hint.map(l =>
544
+ `<span class="bug-label">${esc(l)}</span>`).join('')}</div>`;
 
545
  }
546
 
547
+ // Hidden sections placeholders
548
+ html += `<div id="comments-section"></div>`;
549
+ html += `<div id="logs-section"></div>`;
550
+ html += `<div id="similar-section"></div>`;
551
 
552
  document.getElementById('bug-area').innerHTML = html;
553
  document.getElementById('btn-submit').disabled = false;
554
+ document.getElementById('investigate-bar').style.display = 'flex';
555
  document.getElementById('results-area').innerHTML = '';
556
  hasBug = true;
557
+
558
+ // Reset investigation buttons
559
+ ['btn-read-body', 'btn-read-comments', 'btn-check-logs', 'btn-check-similar'].forEach(id => {
560
+ const el = document.getElementById(id);
561
+ el.disabled = false;
562
+ el.classList.remove('revealed');
563
+ });
564
+
565
+ updateStepTracker();
566
  } catch (err) {
567
  document.getElementById('bug-area').innerHTML = `
568
  <div class="bug-placeholder">
569
  <div class="icon">⚠️</div>
570
+ <p>Error: ${esc(err.message)}</p>
571
+ </div>`;
 
572
  }
573
+ btn.innerHTML = 'Start Episode';
 
574
  btn.disabled = false;
575
  }
576
 
577
+ async function investigate(actionType) {
578
+ if (!hasBug || isDone) return;
579
+
580
+ const btnMap = {
581
+ read_body: 'btn-read-body',
582
+ read_comments: 'btn-read-comments',
583
+ check_logs: 'btn-check-logs',
584
+ check_similar: 'btn-check-similar',
585
+ };
586
+ const btn = document.getElementById(btnMap[actionType]);
587
+ btn.innerHTML = '<span class="spinner"></span>';
588
+ btn.disabled = true;
589
+
590
+ try {
591
+ const payload = { action: { action_type: actionType } };
592
+ if (sessionId) payload.session_id = sessionId;
593
+
594
+ const resp = await fetch('/step', {
595
+ method: 'POST',
596
+ headers: { 'Content-Type': 'application/json' },
597
+ body: JSON.stringify(payload)
598
+ });
599
+ const data = await resp.json();
600
+ const obs = data.observation || {};
601
+ const bug = obs.bug_report || {};
602
+ stepCount = obs.steps_taken || stepCount + 1;
603
+
604
+ // Reveal data based on action type
605
+ if (actionType === 'read_body') {
606
+ const bodyEl = document.getElementById('bug-body-text');
607
+ if (bodyEl) {
608
+ bodyEl.className = 'bug-body';
609
+ bodyEl.textContent = bug.body || '';
610
+ }
611
+ btn.innerHTML = '<span class="emoji">πŸ“–</span> βœ“ Body Revealed';
612
+ btn.classList.add('revealed');
613
+ }
614
+ else if (actionType === 'read_comments') {
615
+ const section = document.getElementById('comments-section');
616
+ if (bug.comments && bug.comments.length > 0) {
617
+ section.innerHTML = `
618
+ <div class="bug-hidden-section">
619
+ <div class="section-title">πŸ’¬ Comments (${bug.comments.length})</div>
620
+ ${bug.comments.map(c => `<div class="bug-comment">${esc(c)}</div>`).join('')}
621
+ </div>`;
622
+ } else {
623
+ section.innerHTML = `
624
+ <div class="bug-hidden-section">
625
+ <div class="section-title">πŸ’¬ Comments</div>
626
+ <p style="font-size:13px;color:var(--text-muted)">No comments on this bug.</p>
627
+ </div>`;
628
+ }
629
+ btn.innerHTML = '<span class="emoji">πŸ’¬</span> βœ“ Comments Revealed';
630
+ btn.classList.add('revealed');
631
+ }
632
+ else if (actionType === 'check_logs') {
633
+ const section = document.getElementById('logs-section');
634
+ let logHtml = '<div class="bug-hidden-section"><div class="section-title">πŸ“Š System Logs & Signals</div>';
635
+ if (bug.stack_trace) logHtml += `<div class="bug-comment" style="font-family:'JetBrains Mono',monospace;font-size:12px">${esc(bug.stack_trace)}</div>`;
636
+ if (bug.affected_component) logHtml += `<p style="font-size:13px;color:var(--text-secondary);margin-top:6px">Component: <strong>${esc(bug.affected_component)}</strong></p>`;
637
+ if (bug.severity_signals && bug.severity_signals.length > 0) logHtml += `<p style="font-size:13px;color:var(--text-secondary);margin-top:4px">Signals: ${bug.severity_signals.map(s => `<span class="bug-label" style="background:rgba(239,68,68,0.1);color:var(--accent-red)">${esc(s)}</span>`).join(' ')}</p>`;
638
+ if (!bug.stack_trace && !bug.affected_component) logHtml += '<p style="font-size:13px;color:var(--text-muted)">No additional log data available.</p>';
639
+ logHtml += '</div>';
640
+ section.innerHTML = logHtml;
641
+ btn.innerHTML = '<span class="emoji">πŸ“Š</span> βœ“ Logs Revealed';
642
+ btn.classList.add('revealed');
643
+ }
644
+ else if (actionType === 'check_similar') {
645
+ const section = document.getElementById('similar-section');
646
+ if (bug.related_bugs && bug.related_bugs.length > 0) {
647
+ section.innerHTML = `
648
+ <div class="bug-hidden-section">
649
+ <div class="section-title">πŸ”— Related Bugs</div>
650
+ ${bug.related_bugs.map(b => `<div class="bug-comment">${esc(b)}</div>`).join('')}
651
+ </div>`;
652
+ } else {
653
+ section.innerHTML = `
654
+ <div class="bug-hidden-section">
655
+ <div class="section-title">πŸ”— Related Bugs</div>
656
+ <p style="font-size:13px;color:var(--text-muted)">No related bugs found.</p>
657
+ </div>`;
658
+ }
659
+ btn.innerHTML = '<span class="emoji">πŸ”—</span> βœ“ Checked';
660
+ btn.classList.add('revealed');
661
+ }
662
+
663
+ updateStepTracker();
664
+
665
+ if (data.done) {
666
+ isDone = true;
667
+ showResults(data.reward || 0, obs.feedback || '');
668
+ }
669
+ } catch (err) {
670
+ btn.innerHTML = `Error`;
671
+ }
672
+ }
673
+
674
  async function submitTriage() {
675
+ if (!hasBug || isDone) return;
676
 
677
  const btn = document.getElementById('btn-submit');
678
+ btn.innerHTML = '<span class="spinner"></span> Grading...';
679
  btn.disabled = true;
680
 
681
  const selectedLabels = [];
 
684
  });
685
 
686
  const action = {
687
+ action_type: 'submit',
688
  priority: document.getElementById('priority').value,
689
  labels: selectedLabels,
690
  assigned_team: document.getElementById('team').value,
 
693
  };
694
 
695
  try {
696
+ const payload = { action };
697
+ if (sessionId) payload.session_id = sessionId;
698
+
699
  const resp = await fetch('/step', {
700
  method: 'POST',
701
  headers: { 'Content-Type': 'application/json' },
702
+ body: JSON.stringify(payload)
703
  });
704
  const data = await resp.json();
705
  const reward = data.reward || 0;
706
  const obs = data.observation || {};
707
  const feedback = obs.feedback || '';
708
+ stepCount = obs.steps_taken || stepCount + 1;
709
 
710
+ updateStepTracker();
711
  showResults(reward, feedback);
712
+ isDone = true;
713
  hasBug = false;
714
+
715
+ // Disable investigate buttons
716
+ ['btn-read-body', 'btn-read-comments', 'btn-check-logs', 'btn-check-similar'].forEach(id => {
717
+ document.getElementById(id).disabled = true;
718
+ });
719
  } catch (err) {
720
  document.getElementById('results-area').innerHTML = `
721
  <div class="score-display">
722
  <div class="score-label">Error</div>
723
  <div class="score-value low">!</div>
724
+ <p style="color:var(--text-secondary)">${esc(err.message)}</p>
725
+ </div>`;
 
726
  }
727
+ btn.innerHTML = '🎯 Submit Triage Decision';
 
728
  btn.disabled = true;
729
  }
730
 
 
734
  const emoji = reward >= 0.7 ? 'πŸŽ‰' : reward >= 0.4 ? 'πŸ‘' : '😬';
735
 
736
  const feedbackParts = feedback.split(' | ').filter(Boolean);
 
737
  let feedbackHtml = '';
738
  if (feedbackParts.length > 0) {
739
  feedbackHtml = `
 
741
  <div class="feedback-title">Grader Feedback</div>
742
  ${feedbackParts.map(part => {
743
  let icon = 'πŸ“';
744
+ if (part.includes('βœ“') || parseFloat(part.match(/[\d.]+/)?.[0]) >= 0.9) icon = 'βœ…';
745
+ else if (part.includes('βœ—') || parseFloat(part.match(/[\d.]+/)?.[0]) <= 0.1) icon = '❌';
746
+ else if (part.includes('~') || part.includes('bonus')) icon = '⭐';
747
+ else if (part.includes('⚠') || part.includes('penalty') || part.includes('missed')) icon = '⚠️';
748
+ else if (part.includes('⚑')) icon = '⚑';
749
+ return `<div class="feedback-item"><span>${icon}</span>${esc(part)}</div>`;
750
  }).join('')}
751
+ </div>`;
 
752
  }
753
 
754
  document.getElementById('results-area').innerHTML = `
755
  <div class="results">
756
  <div class="score-display">
757
+ <div class="score-label">Reward Score (${stepCount} step${stepCount !== 1 ? 's' : ''} used)</div>
758
  <div class="score-value ${cls}">${emoji} ${reward.toFixed(3)}</div>
759
+ <div class="score-bar-wrap"><div class="score-bar ${cls}" id="score-bar"></div></div>
760
+ <p style="color:var(--text-secondary);font-size:13px">
761
+ ${pct}% on <strong>${currentTask}</strong> task
 
 
762
  </p>
763
  ${feedbackHtml}
764
+ <div style="margin-top:14px">
765
+ <button class="btn btn-secondary" onclick="fetchBug()" style="padding:8px 20px;font-size:13px">
766
  Try Another Bug β†’
767
  </button>
768
  </div>
769
  </div>
770
+ </div>`;
 
771
 
772
  requestAnimationFrame(() => {
773
  requestAnimationFrame(() => {
774
+ const bar = document.getElementById('score-bar');
775
+ if (bar) bar.style.width = pct + '%';
776
  });
777
  });
778
  }
779
 
780
+ function toggleLabel(el) { el.classList.toggle('selected'); }
 
 
781
 
782
+ function esc(str) {
783
  if (!str) return '';
784
  const div = document.createElement('div');
785
  div.textContent = str;