FIX: gitignore for Next.js, add lib/api.js and HTML reference
Browse files- .gitignore +7 -3
- pr_review_dashboard.html +287 -0
- pr_review_dashboard/lib/api.js +41 -0
.gitignore
CHANGED
|
@@ -8,14 +8,14 @@ __pycache__/
|
|
| 8 |
|
| 9 |
# Distribution / packaging
|
| 10 |
.Python
|
| 11 |
-
build/
|
| 12 |
develop-eggs/
|
| 13 |
dist/
|
| 14 |
downloads/
|
| 15 |
eggs/
|
| 16 |
.eggs/
|
| 17 |
-
lib/
|
| 18 |
-
lib64/
|
| 19 |
parts/
|
| 20 |
sdist/
|
| 21 |
var/
|
|
@@ -147,6 +147,10 @@ cython_debug/
|
|
| 147 |
.DS_Store
|
| 148 |
Thumbs.db
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
# Project-specific artifacts
|
| 151 |
*.png
|
| 152 |
temp_*
|
|
|
|
| 8 |
|
| 9 |
# Distribution / packaging
|
| 10 |
.Python
|
| 11 |
+
/build/
|
| 12 |
develop-eggs/
|
| 13 |
dist/
|
| 14 |
downloads/
|
| 15 |
eggs/
|
| 16 |
.eggs/
|
| 17 |
+
/lib/
|
| 18 |
+
/lib64/
|
| 19 |
parts/
|
| 20 |
sdist/
|
| 21 |
var/
|
|
|
|
| 147 |
.DS_Store
|
| 148 |
Thumbs.db
|
| 149 |
|
| 150 |
+
# Next.js
|
| 151 |
+
node_modules/
|
| 152 |
+
.next/
|
| 153 |
+
|
| 154 |
# Project-specific artifacts
|
| 155 |
*.png
|
| 156 |
temp_*
|
pr_review_dashboard.html
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
<style>
|
| 3 |
+
*{box-sizing:border-box;margin:0;padding:0}
|
| 4 |
+
body{font-family:var(--font-sans)}
|
| 5 |
+
.dash{display:grid;grid-template-columns:220px 1fr;min-height:600px;gap:0;border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-lg);overflow:hidden;background:var(--color-background-primary)}
|
| 6 |
+
.sidebar{background:var(--color-background-secondary);border-right:0.5px solid var(--color-border-tertiary);padding:16px;display:flex;flex-direction:column;gap:14px}
|
| 7 |
+
.sidebar-label{font-size:11px;font-weight:500;color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px}
|
| 8 |
+
.sidebar select,.sidebar input{width:100%;font-size:13px;padding:7px 10px;background:var(--color-background-primary);border:0.5px solid var(--color-border-secondary);border-radius:var(--border-radius-md);color:var(--color-text-primary)}
|
| 9 |
+
.init-btn{width:100%;padding:9px;font-size:13px;font-weight:500;background:var(--color-background-primary);border:0.5px solid var(--color-border-secondary);border-radius:var(--border-radius-md);color:var(--color-text-primary);cursor:pointer;transition:background .15s}
|
| 10 |
+
.init-btn:hover{background:var(--color-background-tertiary)}
|
| 11 |
+
.init-btn.active{background:#1a7f3c;color:#fff;border-color:#1a7f3c}
|
| 12 |
+
.main{display:flex;flex-direction:column;gap:0;overflow:hidden}
|
| 13 |
+
.topbar{padding:14px 18px;border-bottom:0.5px solid var(--color-border-tertiary);display:flex;align-items:center;justify-content:space-between}
|
| 14 |
+
.pr-title{font-size:15px;font-weight:500;color:var(--color-text-primary)}
|
| 15 |
+
.pr-sub{font-size:12px;color:var(--color-text-secondary);margin-top:2px}
|
| 16 |
+
.badge{font-size:11px;font-weight:500;padding:4px 10px;border-radius:20px;letter-spacing:.02em}
|
| 17 |
+
.badge.approve{background:#d4edda;color:#1a7f3c}
|
| 18 |
+
.badge.request{background:#fde8e8;color:#c0392b}
|
| 19 |
+
.badge.escalate{background:#fef3cd;color:#856404}
|
| 20 |
+
.badge.running{background:var(--color-background-secondary);color:var(--color-text-secondary)}
|
| 21 |
+
.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:14px 18px;border-bottom:0.5px solid var(--color-border-tertiary)}
|
| 22 |
+
.metric-card{background:var(--color-background-secondary);border-radius:var(--border-radius-md);padding:10px 14px}
|
| 23 |
+
.metric-card .m-label{font-size:11px;color:var(--color-text-tertiary);margin-bottom:4px}
|
| 24 |
+
.metric-card .m-val{font-size:22px;font-weight:500;color:var(--color-text-primary);line-height:1}
|
| 25 |
+
.metric-card .m-sub{font-size:11px;color:var(--color-text-secondary);margin-top:3px}
|
| 26 |
+
.progress-bar{height:4px;background:var(--color-border-tertiary);border-radius:2px;overflow:hidden;margin-top:6px}
|
| 27 |
+
.progress-fill{height:100%;border-radius:2px;transition:width .5s ease;background:#1a7f3c}
|
| 28 |
+
.tabs{display:flex;gap:0;padding:0 18px;border-bottom:0.5px solid var(--color-border-tertiary)}
|
| 29 |
+
.tab{font-size:12px;font-weight:500;padding:9px 14px;cursor:pointer;color:var(--color-text-secondary);border-bottom:2px solid transparent;transition:color .15s}
|
| 30 |
+
.tab.active{color:var(--color-text-primary);border-bottom-color:var(--color-text-primary)}
|
| 31 |
+
.content{flex:1;overflow:auto;padding:16px 18px;display:flex;flex-direction:column;gap:14px}
|
| 32 |
+
.diff-box{background:#0d1117;border-radius:var(--border-radius-md);overflow:hidden;border:0.5px solid #30363d}
|
| 33 |
+
.diff-header{background:#161b22;padding:8px 14px;font-size:11px;color:#8b949e;font-family:var(--font-mono);border-bottom:0.5px solid #30363d;display:flex;justify-content:space-between}
|
| 34 |
+
.diff-body{padding:4px 0;font-family:var(--font-mono);font-size:12px;line-height:1.7}
|
| 35 |
+
.diff-line{padding:1px 14px;display:flex;gap:10px;color:#e6edf3}
|
| 36 |
+
.diff-line .ln{color:#484f58;min-width:28px;text-align:right;user-select:none}
|
| 37 |
+
.diff-line.add{background:rgba(46,160,67,.15);color:#aff5b4}
|
| 38 |
+
.diff-line.add .ln{color:rgba(46,160,67,.5)}
|
| 39 |
+
.diff-line.del{background:rgba(248,81,73,.15);color:#ffa198}
|
| 40 |
+
.diff-line.del .ln{color:rgba(248,81,73,.5)}
|
| 41 |
+
.diff-line.meta{color:#8b949e;background:#161b22}
|
| 42 |
+
.chat-thread{display:flex;flex-direction:column;gap:12px}
|
| 43 |
+
.chat-msg{display:flex;gap:10px;align-items:flex-start}
|
| 44 |
+
.chat-msg.author{flex-direction:row-reverse}
|
| 45 |
+
.avatar{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:500;flex-shrink:0}
|
| 46 |
+
.avatar.reviewer{background:#E6F1FB;color:#185FA5}
|
| 47 |
+
.avatar.author{background:#EAF3DE;color:#3B6D11}
|
| 48 |
+
.bubble{background:var(--color-background-secondary);border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-lg);padding:10px 14px;font-size:13px;color:var(--color-text-primary);max-width:80%;line-height:1.55}
|
| 49 |
+
.bubble.reviewer{border-radius:4px var(--border-radius-lg) var(--border-radius-lg) var(--border-radius-lg)}
|
| 50 |
+
.bubble.author{background:#F0F7FF;border-radius:var(--border-radius-lg) 4px var(--border-radius-lg) var(--border-radius-lg)}
|
| 51 |
+
.chat-meta{font-size:10px;color:var(--color-text-tertiary);margin-top:3px}
|
| 52 |
+
.log-box{background:var(--color-background-secondary);border:0.5px solid var(--color-border-tertiary);border-radius:var(--border-radius-md);overflow:hidden}
|
| 53 |
+
.log-toggle{padding:10px 14px;font-size:12px;cursor:pointer;color:var(--color-text-secondary);display:flex;justify-content:space-between;align-items:center;user-select:none}
|
| 54 |
+
.log-toggle:hover{background:var(--color-background-tertiary)}
|
| 55 |
+
.log-content{display:none;padding:12px 14px;font-family:var(--font-mono);font-size:11px;color:var(--color-text-secondary);border-top:0.5px solid var(--color-border-tertiary);line-height:1.8}
|
| 56 |
+
.log-content.open{display:block}
|
| 57 |
+
.reward-chart{padding:4px 0 8px}
|
| 58 |
+
.chart-row{display:flex;align-items:center;gap:8px;margin-bottom:5px}
|
| 59 |
+
.chart-label{font-size:11px;color:var(--color-text-tertiary);min-width:40px;text-align:right}
|
| 60 |
+
.chart-bar-wrap{flex:1;height:12px;background:var(--color-background-secondary);border-radius:2px;overflow:hidden}
|
| 61 |
+
.chart-bar{height:100%;border-radius:2px;transition:width .6s ease}
|
| 62 |
+
.chart-val{font-size:11px;color:var(--color-text-secondary);min-width:28px}
|
| 63 |
+
.section-head{font-size:11px;font-weight:500;color:var(--color-text-tertiary);text-transform:uppercase;letter-spacing:.06em}
|
| 64 |
+
.pulse{animation:pulse 1.8s ease-in-out infinite}
|
| 65 |
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}}
|
| 66 |
+
.thinking{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--color-text-secondary);padding:4px 0}
|
| 67 |
+
.dot{width:6px;height:6px;border-radius:50%;background:var(--color-text-tertiary)}
|
| 68 |
+
.dot:nth-child(1){animation:blink 1.2s .0s infinite}
|
| 69 |
+
.dot:nth-child(2){animation:blink 1.2s .2s infinite}
|
| 70 |
+
.dot:nth-child(3){animation:blink 1.2s .4s infinite}
|
| 71 |
+
@keyframes blink{0%,80%,100%{opacity:.2}40%{opacity:1}}
|
| 72 |
+
.sep{height:0.5px;background:var(--color-border-tertiary)}
|
| 73 |
+
</style>
|
| 74 |
+
|
| 75 |
+
<h2 class="sr-only">PR Review Command Center — interactive dashboard for AI code review negotiation</h2>
|
| 76 |
+
|
| 77 |
+
<div class="dash" id="dash">
|
| 78 |
+
<div class="sidebar">
|
| 79 |
+
<div>
|
| 80 |
+
<div class="sidebar-label">Task difficulty</div>
|
| 81 |
+
<select id="task-sel">
|
| 82 |
+
<option>Easy — fix typo in docs</option>
|
| 83 |
+
<option selected>Medium — fix pagination offset</option>
|
| 84 |
+
<option>Hard — concurrency race condition</option>
|
| 85 |
+
</select>
|
| 86 |
+
</div>
|
| 87 |
+
<div>
|
| 88 |
+
<div class="sidebar-label">API base URL</div>
|
| 89 |
+
<input type="text" value="https://api.anthropic.com" />
|
| 90 |
+
</div>
|
| 91 |
+
<div>
|
| 92 |
+
<div class="sidebar-label">Model</div>
|
| 93 |
+
<input type="text" value="claude-sonnet-4-20250514" />
|
| 94 |
+
</div>
|
| 95 |
+
<div class="sep"></div>
|
| 96 |
+
<button class="init-btn" id="init-btn" onclick="initEnv()">Initialize environment</button>
|
| 97 |
+
<div style="margin-top:auto">
|
| 98 |
+
<div class="sidebar-label" style="margin-bottom:8px">Reward history</div>
|
| 99 |
+
<div class="reward-chart" id="reward-chart">
|
| 100 |
+
<div class="chart-row"><span class="chart-label">T1</span><div class="chart-bar-wrap"><div class="chart-bar" style="width:35%;background:#e24b4a"></div></div><span class="chart-val">0.35</span></div>
|
| 101 |
+
<div class="chart-row"><span class="chart-label">T2</span><div class="chart-bar-wrap"><div class="chart-bar" style="width:62%;background:#ef9f27"></div></div><span class="chart-val">0.62</span></div>
|
| 102 |
+
<div class="chart-row"><span class="chart-label">T3</span><div class="chart-bar-wrap"><div class="chart-bar" style="width:88%;background:#1a7f3c"></div></div><span class="chart-val">0.88</span></div>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
|
| 107 |
+
<div class="main">
|
| 108 |
+
<div class="topbar">
|
| 109 |
+
<div>
|
| 110 |
+
<div class="pr-title" id="pr-title">Fix pagination offset logic in user listings API</div>
|
| 111 |
+
<div class="pr-sub">#4821 · feat/pagination-fix · opened 2h ago by env-author</div>
|
| 112 |
+
</div>
|
| 113 |
+
<span class="badge request" id="status-badge">REQUEST_CHANGES</span>
|
| 114 |
+
</div>
|
| 115 |
+
|
| 116 |
+
<div class="metrics">
|
| 117 |
+
<div class="metric-card">
|
| 118 |
+
<div class="m-label">Cumulative reward</div>
|
| 119 |
+
<div class="m-val" id="score-val">0.62</div>
|
| 120 |
+
<div class="progress-bar"><div class="progress-fill" id="score-bar" style="width:62%"></div></div>
|
| 121 |
+
</div>
|
| 122 |
+
<div class="metric-card">
|
| 123 |
+
<div class="m-label">Turn</div>
|
| 124 |
+
<div class="m-val" id="turn-val">2 <span style="font-size:14px;color:var(--color-text-tertiary)">/ 3</span></div>
|
| 125 |
+
<div class="m-sub" id="turn-sub">Reviewer processing…</div>
|
| 126 |
+
</div>
|
| 127 |
+
<div class="metric-card">
|
| 128 |
+
<div class="m-label">Episode status</div>
|
| 129 |
+
<div class="m-val" style="font-size:14px;font-weight:500;padding-top:4px" id="ep-status">Running</div>
|
| 130 |
+
<div class="m-sub" id="ep-sub">AI turn active</div>
|
| 131 |
+
</div>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<div class="tabs">
|
| 135 |
+
<div class="tab active" onclick="showTab('diff',this)">Diff view</div>
|
| 136 |
+
<div class="tab" onclick="showTab('timeline',this)">Negotiation timeline</div>
|
| 137 |
+
<div class="tab" onclick="showTab('manual',this)">Manual override</div>
|
| 138 |
+
</div>
|
| 139 |
+
|
| 140 |
+
<div class="content">
|
| 141 |
+
<div id="tab-diff">
|
| 142 |
+
<div class="diff-box">
|
| 143 |
+
<div class="diff-header">
|
| 144 |
+
<span>src/api/users.py</span>
|
| 145 |
+
<span>+8 −4 lines</span>
|
| 146 |
+
</div>
|
| 147 |
+
<div class="diff-body" id="diff-body">
|
| 148 |
+
<div class="diff-line meta"><span class="ln">···</span><span>@@ -42,12 +42,16 @@ def get_users(page, limit):</span></div>
|
| 149 |
+
<div class="diff-line"><span class="ln">42</span><span> query = db.session.query(User)</span></div>
|
| 150 |
+
<div class="diff-line del"><span class="ln">43</span><span>- offset = page * limit</span></div>
|
| 151 |
+
<div class="diff-line add"><span class="ln">43</span><span>+ offset = (page - 1) * limit</span></div>
|
| 152 |
+
<div class="diff-line"><span class="ln">44</span><span> results = query.offset(offset).limit(limit).all()</span></div>
|
| 153 |
+
<div class="diff-line del"><span class="ln">45</span><span>- return results</span></div>
|
| 154 |
+
<div class="diff-line add"><span class="ln">45</span><span>+ if not results and page > 1:</span></div>
|
| 155 |
+
<div class="diff-line add"><span class="ln">46</span><span>+ raise PageOutOfRangeError(page)</span></div>
|
| 156 |
+
<div class="diff-line add"><span class="ln">47</span><span>+ return paginate_response(results, page, limit)</span></div>
|
| 157 |
+
<div class="diff-line"><span class="ln">48</span><span> </span></div>
|
| 158 |
+
<div class="diff-line del"><span class="ln">49</span><span>- # TODO: add error handling</span></div>
|
| 159 |
+
<div class="diff-line add"><span class="ln">49</span><span>+ # Handled via PageOutOfRangeError + paginate_response</span></div>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<div id="tab-timeline" style="display:none">
|
| 165 |
+
<div class="chat-thread">
|
| 166 |
+
<div class="chat-msg">
|
| 167 |
+
<div class="avatar reviewer">AI</div>
|
| 168 |
+
<div>
|
| 169 |
+
<div class="bubble reviewer">The offset calculation on line 43 is incorrect. For 1-indexed pagination, <code style="background:var(--color-background-tertiary);padding:1px 4px;border-radius:3px;font-size:11px">offset = page * limit</code> would skip the first page entirely. Should be <code style="background:var(--color-background-tertiary);padding:1px 4px;border-radius:3px;font-size:11px">(page - 1) * limit</code>.</div>
|
| 170 |
+
<div class="chat-meta">Reviewer · Turn 1</div>
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="chat-msg author">
|
| 174 |
+
<div class="avatar author">Env</div>
|
| 175 |
+
<div>
|
| 176 |
+
<div class="bubble author">Fixed the offset bug. Also added a <code style="background:rgba(255,255,255,.3);padding:1px 4px;border-radius:3px;font-size:11px">PageOutOfRangeError</code> guard and wrapped the return in <code style="background:rgba(255,255,255,.3);padding:1px 4px;border-radius:3px;font-size:11px">paginate_response()</code> for consistency. Take another look!</div>
|
| 177 |
+
<div class="chat-meta" style="text-align:right">Author · Turn 1 response</div>
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
<div class="chat-msg">
|
| 181 |
+
<div class="avatar reviewer">AI</div>
|
| 182 |
+
<div>
|
| 183 |
+
<div class="bubble reviewer">Good fixes. However, <code style="background:var(--color-background-tertiary);padding:1px 4px;border-radius:3px;font-size:11px">paginate_response()</code> is not defined in scope — is it imported? Missing import will cause a runtime error.</div>
|
| 184 |
+
<div class="chat-meta">Reviewer · Turn 2</div>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
<div class="chat-msg">
|
| 188 |
+
<div class="avatar reviewer pulse">AI</div>
|
| 189 |
+
<div>
|
| 190 |
+
<div class="thinking"><div class="dot"></div><div class="dot"></div><div class="dot"></div><span style="margin-left:2px">Reviewer is thinking…</span></div>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
|
| 196 |
+
<div id="tab-manual" style="display:none">
|
| 197 |
+
<div style="font-size:13px;color:var(--color-text-secondary);margin-bottom:12px">Act as the reviewer. Submit feedback to see how the environment responds.</div>
|
| 198 |
+
<textarea id="manual-input" style="width:100%;min-height:80px;font-size:13px;padding:10px;background:var(--color-background-secondary);border:0.5px solid var(--color-border-secondary);border-radius:var(--border-radius-md);color:var(--color-text-primary);resize:vertical;font-family:var(--font-sans)" placeholder="e.g. I found a potential null pointer issue in line 47…"></textarea>
|
| 199 |
+
<div style="display:flex;gap:8px;margin-top:8px">
|
| 200 |
+
<button class="init-btn" onclick="sendManual('REQUEST_CHANGES')">Request changes</button>
|
| 201 |
+
<button class="init-btn active" onclick="sendManual('APPROVE')">Approve</button>
|
| 202 |
+
<button class="init-btn" style="border-color:#856404;color:#856404" onclick="sendManual('ESCALATE')">Escalate</button>
|
| 203 |
+
</div>
|
| 204 |
+
<div id="manual-response" style="margin-top:14px;display:none">
|
| 205 |
+
<div class="section-head" style="margin-bottom:8px">Environment response</div>
|
| 206 |
+
<div class="bubble reviewer" id="manual-resp-text" style="max-width:100%"></div>
|
| 207 |
+
</div>
|
| 208 |
+
</div>
|
| 209 |
+
|
| 210 |
+
<div class="log-box">
|
| 211 |
+
<div class="log-toggle" onclick="toggleLog(this)">
|
| 212 |
+
<span>Behind the scenes — raw logs</span>
|
| 213 |
+
<span id="log-arrow">▶</span>
|
| 214 |
+
</div>
|
| 215 |
+
<div class="log-content" id="log-content">
|
| 216 |
+
[START] episode_id=ep_4821 task=medium seed=42
|
| 217 |
+
[ENV] observation: diff loaded, 8 hunks
|
| 218 |
+
[STEP] turn=1 action=REQUEST_CHANGES reward=0.35
|
| 219 |
+
[AI] tokens_used=312 latency_ms=840
|
| 220 |
+
[STEP] turn=2 action=REQUEST_CHANGES reward=0.62
|
| 221 |
+
[AI] tokens_used=289 latency_ms=760
|
| 222 |
+
[STATE] {"page":2,"max_turns":3,"cumulative_reward":0.62,"done":false}
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
</div>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
|
| 229 |
+
<script>
|
| 230 |
+
function showTab(name, el) {
|
| 231 |
+
['diff','timeline','manual'].forEach(t => {
|
| 232 |
+
document.getElementById('tab-'+t).style.display = t===name?'':'none';
|
| 233 |
+
});
|
| 234 |
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
| 235 |
+
el.classList.add('active');
|
| 236 |
+
}
|
| 237 |
+
function toggleLog(el) {
|
| 238 |
+
const c = document.getElementById('log-content');
|
| 239 |
+
const a = document.getElementById('log-arrow');
|
| 240 |
+
const open = c.classList.toggle('open');
|
| 241 |
+
a.textContent = open ? '▼' : '▶';
|
| 242 |
+
}
|
| 243 |
+
function initEnv() {
|
| 244 |
+
const btn = document.getElementById('init-btn');
|
| 245 |
+
const tasks = ['Easy — fix typo in docs','Medium — fix pagination offset','Hard — concurrency race condition'];
|
| 246 |
+
const sel = document.getElementById('task-sel').value;
|
| 247 |
+
const titles = {
|
| 248 |
+
'Easy': 'Fix spelling in README introduction',
|
| 249 |
+
'Medium': 'Fix pagination offset logic in user listings API',
|
| 250 |
+
'Hard': 'Resolve race condition in concurrent write handler'
|
| 251 |
+
};
|
| 252 |
+
const key = sel.startsWith('Easy')?'Easy':sel.startsWith('Medium')?'Medium':'Hard';
|
| 253 |
+
document.getElementById('pr-title').textContent = titles[key];
|
| 254 |
+
document.getElementById('score-val').textContent = '0.00';
|
| 255 |
+
document.getElementById('score-bar').style.width = '0%';
|
| 256 |
+
document.getElementById('turn-val').innerHTML = '1 <span style="font-size:14px;color:var(--color-text-tertiary)">/ 3</span>';
|
| 257 |
+
document.getElementById('turn-sub').textContent = 'Starting…';
|
| 258 |
+
document.getElementById('ep-status').textContent = 'Initializing';
|
| 259 |
+
document.getElementById('ep-sub').textContent = 'Episode reset';
|
| 260 |
+
document.getElementById('status-badge').className = 'badge running';
|
| 261 |
+
document.getElementById('status-badge').textContent = 'RUNNING';
|
| 262 |
+
btn.textContent = 'Initializing…';
|
| 263 |
+
btn.classList.add('active');
|
| 264 |
+
setTimeout(() => {
|
| 265 |
+
btn.textContent = 'Environment ready';
|
| 266 |
+
document.getElementById('ep-status').textContent = 'Running';
|
| 267 |
+
document.getElementById('ep-sub').textContent = 'AI turn active';
|
| 268 |
+
document.getElementById('turn-sub').textContent = 'Reviewer processing…';
|
| 269 |
+
}, 1200);
|
| 270 |
+
}
|
| 271 |
+
const envResponses = {
|
| 272 |
+
REQUEST_CHANGES: "Acknowledged. I've addressed the points raised — updated the import for paginate_response and added a unit test. Please take another look.",
|
| 273 |
+
APPROVE: "Thanks for the approval! Merging into main. All checks passed.",
|
| 274 |
+
ESCALATE: "Understood, escalating to the senior reviewer. I'll add more context to the PR description."
|
| 275 |
+
};
|
| 276 |
+
function sendManual(decision) {
|
| 277 |
+
const input = document.getElementById('manual-input').value.trim();
|
| 278 |
+
const badges = {REQUEST_CHANGES:'request',APPROVE:'approve',ESCALATE:'escalate'};
|
| 279 |
+
document.getElementById('status-badge').className = 'badge ' + badges[decision];
|
| 280 |
+
document.getElementById('status-badge').textContent = decision;
|
| 281 |
+
const resp = document.getElementById('manual-response');
|
| 282 |
+
const txt = document.getElementById('manual-resp-text');
|
| 283 |
+
resp.style.display = 'block';
|
| 284 |
+
txt.textContent = '…';
|
| 285 |
+
setTimeout(() => { txt.textContent = envResponses[decision]; }, 700);
|
| 286 |
+
}
|
| 287 |
+
</script>
|
pr_review_dashboard/lib/api.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const BASE = "/api/env";
|
| 2 |
+
|
| 3 |
+
export async function resetEnv(taskName) {
|
| 4 |
+
const res = await fetch(`${BASE}/reset`, {
|
| 5 |
+
method: "POST",
|
| 6 |
+
headers: { "Content-Type": "application/json" },
|
| 7 |
+
body: JSON.stringify({ task_name: taskName }),
|
| 8 |
+
});
|
| 9 |
+
if (!res.ok) throw new Error(`Reset failed: ${res.status}`);
|
| 10 |
+
return res.json();
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export async function stepEnv(action) {
|
| 14 |
+
const res = await fetch(`${BASE}/step`, {
|
| 15 |
+
method: "POST",
|
| 16 |
+
headers: { "Content-Type": "application/json" },
|
| 17 |
+
body: JSON.stringify({ action }),
|
| 18 |
+
});
|
| 19 |
+
if (!res.ok) throw new Error(`Step failed: ${res.status}`);
|
| 20 |
+
return res.json();
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
export async function configCustom({ diff, pr_title, pr_description }) {
|
| 24 |
+
const res = await fetch(`${BASE}/config/custom`, {
|
| 25 |
+
method: "POST",
|
| 26 |
+
headers: { "Content-Type": "application/json" },
|
| 27 |
+
body: JSON.stringify({ diff, pr_title, pr_description }),
|
| 28 |
+
});
|
| 29 |
+
if (!res.ok) throw new Error(`Config failed: ${res.status}`);
|
| 30 |
+
return res.json();
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export async function callAgent({ observation, modelId, apiUrl, apiKey }) {
|
| 34 |
+
const res = await fetch("/api/agent", {
|
| 35 |
+
method: "POST",
|
| 36 |
+
headers: { "Content-Type": "application/json" },
|
| 37 |
+
body: JSON.stringify({ observation, modelId, apiUrl, apiKey }),
|
| 38 |
+
});
|
| 39 |
+
if (!res.ok) throw new Error(`Agent failed: ${res.status}`);
|
| 40 |
+
return res.json();
|
| 41 |
+
}
|