Skip to content

Commit c868d38

Browse files
committed
Add feedback dropdown to web UI statusbar
Clicking "Feedback: N" in the statusbar now opens a dropdown showing all submitted feedback (pipeline-level and doc-level) with timestamps and labels. Fetches from /feedback/poll on open, closes on outside click. https://claude.ai/code/session_013WUwghjHVh6r5mRHEMuhGP
1 parent 24abc98 commit c868d38

1 file changed

Lines changed: 84 additions & 2 deletions

File tree

docetl/tui/web_reporter.py

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,32 @@ def _serve_sse(self):
815815
padding: 5px 16px; font-size: 12px; color: var(--muted-foreground);
816816
display: flex; gap: 16px; flex-shrink: 0; align-items: center;
817817
box-shadow: 0 -1px 3px 0 rgb(0 0 0 / .04);
818-
}
818+
position: relative;
819+
}
820+
.fb-toggle { cursor: pointer; user-select: none; }
821+
.fb-toggle:hover { color: var(--foreground); }
822+
.fb-dropdown {
823+
display: none; position: absolute; bottom: 100%; left: 50%;
824+
transform: translateX(-50%);
825+
width: 480px; max-height: 360px; overflow-y: auto;
826+
background: white; border: 1px solid var(--border);
827+
border-radius: var(--radius); box-shadow: 0 -4px 20px rgba(0,0,0,.12);
828+
padding: 12px; z-index: 60; margin-bottom: 4px;
829+
}
830+
.fb-dropdown.open { display: block; }
831+
.fb-dropdown-title { font-size: 13px; font-weight: 600; color: var(--foreground); margin-bottom: 8px; }
832+
.fb-dropdown-empty { font-size: 12px; color: var(--muted-foreground); font-style: italic; }
833+
.fb-item {
834+
padding: 8px 10px; margin-bottom: 6px; border-radius: var(--radius);
835+
border: 1px solid var(--border); font-size: 12px; line-height: 1.5;
836+
}
837+
.fb-item-pipeline { background: hsl(211 40% 97%); }
838+
.fb-item-doc { background: hsl(38 100% 97%); }
839+
.fb-item-label {
840+
font-size: 10px; font-weight: 600; text-transform: uppercase;
841+
letter-spacing: .04em; color: var(--muted-foreground); margin-bottom: 3px;
842+
}
843+
.fb-item-text { color: var(--foreground); }
819844
.status-dot {
820845
width: 6px; height: 6px; border-radius: 50%;
821846
display: inline-block; margin-right: 4px;
@@ -931,7 +956,8 @@ def _serve_sse(self):
931956
<div class="statusbar">
932957
<span><span class="status-dot live" id="status-dot"></span> <span id="f-status">Connecting…</span></span>
933958
<span id="f-rows">0 rows</span>
934-
<span id="f-feedback">Feedback: 0</span>
959+
<span class="fb-toggle" id="f-feedback" onclick="toggleFeedbackDropdown()">Feedback: 0</span>
960+
<div class="fb-dropdown" id="fb-dropdown"></div>
935961
</div>
936962
937963
<div class="toast-container" id="toasts"></div>
@@ -1680,11 +1706,67 @@ def _serve_sse(self):
16801706
method: 'POST',
16811707
headers: {'Content-Type': 'application/json'},
16821708
body: JSON.stringify({ text: text })
1709+
}).then(() => {
1710+
allFeedback.pipeline.push({ feedback: text, timestamp: new Date().toISOString().slice(0,19) });
1711+
const total = allFeedback.doc.length + allFeedback.pipeline.length;
1712+
document.getElementById('f-feedback').textContent = 'Feedback: ' + total;
16831713
});
16841714
input.value = '';
16851715
input.placeholder = 'Sent! Type more…';
16861716
}
16871717
1718+
let allFeedback = { doc: [], pipeline: [] };
1719+
1720+
function toggleFeedbackDropdown() {
1721+
const dd = document.getElementById('fb-dropdown');
1722+
if (dd.classList.contains('open')) {
1723+
dd.classList.remove('open');
1724+
return;
1725+
}
1726+
fetch('/feedback/poll')
1727+
.then(r => r.json())
1728+
.then(data => {
1729+
allFeedback.doc = data.doc_feedback || [];
1730+
allFeedback.pipeline = data.pipeline_feedback || [];
1731+
renderFeedbackDropdown();
1732+
dd.classList.add('open');
1733+
})
1734+
.catch(() => {});
1735+
}
1736+
1737+
function renderFeedbackDropdown() {
1738+
const dd = document.getElementById('fb-dropdown');
1739+
const total = allFeedback.doc.length + allFeedback.pipeline.length;
1740+
if (total === 0) {
1741+
dd.innerHTML = '<div class="fb-dropdown-title">All Feedback</div>' +
1742+
'<div class="fb-dropdown-empty">No feedback submitted yet.</div>';
1743+
return;
1744+
}
1745+
let html = '<div class="fb-dropdown-title">All Feedback (' + total + ')</div>';
1746+
allFeedback.pipeline.forEach(fb => {
1747+
html += '<div class="fb-item fb-item-pipeline">' +
1748+
'<div class="fb-item-label">Pipeline · ' + (fb.timestamp || '') + '</div>' +
1749+
'<div class="fb-item-text">' + escHtml(fb.feedback) + '</div>' +
1750+
'</div>';
1751+
});
1752+
allFeedback.doc.forEach(fb => {
1753+
const opShort = (fb.operation || '').split('/').pop();
1754+
html += '<div class="fb-item fb-item-doc">' +
1755+
'<div class="fb-item-label">Doc #' + fb.doc_index + ' · ' + opShort + ' · ' + (fb.timestamp || '') + '</div>' +
1756+
'<div class="fb-item-text">' + escHtml(fb.feedback) + '</div>' +
1757+
'</div>';
1758+
});
1759+
dd.innerHTML = html;
1760+
}
1761+
1762+
document.addEventListener('click', function(e) {
1763+
const dd = document.getElementById('fb-dropdown');
1764+
const toggle = document.getElementById('f-feedback');
1765+
if (dd.classList.contains('open') && !dd.contains(e.target) && e.target !== toggle) {
1766+
dd.classList.remove('open');
1767+
}
1768+
});
1769+
16881770
function killPipeline() {
16891771
const reason = prompt('Reason for stopping (optional):') || '';
16901772
if (killed) return;

0 commit comments

Comments
 (0)