Spaces:
Sleeping
Sleeping
v2.0 frontend: multi-step investigation UI with step tracker, progressive reveal, and reasoning bonus
8483903 | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Bug Triage Environment v2.0 β OpenEnv</title> | |
| <meta name="description" content="Multi-step RL environment for AI bug triage β investigate, decide, and learn."> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <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"> | |
| <style> | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| :root { | |
| --bg-primary: #0a0a0f; | |
| --bg-secondary: #12121a; | |
| --bg-card: rgba(20, 20, 32, 0.7); | |
| --bg-card-hover: rgba(30, 30, 48, 0.8); | |
| --border: rgba(255, 255, 255, 0.06); | |
| --border-active: rgba(139, 92, 246, 0.4); | |
| --text-primary: #f0f0f5; | |
| --text-secondary: #8b8b9e; | |
| --text-muted: #5a5a6e; | |
| --accent-purple: #8b5cf6; | |
| --accent-blue: #3b82f6; | |
| --accent-cyan: #06b6d4; | |
| --accent-green: #10b981; | |
| --accent-amber: #f59e0b; | |
| --accent-red: #ef4444; | |
| --accent-pink: #ec4899; | |
| --gradient-main: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 50%, #06b6d4 100%); | |
| --gradient-warm: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%); | |
| --gradient-cool: linear-gradient(135deg, #10b981 0%, #06b6d4 100%); | |
| --shadow-glow: 0 0 40px rgba(139, 92, 246, 0.15); | |
| --radius: 16px; | |
| --radius-sm: 10px; | |
| --radius-xs: 6px; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| .bg-grid { | |
| position: fixed; inset: 0; | |
| background-image: | |
| linear-gradient(rgba(139, 92, 246, 0.03) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(139, 92, 246, 0.03) 1px, transparent 1px); | |
| background-size: 60px 60px; | |
| pointer-events: none; z-index: 0; | |
| } | |
| .bg-orb { position: fixed; border-radius: 50%; filter: blur(120px); pointer-events: none; z-index: 0; } | |
| .bg-orb-1 { width: 600px; height: 600px; top: -200px; right: -100px; background: rgba(139, 92, 246, 0.08); } | |
| .bg-orb-2 { width: 500px; height: 500px; bottom: -150px; left: -100px; background: rgba(6, 182, 212, 0.06); } | |
| .bg-orb-3 { width: 400px; height: 400px; top: 40%; left: 50%; transform: translateX(-50%); background: rgba(59, 130, 246, 0.04); } | |
| .container { max-width: 1100px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 1; } | |
| /* ββ Header βββββββββββββββββββββββ */ | |
| .header { padding: 40px 0 28px; text-align: center; } | |
| .header-badge { | |
| display: inline-flex; align-items: center; gap: 8px; | |
| padding: 6px 16px; background: rgba(139, 92, 246, 0.1); | |
| border: 1px solid rgba(139, 92, 246, 0.2); border-radius: 100px; | |
| font-size: 12px; font-weight: 500; color: var(--accent-purple); | |
| letter-spacing: 0.5px; text-transform: uppercase; margin-bottom: 16px; | |
| } | |
| .header-badge .dot { | |
| width: 6px; height: 6px; background: var(--accent-green); | |
| border-radius: 50%; animation: pulse-dot 2s ease-in-out infinite; | |
| } | |
| @keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } | |
| .header h1 { font-size: 44px; font-weight: 800; letter-spacing: -1.5px; line-height: 1.1; margin-bottom: 10px; } | |
| .header h1 .emoji { font-size: 40px; margin-right: 4px; } | |
| .header h1 .gradient-text { | |
| background: var(--gradient-main); -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; background-clip: text; | |
| } | |
| .header p { font-size: 16px; color: var(--text-secondary); max-width: 640px; margin: 0 auto; } | |
| .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; } | |
| /* ββ Task Selector ββββββββββββββββ */ | |
| .task-selector { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 24px 0; } | |
| .task-card { | |
| position: relative; padding: 18px; background: var(--bg-card); | |
| border: 1px solid var(--border); border-radius: var(--radius); | |
| cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| backdrop-filter: blur(12px); overflow: hidden; | |
| } | |
| .task-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; opacity: 0; transition: opacity 0.3s; } | |
| .task-card[data-task="easy"]::before { background: var(--accent-green); } | |
| .task-card[data-task="medium"]::before { background: var(--accent-amber); } | |
| .task-card[data-task="hard"]::before { background: var(--accent-red); } | |
| .task-card:hover { background: var(--bg-card-hover); border-color: rgba(255,255,255,0.1); transform: translateY(-2px); } | |
| .task-card.active { border-color: var(--border-active); background: var(--bg-card-hover); box-shadow: var(--shadow-glow); } | |
| .task-card.active::before { opacity: 1; } | |
| .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; } | |
| .task-card[data-task="easy"] .task-difficulty { background: rgba(16,185,129,0.1); color: var(--accent-green); } | |
| .task-card[data-task="medium"] .task-difficulty { background: rgba(245,158,11,0.1); color: var(--accent-amber); } | |
| .task-card[data-task="hard"] .task-difficulty { background: rgba(239,68,68,0.1); color: var(--accent-red); } | |
| .task-card h3 { font-size: 15px; font-weight: 600; margin-bottom: 4px; } | |
| .task-card p { font-size: 12px; color: var(--text-secondary); line-height: 1.4; } | |
| .task-steps { margin-top: 8px; font-size: 11px; color: var(--text-muted); font-family: 'JetBrains Mono', monospace; } | |
| /* ββ Step Tracker βββββββββββββββββ */ | |
| .step-tracker { | |
| display: flex; align-items: center; gap: 4px; | |
| padding: 12px 20px; background: var(--bg-card); border: 1px solid var(--border); | |
| border-radius: var(--radius); margin-bottom: 16px; backdrop-filter: blur(12px); | |
| } | |
| .step-dot { | |
| width: 28px; height: 28px; border-radius: 50%; display: flex; | |
| align-items: center; justify-content: center; font-size: 11px; | |
| font-weight: 600; transition: all 0.3s; | |
| } | |
| .step-dot.pending { background: rgba(255,255,255,0.05); color: var(--text-muted); border: 1px solid var(--border); } | |
| .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); } | |
| .step-dot.done { background: rgba(16,185,129,0.15); color: var(--accent-green); border: 1px solid rgba(16,185,129,0.3); } | |
| .step-line { flex: 1; height: 2px; background: var(--border); } | |
| .step-line.done { background: rgba(16,185,129,0.3); } | |
| .step-label { margin-left: auto; font-size: 12px; color: var(--text-muted); font-weight: 500; } | |
| /* ββ Investigation Bar ββββββββββββ */ | |
| .investigate-bar { | |
| display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; | |
| } | |
| .investigate-btn { | |
| display: inline-flex; align-items: center; gap: 6px; | |
| padding: 10px 16px; background: rgba(255,255,255,0.04); | |
| border: 1px solid var(--border); border-radius: var(--radius-sm); | |
| color: var(--text-secondary); font-family: 'Inter', sans-serif; font-size: 13px; | |
| font-weight: 500; cursor: pointer; transition: all 0.25s; | |
| } | |
| .investigate-btn:hover:not(:disabled) { background: rgba(139,92,246,0.1); border-color: rgba(139,92,246,0.3); color: var(--text-primary); } | |
| .investigate-btn:disabled { opacity: 0.3; cursor: not-allowed; } | |
| .investigate-btn.revealed { background: rgba(16,185,129,0.08); border-color: rgba(16,185,129,0.25); color: var(--accent-green); } | |
| .investigate-btn .emoji { font-size: 16px; } | |
| /* ββ Content Panels βββββββββββββββ */ | |
| .content { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 32px; } | |
| .panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); backdrop-filter: blur(12px); overflow: hidden; } | |
| .panel-header { | |
| display: flex; align-items: center; justify-content: space-between; | |
| padding: 14px 20px; border-bottom: 1px solid var(--border); | |
| } | |
| .panel-header h2 { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; } | |
| .panel-body { padding: 18px; } | |
| /* ββ Bug Report βββββββββββββββββββ */ | |
| .bug-placeholder { text-align: center; padding: 48px 20px; color: var(--text-muted); } | |
| .bug-placeholder .icon { font-size: 48px; margin-bottom: 12px; opacity: 0.5; } | |
| .bug-placeholder p { font-size: 14px; } | |
| .bug-title { font-size: 17px; font-weight: 700; margin-bottom: 4px; line-height: 1.3; } | |
| .bug-meta { font-size: 12px; color: var(--text-muted); margin-bottom: 14px; display: flex; gap: 10px; align-items: center; } | |
| .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; } | |
| .bug-body { | |
| font-size: 14px; color: var(--text-secondary); line-height: 1.7; | |
| margin-bottom: 14px; padding: 12px 14px; background: rgba(0,0,0,0.2); | |
| border-radius: var(--radius-sm); border-left: 3px solid var(--accent-purple); | |
| } | |
| .bug-body.truncated { position: relative; } | |
| .bug-body.truncated::after { | |
| content: 'π Use "Read Full Body" to reveal...'; | |
| display: block; margin-top: 8px; font-size: 11px; color: var(--accent-amber); | |
| font-style: italic; | |
| } | |
| .bug-hidden-section { | |
| margin-top: 12px; padding: 10px 14px; background: rgba(0,0,0,0.15); | |
| border-radius: var(--radius-xs); border: 1px dashed rgba(255,255,255,0.06); | |
| animation: reveal 0.4s ease; | |
| } | |
| @keyframes reveal { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } | |
| .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; } | |
| .bug-comment { | |
| padding: 8px 12px; background: rgba(0,0,0,0.15); border-radius: var(--radius-xs); | |
| font-size: 13px; color: var(--text-secondary); margin-bottom: 5px; | |
| border-left: 2px solid rgba(139,92,246,0.3); | |
| } | |
| .bug-labels { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; } | |
| .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; } | |
| .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); } | |
| /* ββ Triage Form ββββββββββββββββββ */ | |
| .form-group { margin-bottom: 14px; } | |
| .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; } | |
| .form-control { | |
| width: 100%; padding: 10px 14px; background: rgba(0,0,0,0.3); | |
| border: 1px solid var(--border); border-radius: var(--radius-xs); | |
| color: var(--text-primary); font-family: 'Inter', sans-serif; font-size: 14px; | |
| transition: border-color 0.2s; outline: none; | |
| } | |
| .form-control:focus { border-color: var(--accent-purple); box-shadow: 0 0 0 3px rgba(139,92,246,0.1); } | |
| .form-control option { background: var(--bg-secondary); } | |
| textarea.form-control { min-height: 56px; resize: vertical; } | |
| .checkbox-group { display: flex; flex-wrap: wrap; gap: 6px; } | |
| .label-chip { | |
| padding: 5px 12px; background: rgba(0,0,0,0.3); border: 1px solid var(--border); | |
| border-radius: 100px; font-size: 12px; cursor: pointer; transition: all 0.2s; | |
| user-select: none; color: var(--text-secondary); | |
| } | |
| .label-chip:hover { border-color: rgba(255,255,255,0.15); } | |
| .label-chip.selected { background: rgba(139,92,246,0.15); border-color: var(--accent-purple); color: var(--accent-purple); } | |
| /* ββ Buttons ββββββββββββββββββββββ */ | |
| .btn { | |
| display: inline-flex; align-items: center; justify-content: center; gap: 8px; | |
| padding: 11px 22px; border: none; border-radius: var(--radius-sm); | |
| font-family: 'Inter', sans-serif; font-size: 14px; font-weight: 600; | |
| cursor: pointer; transition: all 0.25s cubic-bezier(0.4,0,0.2,1); outline: none; | |
| } | |
| .btn-primary { background: var(--gradient-main); color: white; box-shadow: 0 4px 15px rgba(139,92,246,0.3); } | |
| .btn-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(139,92,246,0.4); } | |
| .btn-primary:active { transform: translateY(0); } | |
| .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; transform: none; } | |
| .btn-secondary { background: rgba(255,255,255,0.05); color: var(--text-secondary); border: 1px solid var(--border); } | |
| .btn-secondary:hover { background: rgba(255,255,255,0.08); color: var(--text-primary); } | |
| .btn-full { width: 100%; } | |
| /* ββ Results ββββββββββββββββββββββ */ | |
| .results { margin-top: 16px; animation: slide-up 0.4s cubic-bezier(0.4,0,0.2,1); } | |
| @keyframes slide-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } } | |
| .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); } | |
| .score-label { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); margin-bottom: 6px; } | |
| .score-value { font-size: 52px; font-weight: 800; letter-spacing: -2px; margin-bottom: 4px; } | |
| .score-value.high { color: var(--accent-green); } | |
| .score-value.mid { color: var(--accent-amber); } | |
| .score-value.low { color: var(--accent-red); } | |
| .score-bar-wrap { width: 100%; height: 8px; background: rgba(255,255,255,0.05); border-radius: 100px; overflow: hidden; margin: 10px 0; } | |
| .score-bar { height: 100%; border-radius: 100px; transition: width 1s cubic-bezier(0.4,0,0.2,1); width: 0; } | |
| .score-bar.high { background: var(--gradient-cool); } | |
| .score-bar.mid { background: linear-gradient(90deg, var(--accent-amber), var(--accent-green)); } | |
| .score-bar.low { background: var(--gradient-warm); } | |
| .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; } | |
| .feedback-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); margin-bottom: 6px; } | |
| .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); } | |
| .feedback-item:last-child { border-bottom: none; } | |
| /* ββ Spinner ββββββββββββββββββββββ */ | |
| .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; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| /* ββ How It Works βββββββββββββββββ */ | |
| .how-section { margin: 40px 0; padding: 32px 0; border-top: 1px solid var(--border); } | |
| .how-section h2 { font-size: 22px; font-weight: 700; text-align: center; margin-bottom: 28px; } | |
| .how-steps { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; } | |
| .how-step { text-align: center; padding: 20px 12px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); } | |
| .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; } | |
| .how-step h3 { font-size: 13px; font-weight: 600; margin-bottom: 4px; } | |
| .how-step p { font-size: 11px; color: var(--text-secondary); } | |
| .how-step .step-icon { font-size: 24px; margin-bottom: 8px; } | |
| /* ββ Footer βββββββββββββββββββββββ */ | |
| .footer { text-align: center; padding: 28px 0; border-top: 1px solid var(--border); color: var(--text-muted); font-size: 13px; } | |
| .footer a { color: var(--accent-purple); text-decoration: none; } | |
| .footer a:hover { text-decoration: underline; } | |
| .footer-links { display: flex; justify-content: center; gap: 20px; margin-bottom: 10px; } | |
| /* ββ Responsive βββββββββββββββββββ */ | |
| @media (max-width: 768px) { | |
| .header h1 { font-size: 30px; } | |
| .task-selector { grid-template-columns: 1fr; } | |
| .content { grid-template-columns: 1fr; } | |
| .how-steps { grid-template-columns: repeat(2, 1fr); } | |
| .investigate-bar { flex-direction: column; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-grid"></div> | |
| <div class="bg-orb bg-orb-1"></div> | |
| <div class="bg-orb bg-orb-2"></div> | |
| <div class="bg-orb bg-orb-3"></div> | |
| <div class="container"> | |
| <header class="header"> | |
| <div class="header-badge"><span class="dot"></span> OpenEnv Environment β Live</div> | |
| <h1> | |
| <span class="emoji">π</span> | |
| <span class="gradient-text">Bug Triage</span> Environment | |
| <span class="version-tag">v2.0</span> | |
| </h1> | |
| <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> | |
| </header> | |
| <!-- Task Selector --> | |
| <div class="task-selector"> | |
| <div class="task-card active" data-task="easy" onclick="selectTask('easy')"> | |
| <span class="task-difficulty">Easy</span> | |
| <h3>Priority Assignment</h3> | |
| <p>Investigate and assign P0βP3 priority</p> | |
| <div class="task-steps">4 steps max Β· priority only</div> | |
| </div> | |
| <div class="task-card" data-task="medium" onclick="selectTask('medium')"> | |
| <span class="task-difficulty">Medium</span> | |
| <h3>Priority + Labels + Team</h3> | |
| <p>Investigate and assign priority, labels, team</p> | |
| <div class="task-steps">5 steps max Β· weighted scoring</div> | |
| </div> | |
| <div class="task-card" data-task="hard" onclick="selectTask('hard')"> | |
| <span class="task-difficulty">Hard</span> | |
| <h3>Full Triage</h3> | |
| <p>Complete triage with security penalty</p> | |
| <div class="task-steps">6 steps max Β· security penalty</div> | |
| </div> | |
| </div> | |
| <!-- Step Tracker --> | |
| <div class="step-tracker" id="step-tracker" style="display:none;"> | |
| <div class="step-label" id="step-label">Step 0 / 4</div> | |
| </div> | |
| <!-- Investigation Bar --> | |
| <div class="investigate-bar" id="investigate-bar" style="display:none;"> | |
| <button class="investigate-btn" id="btn-read-body" onclick="investigate('read_body')"> | |
| <span class="emoji">π</span> Read Full Body | |
| </button> | |
| <button class="investigate-btn" id="btn-read-comments" onclick="investigate('read_comments')"> | |
| <span class="emoji">π¬</span> Read Comments | |
| </button> | |
| <button class="investigate-btn" id="btn-check-logs" onclick="investigate('check_logs')"> | |
| <span class="emoji">π</span> Check Logs | |
| </button> | |
| <button class="investigate-btn" id="btn-check-similar" onclick="investigate('check_similar')"> | |
| <span class="emoji">π</span> Similar Bugs | |
| </button> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="content"> | |
| <!-- Bug Report Panel --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h2>π Bug Report</h2> | |
| <button class="btn btn-secondary" onclick="fetchBug()" id="btn-reset" style="padding: 6px 14px; font-size: 12px;"> | |
| Start Episode | |
| </button> | |
| </div> | |
| <div class="panel-body" id="bug-area"> | |
| <div class="bug-placeholder"> | |
| <div class="icon">π</div> | |
| <p>Click <strong>"Start Episode"</strong> to begin investigating a bug</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Triage Form Panel --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h2>π― Your Triage Decision</h2> | |
| </div> | |
| <div class="panel-body"> | |
| <div class="form-group"> | |
| <label>Priority</label> | |
| <select class="form-control" id="priority"> | |
| <option value="P0">P0 β Critical / Production Down</option> | |
| <option value="P1">P1 β Major / No Workaround</option> | |
| <option value="P2" selected>P2 β Degraded / Workaround Exists</option> | |
| <option value="P3">P3 β Minor / Cosmetic</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>Labels</label> | |
| <div class="checkbox-group" id="labels-group"> | |
| <span class="label-chip selected" data-label="bug" onclick="toggleLabel(this)">bug</span> | |
| <span class="label-chip" data-label="security" onclick="toggleLabel(this)">security</span> | |
| <span class="label-chip" data-label="performance" onclick="toggleLabel(this)">performance</span> | |
| <span class="label-chip" data-label="ux" onclick="toggleLabel(this)">ux</span> | |
| <span class="label-chip" data-label="data-integrity" onclick="toggleLabel(this)">data-integrity</span> | |
| <span class="label-chip" data-label="payments" onclick="toggleLabel(this)">payments</span> | |
| <span class="label-chip" data-label="documentation" onclick="toggleLabel(this)">documentation</span> | |
| <span class="label-chip" data-label="api" onclick="toggleLabel(this)">api</span> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label>Assigned Team</label> | |
| <select class="form-control" id="team"> | |
| <option value="backend">backend</option> | |
| <option value="frontend">frontend</option> | |
| <option value="infra">infra</option> | |
| <option value="security">security</option> | |
| <option value="devx">devx</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>Milestone</label> | |
| <select class="form-control" id="milestone"> | |
| <option value="backlog">backlog</option> | |
| <option value="v2.1">v2.1</option> | |
| <option value="hotfix">hotfix</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>Reasoning <span style="font-weight:400;text-transform:none;color:var(--accent-green)">(earns bonus!)</span></label> | |
| <textarea class="form-control" id="reasoning" placeholder="Why did you choose this triage? Mentioning relevant signals earns up to +0.15 bonus."></textarea> | |
| </div> | |
| <button class="btn btn-primary btn-full" id="btn-submit" onclick="submitTriage()" disabled> | |
| π― Submit Triage Decision | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Results --> | |
| <div id="results-area"></div> | |
| <!-- How It Works (v2) --> | |
| <div class="how-section"> | |
| <h2>How the Multi-Step Environment Works</h2> | |
| <div class="how-steps"> | |
| <div class="how-step"> | |
| <div class="step-icon">π‘</div> | |
| <div class="how-step-num">1</div> | |
| <h3>Reset</h3> | |
| <p>Agent calls /reset β sees title + body preview</p> | |
| </div> | |
| <div class="how-step"> | |
| <div class="step-icon">π</div> | |
| <div class="how-step-num">2</div> | |
| <h3>Investigate</h3> | |
| <p>Read body, comments, logs β each costs a step</p> | |
| </div> | |
| <div class="how-step"> | |
| <div class="step-icon">π€</div> | |
| <div class="how-step-num">3</div> | |
| <h3>Decide</h3> | |
| <p>Enough info? Submit triage or investigate more</p> | |
| </div> | |
| <div class="how-step"> | |
| <div class="step-icon">π―</div> | |
| <div class="how-step-num">4</div> | |
| <h3>Submit</h3> | |
| <p>Priority, labels, team, milestone, reasoning</p> | |
| </div> | |
| <div class="how-step"> | |
| <div class="step-icon">π</div> | |
| <div class="how-step-num">5</div> | |
| <h3>Score</h3> | |
| <p>Semantic grading + efficiency bonus/penalty</p> | |
| </div> | |
| </div> | |
| </div> | |
| <footer class="footer"> | |
| <div class="footer-links"> | |
| <a href="https://github.com/Siteshcodes/bug-triage-env" target="_blank">GitHub</a> | |
| <a href="/docs" target="_blank">API Docs</a> | |
| <a href="/tasks" target="_blank">Tasks</a> | |
| <a href="/leaderboard" target="_blank">Leaderboard</a> | |
| </div> | |
| <p>Bug Triage Environment v2.0 β Meta PyTorch Hackathon Γ Scaler School of Technology</p> | |
| </footer> | |
| </div> | |
| <script> | |
| let currentTask = 'easy'; | |
| let sessionId = null; | |
| let hasBug = false; | |
| let stepCount = 0; | |
| let maxSteps = 4; | |
| let isDone = false; | |
| const maxStepsMap = { easy: 4, medium: 5, hard: 6 }; | |
| function selectTask(task) { | |
| currentTask = task; | |
| maxSteps = maxStepsMap[task]; | |
| document.querySelectorAll('.task-card').forEach(c => c.classList.remove('active')); | |
| document.querySelector(`.task-card[data-task="${task}"]`).classList.add('active'); | |
| resetUI(); | |
| } | |
| function resetUI() { | |
| hasBug = false; isDone = false; stepCount = 0; sessionId = null; | |
| document.getElementById('btn-submit').disabled = true; | |
| document.getElementById('step-tracker').style.display = 'none'; | |
| document.getElementById('investigate-bar').style.display = 'none'; | |
| document.getElementById('bug-area').innerHTML = ` | |
| <div class="bug-placeholder"> | |
| <div class="icon">π</div> | |
| <p>Click <strong>"Start Episode"</strong> to investigate a <strong>${currentTask}</strong> bug</p> | |
| </div>`; | |
| document.getElementById('results-area').innerHTML = ''; | |
| // Reset investigation buttons | |
| ['btn-read-body', 'btn-read-comments', 'btn-check-logs', 'btn-check-similar'].forEach(id => { | |
| const el = document.getElementById(id); | |
| el.disabled = false; | |
| el.classList.remove('revealed'); | |
| }); | |
| } | |
| function updateStepTracker() { | |
| const tracker = document.getElementById('step-tracker'); | |
| tracker.style.display = 'flex'; | |
| let dots = ''; | |
| for (let i = 1; i <= maxSteps; i++) { | |
| const cls = i < stepCount + 1 ? 'done' : i === stepCount + 1 ? 'active' : 'pending'; | |
| dots += `<div class="step-dot ${cls}">${i}</div>`; | |
| if (i < maxSteps) { | |
| const lineCls = i < stepCount + 1 ? 'done' : ''; | |
| dots += `<div class="step-line ${lineCls}"></div>`; | |
| } | |
| } | |
| dots += `<div class="step-label">Step ${stepCount} / ${maxSteps}</div>`; | |
| tracker.innerHTML = dots; | |
| } | |
| async function fetchBug() { | |
| const btn = document.getElementById('btn-reset'); | |
| btn.innerHTML = '<span class="spinner"></span>'; | |
| btn.disabled = true; | |
| try { | |
| const resp = await fetch('/reset', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ task_id: currentTask }) | |
| }); | |
| const data = await resp.json(); | |
| sessionId = data.session_id; | |
| const obs = data.observation || data; | |
| const bug = obs.bug_report; | |
| stepCount = 0; | |
| isDone = false; | |
| let html = ` | |
| <div class="bug-title">${esc(bug.title)}</div> | |
| <div class="bug-meta"> | |
| <span>by <strong>${esc(bug.author)}</strong></span> | |
| <span class="tag">${esc(bug.id)}</span> | |
| <span class="tag" style="background:rgba(245,158,11,0.1);color:var(--accent-amber)"> | |
| ${maxSteps} steps max | |
| </span> | |
| </div> | |
| <div class="bug-body truncated" id="bug-body-text">${esc(bug.body)}</div>`; | |
| if (bug.labels_hint && bug.labels_hint.length > 0) { | |
| html += `<div class="bug-labels">${bug.labels_hint.map(l => | |
| `<span class="bug-label">${esc(l)}</span>`).join('')}</div>`; | |
| } | |
| // Hidden sections placeholders | |
| html += `<div id="comments-section"></div>`; | |
| html += `<div id="logs-section"></div>`; | |
| html += `<div id="similar-section"></div>`; | |
| document.getElementById('bug-area').innerHTML = html; | |
| document.getElementById('btn-submit').disabled = false; | |
| document.getElementById('investigate-bar').style.display = 'flex'; | |
| document.getElementById('results-area').innerHTML = ''; | |
| hasBug = true; | |
| // Reset investigation buttons | |
| ['btn-read-body', 'btn-read-comments', 'btn-check-logs', 'btn-check-similar'].forEach(id => { | |
| const el = document.getElementById(id); | |
| el.disabled = false; | |
| el.classList.remove('revealed'); | |
| }); | |
| updateStepTracker(); | |
| } catch (err) { | |
| document.getElementById('bug-area').innerHTML = ` | |
| <div class="bug-placeholder"> | |
| <div class="icon">β οΈ</div> | |
| <p>Error: ${esc(err.message)}</p> | |
| </div>`; | |
| } | |
| btn.innerHTML = 'Start Episode'; | |
| btn.disabled = false; | |
| } | |
| async function investigate(actionType) { | |
| if (!hasBug || isDone) return; | |
| const btnMap = { | |
| read_body: 'btn-read-body', | |
| read_comments: 'btn-read-comments', | |
| check_logs: 'btn-check-logs', | |
| check_similar: 'btn-check-similar', | |
| }; | |
| const btn = document.getElementById(btnMap[actionType]); | |
| btn.innerHTML = '<span class="spinner"></span>'; | |
| btn.disabled = true; | |
| try { | |
| const payload = { action: { action_type: actionType } }; | |
| if (sessionId) payload.session_id = sessionId; | |
| const resp = await fetch('/step', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload) | |
| }); | |
| const data = await resp.json(); | |
| const obs = data.observation || {}; | |
| const bug = obs.bug_report || {}; | |
| stepCount = obs.steps_taken || stepCount + 1; | |
| // Reveal data based on action type | |
| if (actionType === 'read_body') { | |
| const bodyEl = document.getElementById('bug-body-text'); | |
| if (bodyEl) { | |
| bodyEl.className = 'bug-body'; | |
| bodyEl.textContent = bug.body || ''; | |
| } | |
| btn.innerHTML = '<span class="emoji">π</span> β Body Revealed'; | |
| btn.classList.add('revealed'); | |
| } | |
| else if (actionType === 'read_comments') { | |
| const section = document.getElementById('comments-section'); | |
| if (bug.comments && bug.comments.length > 0) { | |
| section.innerHTML = ` | |
| <div class="bug-hidden-section"> | |
| <div class="section-title">π¬ Comments (${bug.comments.length})</div> | |
| ${bug.comments.map(c => `<div class="bug-comment">${esc(c)}</div>`).join('')} | |
| </div>`; | |
| } else { | |
| section.innerHTML = ` | |
| <div class="bug-hidden-section"> | |
| <div class="section-title">π¬ Comments</div> | |
| <p style="font-size:13px;color:var(--text-muted)">No comments on this bug.</p> | |
| </div>`; | |
| } | |
| btn.innerHTML = '<span class="emoji">π¬</span> β Comments Revealed'; | |
| btn.classList.add('revealed'); | |
| } | |
| else if (actionType === 'check_logs') { | |
| const section = document.getElementById('logs-section'); | |
| let logHtml = '<div class="bug-hidden-section"><div class="section-title">π System Logs & Signals</div>'; | |
| if (bug.stack_trace) logHtml += `<div class="bug-comment" style="font-family:'JetBrains Mono',monospace;font-size:12px">${esc(bug.stack_trace)}</div>`; | |
| 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>`; | |
| 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>`; | |
| if (!bug.stack_trace && !bug.affected_component) logHtml += '<p style="font-size:13px;color:var(--text-muted)">No additional log data available.</p>'; | |
| logHtml += '</div>'; | |
| section.innerHTML = logHtml; | |
| btn.innerHTML = '<span class="emoji">π</span> β Logs Revealed'; | |
| btn.classList.add('revealed'); | |
| } | |
| else if (actionType === 'check_similar') { | |
| const section = document.getElementById('similar-section'); | |
| if (bug.related_bugs && bug.related_bugs.length > 0) { | |
| section.innerHTML = ` | |
| <div class="bug-hidden-section"> | |
| <div class="section-title">π Related Bugs</div> | |
| ${bug.related_bugs.map(b => `<div class="bug-comment">${esc(b)}</div>`).join('')} | |
| </div>`; | |
| } else { | |
| section.innerHTML = ` | |
| <div class="bug-hidden-section"> | |
| <div class="section-title">π Related Bugs</div> | |
| <p style="font-size:13px;color:var(--text-muted)">No related bugs found.</p> | |
| </div>`; | |
| } | |
| btn.innerHTML = '<span class="emoji">π</span> β Checked'; | |
| btn.classList.add('revealed'); | |
| } | |
| updateStepTracker(); | |
| if (data.done) { | |
| isDone = true; | |
| showResults(data.reward || 0, obs.feedback || ''); | |
| } | |
| } catch (err) { | |
| btn.innerHTML = `Error`; | |
| } | |
| } | |
| async function submitTriage() { | |
| if (!hasBug || isDone) return; | |
| const btn = document.getElementById('btn-submit'); | |
| btn.innerHTML = '<span class="spinner"></span> Grading...'; | |
| btn.disabled = true; | |
| const selectedLabels = []; | |
| document.querySelectorAll('.label-chip.selected').forEach(el => { | |
| selectedLabels.push(el.dataset.label); | |
| }); | |
| const action = { | |
| action_type: 'submit', | |
| priority: document.getElementById('priority').value, | |
| labels: selectedLabels, | |
| assigned_team: document.getElementById('team').value, | |
| milestone: document.getElementById('milestone').value, | |
| reasoning: document.getElementById('reasoning').value | |
| }; | |
| try { | |
| const payload = { action }; | |
| if (sessionId) payload.session_id = sessionId; | |
| const resp = await fetch('/step', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload) | |
| }); | |
| const data = await resp.json(); | |
| const reward = data.reward || 0; | |
| const obs = data.observation || {}; | |
| const feedback = obs.feedback || ''; | |
| stepCount = obs.steps_taken || stepCount + 1; | |
| updateStepTracker(); | |
| showResults(reward, feedback); | |
| isDone = true; | |
| hasBug = false; | |
| // Disable investigate buttons | |
| ['btn-read-body', 'btn-read-comments', 'btn-check-logs', 'btn-check-similar'].forEach(id => { | |
| document.getElementById(id).disabled = true; | |
| }); | |
| } catch (err) { | |
| document.getElementById('results-area').innerHTML = ` | |
| <div class="score-display"> | |
| <div class="score-label">Error</div> | |
| <div class="score-value low">!</div> | |
| <p style="color:var(--text-secondary)">${esc(err.message)}</p> | |
| </div>`; | |
| } | |
| btn.innerHTML = 'π― Submit Triage Decision'; | |
| btn.disabled = true; | |
| } | |
| function showResults(reward, feedback) { | |
| const pct = Math.round(reward * 100); | |
| const cls = reward >= 0.7 ? 'high' : reward >= 0.4 ? 'mid' : 'low'; | |
| const emoji = reward >= 0.7 ? 'π' : reward >= 0.4 ? 'π' : 'π¬'; | |
| const feedbackParts = feedback.split(' | ').filter(Boolean); | |
| let feedbackHtml = ''; | |
| if (feedbackParts.length > 0) { | |
| feedbackHtml = ` | |
| <div class="feedback-box"> | |
| <div class="feedback-title">Grader Feedback</div> | |
| ${feedbackParts.map(part => { | |
| let icon = 'π'; | |
| if (part.includes('β') || parseFloat(part.match(/[\d.]+/)?.[0]) >= 0.9) icon = 'β '; | |
| else if (part.includes('β') || parseFloat(part.match(/[\d.]+/)?.[0]) <= 0.1) icon = 'β'; | |
| else if (part.includes('~') || part.includes('bonus')) icon = 'β'; | |
| else if (part.includes('β ') || part.includes('penalty') || part.includes('missed')) icon = 'β οΈ'; | |
| else if (part.includes('β‘')) icon = 'β‘'; | |
| return `<div class="feedback-item"><span>${icon}</span>${esc(part)}</div>`; | |
| }).join('')} | |
| </div>`; | |
| } | |
| document.getElementById('results-area').innerHTML = ` | |
| <div class="results"> | |
| <div class="score-display"> | |
| <div class="score-label">Reward Score (${stepCount} step${stepCount !== 1 ? 's' : ''} used)</div> | |
| <div class="score-value ${cls}">${emoji} ${reward.toFixed(3)}</div> | |
| <div class="score-bar-wrap"><div class="score-bar ${cls}" id="score-bar"></div></div> | |
| <p style="color:var(--text-secondary);font-size:13px"> | |
| ${pct}% on <strong>${currentTask}</strong> task | |
| </p> | |
| ${feedbackHtml} | |
| <div style="margin-top:14px"> | |
| <button class="btn btn-secondary" onclick="fetchBug()" style="padding:8px 20px;font-size:13px"> | |
| Try Another Bug β | |
| </button> | |
| </div> | |
| </div> | |
| </div>`; | |
| requestAnimationFrame(() => { | |
| requestAnimationFrame(() => { | |
| const bar = document.getElementById('score-bar'); | |
| if (bar) bar.style.width = pct + '%'; | |
| }); | |
| }); | |
| } | |
| function toggleLabel(el) { el.classList.toggle('selected'); } | |
| function esc(str) { | |
| if (!str) return ''; | |
| const div = document.createElement('div'); | |
| div.textContent = str; | |
| return div.innerHTML; | |
| } | |
| </script> | |
| </body> | |
| </html> | |