Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
| // evaluate.js - Supervisor page (/evaluate) | |
| // | |
| // Lists submitted critiques (fetched from the server), lets a supervisor | |
| // select one, review the question / answer / critique, then write an | |
| // evaluation and grade the critique 1-5. The evaluation is saved as one | |
| // record and the form locks after submission. | |
| import { Utils } from './utils.js'; | |
| const userId = Utils.getMachineId(); | |
| const sessionId = Utils.generateSessionId(); | |
| const RATING_LABELS = { good: 'Good', bad: 'Bad', mixed: 'Mixed' }; | |
| // -------------------- State -------------------- | |
| let critiques = []; | |
| let currentCritique = null; | |
| // -------------------- Elements -------------------- | |
| const listView = document.getElementById('critique-list-view'); | |
| const detailView = document.getElementById('critique-detail-view'); | |
| const critiqueList = document.getElementById('critique-list'); | |
| const critiqueListStatus = document.getElementById('critique-list-status'); | |
| const backBtn = document.getElementById('back-btn'); | |
| const detailQuestion = document.getElementById('detail-question'); | |
| const detailAnswer = document.getElementById('detail-answer'); | |
| const detailAnswerGrade = document.getElementById('detail-answer-grade'); | |
| const detailGeneralComment = document.getElementById('detail-general-comment'); | |
| const detailHighlights = document.getElementById('detail-highlights'); | |
| const evaluationForm = document.getElementById('evaluation-form'); | |
| const evaluationDone = document.getElementById('evaluation-done'); | |
| const evaluationCommentInput = document.getElementById('evaluation-comment-input'); | |
| const submitEvaluationBtn = document.getElementById('submit-evaluation-btn'); | |
| const evaluationStatus = document.getElementById('evaluation-status'); | |
| // -------------------- Helpers -------------------- | |
| async function postJson(url, payload) { | |
| const res = await fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload), | |
| }); | |
| if (!res.ok) { | |
| throw new Error(`HTTP ${res.status}`); | |
| } | |
| return res; | |
| } | |
| function renderMarkdown(target, markdownText) { | |
| const text = markdownText || ''; | |
| if (window.marked && window.DOMPurify) { | |
| target.innerHTML = DOMPurify.sanitize(marked.parse(text)); | |
| } else { | |
| target.textContent = text; | |
| } | |
| } | |
| function truncate(text, maxLength) { | |
| return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text; | |
| } | |
| function getSelectedGrade() { | |
| const checked = document.querySelector('input[name="critique-grade"]:checked'); | |
| return checked ? Number(checked.value) : null; | |
| } | |
| // -------------------- Load critique list -------------------- | |
| async function loadCritiques() { | |
| critiqueListStatus.textContent = 'Loading critiques…'; | |
| try { | |
| const res = await fetch('/critiques'); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}`); | |
| const data = await res.json(); | |
| critiques = data.critiques || []; | |
| renderCritiqueList(); | |
| } catch (err) { | |
| critiqueListStatus.textContent = 'Could not load critiques. Please refresh the page.'; | |
| } | |
| } | |
| function renderCritiqueList() { | |
| critiqueList.innerHTML = ''; | |
| if (critiques.length === 0) { | |
| critiqueListStatus.textContent = 'No critiques have been submitted yet.'; | |
| return; | |
| } | |
| critiqueListStatus.textContent = ''; | |
| critiques.forEach((critique) => { | |
| const item = document.createElement('li'); | |
| const btn = document.createElement('button'); | |
| btn.type = 'button'; | |
| btn.className = 'critique-select-btn'; | |
| const question = document.createElement('span'); | |
| question.textContent = truncate(critique.question || '(question unavailable)', 120); | |
| const meta = document.createElement('span'); | |
| const count = (critique.highlights || []).length; | |
| meta.textContent = ` — grade ${critique.answer_grade ?? '?'}/5, ${count} highlight review(s)`; | |
| btn.append(question, meta); | |
| btn.addEventListener('click', () => selectCritique(critique)); | |
| item.appendChild(btn); | |
| critiqueList.appendChild(item); | |
| }); | |
| } | |
| // -------------------- Select a critique -------------------- | |
| function selectCritique(critique) { | |
| currentCritique = critique; | |
| detailQuestion.textContent = critique.question || ''; | |
| renderMarkdown(detailAnswer, critique.answer); | |
| detailAnswerGrade.textContent = critique.answer_grade ?? '?'; | |
| detailGeneralComment.textContent = critique.general_comment || '(no general comment)'; | |
| detailHighlights.innerHTML = ''; | |
| const highlights = critique.highlights || []; | |
| if (highlights.length === 0) { | |
| const empty = document.createElement('li'); | |
| empty.textContent = '(no highlight reviews)'; | |
| detailHighlights.appendChild(empty); | |
| } else { | |
| highlights.forEach((hl) => { | |
| const item = document.createElement('li'); | |
| const label = document.createElement('span'); | |
| label.textContent = `${RATING_LABELS[hl.rating] || hl.rating} — `; | |
| const quote = document.createElement('q'); | |
| quote.textContent = truncate(hl.highlighted_text || '', 200); | |
| item.append(label, quote); | |
| if (hl.comment) { | |
| item.append(` : ${hl.comment}`); | |
| } | |
| detailHighlights.appendChild(item); | |
| }); | |
| } | |
| // Reset the evaluation form. | |
| evaluationForm.hidden = false; | |
| evaluationDone.hidden = true; | |
| evaluationCommentInput.value = ''; | |
| submitEvaluationBtn.disabled = false; | |
| evaluationStatus.textContent = ''; | |
| document | |
| .querySelectorAll('input[name="critique-grade"]') | |
| .forEach((radio) => (radio.checked = false)); | |
| listView.hidden = true; | |
| detailView.hidden = false; | |
| window.scrollTo(0, 0); | |
| } | |
| backBtn.addEventListener('click', () => { | |
| currentCritique = null; | |
| detailView.hidden = true; | |
| listView.hidden = false; | |
| }); | |
| // -------------------- Submit evaluation -------------------- | |
| submitEvaluationBtn.addEventListener('click', async () => { | |
| if (!currentCritique) return; | |
| const grade = getSelectedGrade(); | |
| if (grade === null) { | |
| evaluationStatus.textContent = 'Please grade the critique (1–5) before submitting.'; | |
| return; | |
| } | |
| submitEvaluationBtn.disabled = true; | |
| evaluationStatus.textContent = 'Submitting…'; | |
| try { | |
| await postJson('/evaluations', { | |
| user_id: userId, | |
| session_id: sessionId, | |
| critique_id: currentCritique.critique_id, | |
| critique_grade: grade, | |
| comment: evaluationCommentInput.value.trim(), | |
| }); | |
| // Lock the evaluation. | |
| evaluationForm.hidden = true; | |
| evaluationDone.hidden = false; | |
| } catch (err) { | |
| submitEvaluationBtn.disabled = false; | |
| evaluationStatus.textContent = 'Could not submit the evaluation. Please try again.'; | |
| } | |
| }); | |
| // -------------------- Init -------------------- | |
| loadCritiques(); | |