@@ -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+
16881770function killPipeline() {
16891771 const reason = prompt('Reason for stopping (optional):') || '';
16901772 if (killed) return;
0 commit comments