Update app.py
Browse files
app.py
CHANGED
@@ -3,7 +3,6 @@ import json
|
|
3 |
import uuid
|
4 |
from datetime import datetime
|
5 |
import os
|
6 |
-
import pandas as pd
|
7 |
|
8 |
class LegalTechEvaluator:
|
9 |
def __init__(self):
|
@@ -45,6 +44,7 @@ class LegalTechEvaluator:
|
|
45 |
"responses": {},
|
46 |
"completed": False
|
47 |
}
|
|
|
48 |
return user_id
|
49 |
|
50 |
def record_choice(self, sample_choice):
|
@@ -134,8 +134,8 @@ Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
|
134 |
"""
|
135 |
|
136 |
for idx in range(len(self.data)):
|
137 |
-
if
|
138 |
-
stats = question_stats[
|
139 |
total_responses = sum(stats.values())
|
140 |
if total_responses > 0:
|
141 |
question = self.data[idx]
|
@@ -169,6 +169,13 @@ custom_css = """
|
|
169 |
max-width: 1200px !important;
|
170 |
margin: 0 auto !important;
|
171 |
padding: 0 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
}
|
173 |
|
174 |
#main-container {
|
@@ -201,26 +208,28 @@ custom_css = """
|
|
201 |
line-height: 1.6;
|
202 |
}
|
203 |
|
204 |
-
#name-input {
|
205 |
max-width: 400px;
|
206 |
margin: 0 auto 24px;
|
207 |
}
|
208 |
|
209 |
-
#name-input input {
|
210 |
font-size: 16px;
|
211 |
padding: 12px 20px;
|
212 |
border: 2px solid #e0e0e0;
|
213 |
border-radius: 8px;
|
214 |
width: 100%;
|
215 |
transition: all 0.3s ease;
|
|
|
|
|
216 |
}
|
217 |
|
218 |
-
#name-input input:focus {
|
219 |
border-color: #4a90e2;
|
220 |
outline: none;
|
221 |
}
|
222 |
|
223 |
-
|
224 |
background: #2563eb;
|
225 |
color: white;
|
226 |
border: none;
|
@@ -232,7 +241,7 @@ custom_css = """
|
|
232 |
transition: all 0.3s ease;
|
233 |
}
|
234 |
|
235 |
-
|
236 |
background: #1d4ed8;
|
237 |
transform: translateY(-1px);
|
238 |
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
@@ -391,7 +400,7 @@ custom_css = """
|
|
391 |
border-top: 2px solid #e5e7eb;
|
392 |
}
|
393 |
|
394 |
-
|
395 |
background: #6366f1;
|
396 |
color: white;
|
397 |
padding: 14px 40px;
|
@@ -403,7 +412,7 @@ custom_css = """
|
|
403 |
transition: all 0.3s ease;
|
404 |
}
|
405 |
|
406 |
-
|
407 |
background: #4f46e5;
|
408 |
transform: translateY(-1px);
|
409 |
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
@@ -428,6 +437,10 @@ custom_css = """
|
|
428 |
display: none !important;
|
429 |
}
|
430 |
|
|
|
|
|
|
|
|
|
431 |
.success-message {
|
432 |
background: #d1fae5;
|
433 |
color: #065f46;
|
@@ -450,19 +463,163 @@ custom_css = """
|
|
450 |
}
|
451 |
"""
|
452 |
|
453 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
html_template = """
|
455 |
<div id="main-container">
|
456 |
<div id="welcome-screen">
|
457 |
<h1>Legal Tech Tool Evaluation</h1>
|
458 |
<p>Compare and evaluate outputs from different legal technology tools to help us understand user preferences.</p>
|
459 |
-
<div id="name-input">
|
460 |
-
|
461 |
</div>
|
462 |
-
<button id="start-button" onclick="startEvaluation()">Start Evaluation</button>
|
463 |
</div>
|
464 |
|
465 |
-
<div id="evaluation-container"
|
466 |
<div class="user-info" id="user-info"></div>
|
467 |
|
468 |
<div id="question-header">
|
@@ -497,136 +654,22 @@ html_template = """
|
|
497 |
|
498 |
<div id="action-buttons">
|
499 |
<button class="nav-button" id="prev-button" onclick="previousQuestion()">← Previous</button>
|
500 |
-
<button class="nav-button" id="confirm-button" onclick="confirmSelection()">Confirm Selection</button>
|
501 |
<button class="nav-button" id="next-button" onclick="nextQuestion()">Next →</button>
|
502 |
</div>
|
503 |
|
504 |
<div id="export-section">
|
505 |
-
<button
|
506 |
<div id="export-status"></div>
|
507 |
</div>
|
508 |
</div>
|
509 |
|
510 |
-
<div id="file-upload-section"
|
511 |
<h3>No data file found</h3>
|
512 |
<p>Please upload a JSONL file to begin the evaluation</p>
|
513 |
-
|
514 |
</div>
|
515 |
</div>
|
516 |
-
|
517 |
-
<script>
|
518 |
-
let currentSelection = null;
|
519 |
-
let evaluationStarted = false;
|
520 |
-
|
521 |
-
function startEvaluation() {
|
522 |
-
const name = document.getElementById('user-name').value.trim();
|
523 |
-
if (!name) {
|
524 |
-
alert('Please enter your name');
|
525 |
-
return;
|
526 |
-
}
|
527 |
-
|
528 |
-
// Call Gradio function to start session
|
529 |
-
window.gradioApp().querySelector('#start-session-btn').click();
|
530 |
-
}
|
531 |
-
|
532 |
-
function selectSample(sampleIndex) {
|
533 |
-
// Clear previous selections
|
534 |
-
document.querySelectorAll('.sample-card').forEach(card => {
|
535 |
-
card.classList.remove('selected');
|
536 |
-
});
|
537 |
-
|
538 |
-
// Add selection to clicked sample
|
539 |
-
document.getElementById(`sample-${sampleIndex}`).classList.add('selected');
|
540 |
-
currentSelection = sampleIndex;
|
541 |
-
|
542 |
-
// Enable confirm button
|
543 |
-
document.getElementById('confirm-button').disabled = false;
|
544 |
-
}
|
545 |
-
|
546 |
-
function updateDisplay(data) {
|
547 |
-
if (!data) return;
|
548 |
-
|
549 |
-
// Parse the data
|
550 |
-
const questionData = JSON.parse(data);
|
551 |
-
|
552 |
-
if (questionData.showWelcome) {
|
553 |
-
document.getElementById('welcome-screen').classList.remove('hide');
|
554 |
-
document.getElementById('evaluation-container').classList.add('hide');
|
555 |
-
document.getElementById('file-upload-section').classList.add('hide');
|
556 |
-
} else if (questionData.showFileUpload) {
|
557 |
-
document.getElementById('welcome-screen').classList.add('hide');
|
558 |
-
document.getElementById('evaluation-container').classList.add('hide');
|
559 |
-
document.getElementById('file-upload-section').classList.remove('hide');
|
560 |
-
} else {
|
561 |
-
document.getElementById('welcome-screen').classList.add('hide');
|
562 |
-
document.getElementById('evaluation-container').classList.remove('hide');
|
563 |
-
document.getElementById('file-upload-section').classList.add('hide');
|
564 |
-
|
565 |
-
// Update user info
|
566 |
-
if (questionData.userName) {
|
567 |
-
document.getElementById('user-info').textContent = `Evaluator: ${questionData.userName}`;
|
568 |
-
}
|
569 |
-
|
570 |
-
// Update question info
|
571 |
-
document.getElementById('current-question').textContent = questionData.currentIndex + 1;
|
572 |
-
document.getElementById('total-questions').textContent = questionData.totalQuestions;
|
573 |
-
|
574 |
-
// Update example and samples
|
575 |
-
document.getElementById('example-text').textContent = questionData.introductoryExample;
|
576 |
-
document.getElementById('sample-0-text').textContent = questionData.sampleZero;
|
577 |
-
document.getElementById('sample-1-text').textContent = questionData.sampleOne;
|
578 |
-
document.getElementById('sample-2-text').textContent = questionData.sampleTwo;
|
579 |
-
|
580 |
-
// Update button states
|
581 |
-
document.getElementById('prev-button').disabled = questionData.currentIndex === 0;
|
582 |
-
document.getElementById('next-button').disabled = questionData.currentIndex >= questionData.totalQuestions - 1;
|
583 |
-
document.getElementById('confirm-button').disabled = true;
|
584 |
-
|
585 |
-
// Clear selection
|
586 |
-
document.querySelectorAll('.sample-card').forEach(card => {
|
587 |
-
card.classList.remove('selected');
|
588 |
-
});
|
589 |
-
currentSelection = null;
|
590 |
-
}
|
591 |
-
}
|
592 |
-
|
593 |
-
function confirmSelection() {
|
594 |
-
if (currentSelection === null) return;
|
595 |
-
|
596 |
-
const sampleMap = ['sample_zero', 'sample_one', 'sample_two'];
|
597 |
-
// Trigger Gradio function with selection
|
598 |
-
window.gradioApp().querySelector('#hidden-selection').value = sampleMap[currentSelection];
|
599 |
-
window.gradioApp().querySelector('#confirm-btn').click();
|
600 |
-
}
|
601 |
-
|
602 |
-
function previousQuestion() {
|
603 |
-
window.gradioApp().querySelector('#prev-btn').click();
|
604 |
-
}
|
605 |
-
|
606 |
-
function nextQuestion() {
|
607 |
-
window.gradioApp().querySelector('#next-btn').click();
|
608 |
-
}
|
609 |
-
|
610 |
-
function exportResults() {
|
611 |
-
window.gradioApp().querySelector('#export-btn').click();
|
612 |
-
}
|
613 |
-
|
614 |
-
function uploadFile() {
|
615 |
-
const fileInput = document.getElementById('file-input');
|
616 |
-
if (fileInput.files.length > 0) {
|
617 |
-
// Trigger Gradio file upload
|
618 |
-
window.gradioApp().querySelector('#file-upload-btn').click();
|
619 |
-
}
|
620 |
-
}
|
621 |
-
|
622 |
-
// Listen for Gradio updates
|
623 |
-
setInterval(() => {
|
624 |
-
const stateElement = window.gradioApp().querySelector('#current-state');
|
625 |
-
if (stateElement && stateElement.value) {
|
626 |
-
updateDisplay(stateElement.value);
|
627 |
-
}
|
628 |
-
}, 100);
|
629 |
-
</script>
|
630 |
"""
|
631 |
|
632 |
def start_session(name):
|
@@ -644,9 +687,12 @@ def get_current_state():
|
|
644 |
"""Get current state as JSON"""
|
645 |
question = evaluator.get_current_question()
|
646 |
|
647 |
-
if not
|
648 |
return json.dumps({"showWelcome": True})
|
649 |
|
|
|
|
|
|
|
650 |
return json.dumps({
|
651 |
"showWelcome": False,
|
652 |
"showFileUpload": False,
|
@@ -683,26 +729,19 @@ def export_results_handler():
|
|
683 |
"""Export results and return status"""
|
684 |
try:
|
685 |
jsonl_file, md_file, json_file, md_content = evaluator.export_results()
|
686 |
-
|
687 |
-
# Read files for download
|
688 |
-
with open(jsonl_file, 'r') as f:
|
689 |
-
jsonl_content = f.read()
|
690 |
-
with open(md_file, 'r') as f:
|
691 |
-
md_download = f.read()
|
692 |
-
|
693 |
return (
|
694 |
get_current_state(),
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
md_content
|
699 |
)
|
700 |
except Exception as e:
|
701 |
return (
|
702 |
get_current_state(),
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
""
|
707 |
)
|
708 |
|
@@ -713,19 +752,29 @@ def load_file(file):
|
|
713 |
return get_current_state()
|
714 |
|
715 |
# Create Gradio interface
|
716 |
-
with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
717 |
# Hidden components for JavaScript interaction
|
718 |
with gr.Column(visible=False):
|
719 |
-
|
720 |
start_btn = gr.Button("Start", elem_id="start-session-btn")
|
721 |
-
current_state = gr.Textbox(elem_id="current-state")
|
722 |
selection_input = gr.Textbox(elem_id="hidden-selection")
|
723 |
confirm_btn = gr.Button("Confirm", elem_id="confirm-btn")
|
724 |
prev_btn = gr.Button("Previous", elem_id="prev-btn")
|
725 |
next_btn = gr.Button("Next", elem_id="next-btn")
|
726 |
export_btn = gr.Button("Export", elem_id="export-btn")
|
727 |
-
file_upload = gr.File(elem_id="file-upload-input")
|
728 |
-
file_upload_btn = gr.Button("Upload", elem_id="file-upload-btn")
|
729 |
|
730 |
# Output components (hidden)
|
731 |
with gr.Column(visible=False):
|
@@ -734,13 +783,16 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as demo:
|
|
734 |
download_md = gr.File(label="Download Summary")
|
735 |
summary_display = gr.Markdown()
|
736 |
|
737 |
-
# Main HTML interface
|
738 |
-
gr.HTML(html_template)
|
739 |
-
|
740 |
# Event handlers
|
|
|
|
|
|
|
|
|
|
|
|
|
741 |
start_btn.click(
|
742 |
-
fn=
|
743 |
-
inputs=[
|
744 |
outputs=[current_state]
|
745 |
)
|
746 |
|
@@ -765,16 +817,41 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Base()) as demo:
|
|
765 |
outputs=[current_state, export_status, download_jsonl, download_md, summary_display]
|
766 |
)
|
767 |
|
768 |
-
|
769 |
fn=load_file,
|
770 |
inputs=[file_upload],
|
771 |
outputs=[current_state]
|
772 |
)
|
773 |
|
774 |
-
#
|
775 |
demo.load(
|
776 |
-
fn=
|
777 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
778 |
)
|
779 |
|
780 |
# Create sample data if no file exists
|
|
|
3 |
import uuid
|
4 |
from datetime import datetime
|
5 |
import os
|
|
|
6 |
|
7 |
class LegalTechEvaluator:
|
8 |
def __init__(self):
|
|
|
44 |
"responses": {},
|
45 |
"completed": False
|
46 |
}
|
47 |
+
self.current_index = 0
|
48 |
return user_id
|
49 |
|
50 |
def record_choice(self, sample_choice):
|
|
|
134 |
"""
|
135 |
|
136 |
for idx in range(len(self.data)):
|
137 |
+
if idx in question_stats:
|
138 |
+
stats = question_stats[idx]
|
139 |
total_responses = sum(stats.values())
|
140 |
if total_responses > 0:
|
141 |
question = self.data[idx]
|
|
|
169 |
max-width: 1200px !important;
|
170 |
margin: 0 auto !important;
|
171 |
padding: 0 !important;
|
172 |
+
background: transparent !important;
|
173 |
+
}
|
174 |
+
|
175 |
+
/* Fix for input text visibility */
|
176 |
+
input[type="text"] {
|
177 |
+
color: #1f2937 !important;
|
178 |
+
background-color: white !important;
|
179 |
}
|
180 |
|
181 |
#main-container {
|
|
|
208 |
line-height: 1.6;
|
209 |
}
|
210 |
|
211 |
+
#name-input-container {
|
212 |
max-width: 400px;
|
213 |
margin: 0 auto 24px;
|
214 |
}
|
215 |
|
216 |
+
#name-input-container input {
|
217 |
font-size: 16px;
|
218 |
padding: 12px 20px;
|
219 |
border: 2px solid #e0e0e0;
|
220 |
border-radius: 8px;
|
221 |
width: 100%;
|
222 |
transition: all 0.3s ease;
|
223 |
+
color: #1f2937 !important;
|
224 |
+
background-color: white !important;
|
225 |
}
|
226 |
|
227 |
+
#name-input-container input:focus {
|
228 |
border-color: #4a90e2;
|
229 |
outline: none;
|
230 |
}
|
231 |
|
232 |
+
.start-button {
|
233 |
background: #2563eb;
|
234 |
color: white;
|
235 |
border: none;
|
|
|
241 |
transition: all 0.3s ease;
|
242 |
}
|
243 |
|
244 |
+
.start-button:hover {
|
245 |
background: #1d4ed8;
|
246 |
transform: translateY(-1px);
|
247 |
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
|
|
|
400 |
border-top: 2px solid #e5e7eb;
|
401 |
}
|
402 |
|
403 |
+
.export-button {
|
404 |
background: #6366f1;
|
405 |
color: white;
|
406 |
padding: 14px 40px;
|
|
|
412 |
transition: all 0.3s ease;
|
413 |
}
|
414 |
|
415 |
+
.export-button:hover {
|
416 |
background: #4f46e5;
|
417 |
transform: translateY(-1px);
|
418 |
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
|
|
|
437 |
display: none !important;
|
438 |
}
|
439 |
|
440 |
+
.show {
|
441 |
+
display: block !important;
|
442 |
+
}
|
443 |
+
|
444 |
.success-message {
|
445 |
background: #d1fae5;
|
446 |
color: #065f46;
|
|
|
463 |
}
|
464 |
"""
|
465 |
|
466 |
+
# JavaScript code to handle interactions
|
467 |
+
js_code = """
|
468 |
+
function startEvaluation() {
|
469 |
+
const nameInput = document.querySelector('textarea[data-testid="textbox"]');
|
470 |
+
if (nameInput && nameInput.value.trim()) {
|
471 |
+
const hiddenNameInput = document.querySelector('#hidden-name textarea');
|
472 |
+
if (hiddenNameInput) {
|
473 |
+
hiddenNameInput.value = nameInput.value.trim();
|
474 |
+
hiddenNameInput.dispatchEvent(new Event('input', { bubbles: true }));
|
475 |
+
|
476 |
+
setTimeout(() => {
|
477 |
+
const startBtn = document.querySelector('#start-session-btn button');
|
478 |
+
if (startBtn) {
|
479 |
+
startBtn.click();
|
480 |
+
}
|
481 |
+
}, 100);
|
482 |
+
}
|
483 |
+
} else {
|
484 |
+
alert('Please enter your name');
|
485 |
+
}
|
486 |
+
}
|
487 |
+
|
488 |
+
function selectSample(sampleIndex) {
|
489 |
+
// Update hidden selection input
|
490 |
+
const selectionInput = document.querySelector('#hidden-selection textarea');
|
491 |
+
if (selectionInput) {
|
492 |
+
const sampleMap = ['sample_zero', 'sample_one', 'sample_two'];
|
493 |
+
selectionInput.value = sampleMap[sampleIndex];
|
494 |
+
selectionInput.dispatchEvent(new Event('input', { bubbles: true }));
|
495 |
+
|
496 |
+
// Update visual selection
|
497 |
+
document.querySelectorAll('.sample-card').forEach(card => {
|
498 |
+
card.classList.remove('selected');
|
499 |
+
});
|
500 |
+
document.getElementById(`sample-${sampleIndex}`).classList.add('selected');
|
501 |
+
|
502 |
+
// Enable confirm button
|
503 |
+
const confirmBtn = document.getElementById('confirm-button');
|
504 |
+
if (confirmBtn) {
|
505 |
+
confirmBtn.disabled = false;
|
506 |
+
}
|
507 |
+
}
|
508 |
+
}
|
509 |
+
|
510 |
+
function confirmSelection() {
|
511 |
+
const confirmBtn = document.querySelector('#confirm-btn button');
|
512 |
+
if (confirmBtn) {
|
513 |
+
confirmBtn.click();
|
514 |
+
}
|
515 |
+
}
|
516 |
+
|
517 |
+
function previousQuestion() {
|
518 |
+
const prevBtn = document.querySelector('#prev-btn button');
|
519 |
+
if (prevBtn) {
|
520 |
+
prevBtn.click();
|
521 |
+
}
|
522 |
+
}
|
523 |
+
|
524 |
+
function nextQuestion() {
|
525 |
+
const nextBtn = document.querySelector('#next-btn button');
|
526 |
+
if (nextBtn) {
|
527 |
+
nextBtn.click();
|
528 |
+
}
|
529 |
+
}
|
530 |
+
|
531 |
+
function exportResults() {
|
532 |
+
const exportBtn = document.querySelector('#export-btn button');
|
533 |
+
if (exportBtn) {
|
534 |
+
exportBtn.click();
|
535 |
+
}
|
536 |
+
}
|
537 |
+
|
538 |
+
// Update display based on state changes
|
539 |
+
function updateDisplay() {
|
540 |
+
const stateElement = document.querySelector('#current-state textarea');
|
541 |
+
if (stateElement && stateElement.value) {
|
542 |
+
try {
|
543 |
+
const data = JSON.parse(stateElement.value);
|
544 |
+
|
545 |
+
const welcomeScreen = document.getElementById('welcome-screen');
|
546 |
+
const evaluationContainer = document.getElementById('evaluation-container');
|
547 |
+
const fileUploadSection = document.getElementById('file-upload-section');
|
548 |
+
|
549 |
+
if (data.showWelcome) {
|
550 |
+
welcomeScreen.style.display = 'block';
|
551 |
+
evaluationContainer.style.display = 'none';
|
552 |
+
fileUploadSection.style.display = 'none';
|
553 |
+
} else if (data.showFileUpload) {
|
554 |
+
welcomeScreen.style.display = 'none';
|
555 |
+
evaluationContainer.style.display = 'none';
|
556 |
+
fileUploadSection.style.display = 'block';
|
557 |
+
} else {
|
558 |
+
welcomeScreen.style.display = 'none';
|
559 |
+
evaluationContainer.style.display = 'block';
|
560 |
+
fileUploadSection.style.display = 'none';
|
561 |
+
|
562 |
+
// Update content
|
563 |
+
if (data.userName) {
|
564 |
+
const userInfo = document.getElementById('user-info');
|
565 |
+
if (userInfo) userInfo.textContent = `Evaluator: ${data.userName}`;
|
566 |
+
}
|
567 |
+
|
568 |
+
document.getElementById('current-question').textContent = data.currentIndex + 1;
|
569 |
+
document.getElementById('total-questions').textContent = data.totalQuestions;
|
570 |
+
document.getElementById('example-text').textContent = data.introductoryExample;
|
571 |
+
document.getElementById('sample-0-text').textContent = data.sampleZero;
|
572 |
+
document.getElementById('sample-1-text').textContent = data.sampleOne;
|
573 |
+
document.getElementById('sample-2-text').textContent = data.sampleTwo;
|
574 |
+
|
575 |
+
// Update button states
|
576 |
+
document.getElementById('prev-button').disabled = data.currentIndex === 0;
|
577 |
+
document.getElementById('next-button').disabled = data.currentIndex >= data.totalQuestions - 1;
|
578 |
+
document.getElementById('confirm-button').disabled = true;
|
579 |
+
|
580 |
+
// Clear selection
|
581 |
+
document.querySelectorAll('.sample-card').forEach(card => {
|
582 |
+
card.classList.remove('selected');
|
583 |
+
});
|
584 |
+
}
|
585 |
+
} catch (e) {
|
586 |
+
console.error('Error parsing state:', e);
|
587 |
+
}
|
588 |
+
}
|
589 |
+
}
|
590 |
+
|
591 |
+
// Set up observer for state changes
|
592 |
+
const observer = new MutationObserver(() => {
|
593 |
+
updateDisplay();
|
594 |
+
});
|
595 |
+
|
596 |
+
// Start observing when DOM is ready
|
597 |
+
document.addEventListener('DOMContentLoaded', () => {
|
598 |
+
const stateElement = document.querySelector('#current-state textarea');
|
599 |
+
if (stateElement) {
|
600 |
+
observer.observe(stateElement, {
|
601 |
+
attributes: true,
|
602 |
+
childList: true,
|
603 |
+
characterData: true,
|
604 |
+
subtree: true
|
605 |
+
});
|
606 |
+
updateDisplay();
|
607 |
+
}
|
608 |
+
});
|
609 |
+
"""
|
610 |
+
|
611 |
+
# HTML Template with onclick handlers
|
612 |
html_template = """
|
613 |
<div id="main-container">
|
614 |
<div id="welcome-screen">
|
615 |
<h1>Legal Tech Tool Evaluation</h1>
|
616 |
<p>Compare and evaluate outputs from different legal technology tools to help us understand user preferences.</p>
|
617 |
+
<div id="name-input-container">
|
618 |
+
<!-- Gradio will inject the textbox here -->
|
619 |
</div>
|
|
|
620 |
</div>
|
621 |
|
622 |
+
<div id="evaluation-container">
|
623 |
<div class="user-info" id="user-info"></div>
|
624 |
|
625 |
<div id="question-header">
|
|
|
654 |
|
655 |
<div id="action-buttons">
|
656 |
<button class="nav-button" id="prev-button" onclick="previousQuestion()">← Previous</button>
|
657 |
+
<button class="nav-button" id="confirm-button" onclick="confirmSelection()" disabled>Confirm Selection</button>
|
658 |
<button class="nav-button" id="next-button" onclick="nextQuestion()">Next →</button>
|
659 |
</div>
|
660 |
|
661 |
<div id="export-section">
|
662 |
+
<button class="export-button" onclick="exportResults()">Export Results</button>
|
663 |
<div id="export-status"></div>
|
664 |
</div>
|
665 |
</div>
|
666 |
|
667 |
+
<div id="file-upload-section">
|
668 |
<h3>No data file found</h3>
|
669 |
<p>Please upload a JSONL file to begin the evaluation</p>
|
670 |
+
<!-- File upload will be handled by Gradio -->
|
671 |
</div>
|
672 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
673 |
"""
|
674 |
|
675 |
def start_session(name):
|
|
|
687 |
"""Get current state as JSON"""
|
688 |
question = evaluator.get_current_question()
|
689 |
|
690 |
+
if not evaluator.current_user_name:
|
691 |
return json.dumps({"showWelcome": True})
|
692 |
|
693 |
+
if not question:
|
694 |
+
return json.dumps({"showFileUpload": True})
|
695 |
+
|
696 |
return json.dumps({
|
697 |
"showWelcome": False,
|
698 |
"showFileUpload": False,
|
|
|
729 |
"""Export results and return status"""
|
730 |
try:
|
731 |
jsonl_file, md_file, json_file, md_content = evaluator.export_results()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
732 |
return (
|
733 |
get_current_state(),
|
734 |
+
"✅ Results exported successfully!",
|
735 |
+
jsonl_file,
|
736 |
+
md_file,
|
737 |
md_content
|
738 |
)
|
739 |
except Exception as e:
|
740 |
return (
|
741 |
get_current_state(),
|
742 |
+
f"❌ Export failed: {str(e)}",
|
743 |
+
None,
|
744 |
+
None,
|
745 |
""
|
746 |
)
|
747 |
|
|
|
752 |
return get_current_state()
|
753 |
|
754 |
# Create Gradio interface
|
755 |
+
with gr.Blocks(css=custom_css, js=js_code, theme=gr.themes.Base()) as demo:
|
756 |
+
gr.HTML(html_template)
|
757 |
+
|
758 |
+
# Visible name input in welcome screen
|
759 |
+
with gr.Column(elem_id="name-input-wrapper", visible=True):
|
760 |
+
user_name_input = gr.Textbox(
|
761 |
+
label="",
|
762 |
+
placeholder="Enter your name",
|
763 |
+
elem_id="user-name-field"
|
764 |
+
)
|
765 |
+
start_button = gr.Button("Start Evaluation", elem_classes=["start-button"])
|
766 |
+
|
767 |
# Hidden components for JavaScript interaction
|
768 |
with gr.Column(visible=False):
|
769 |
+
hidden_name = gr.Textbox(elem_id="hidden-name")
|
770 |
start_btn = gr.Button("Start", elem_id="start-session-btn")
|
771 |
+
current_state = gr.Textbox(value=json.dumps({"showWelcome": True}), elem_id="current-state")
|
772 |
selection_input = gr.Textbox(elem_id="hidden-selection")
|
773 |
confirm_btn = gr.Button("Confirm", elem_id="confirm-btn")
|
774 |
prev_btn = gr.Button("Previous", elem_id="prev-btn")
|
775 |
next_btn = gr.Button("Next", elem_id="next-btn")
|
776 |
export_btn = gr.Button("Export", elem_id="export-btn")
|
777 |
+
file_upload = gr.File(elem_id="file-upload-input", file_types=[".jsonl"])
|
|
|
778 |
|
779 |
# Output components (hidden)
|
780 |
with gr.Column(visible=False):
|
|
|
783 |
download_md = gr.File(label="Download Summary")
|
784 |
summary_display = gr.Markdown()
|
785 |
|
|
|
|
|
|
|
786 |
# Event handlers
|
787 |
+
start_button.click(
|
788 |
+
fn=start_session,
|
789 |
+
inputs=[user_name_input],
|
790 |
+
outputs=[current_state]
|
791 |
+
)
|
792 |
+
|
793 |
start_btn.click(
|
794 |
+
fn=start_session,
|
795 |
+
inputs=[hidden_name],
|
796 |
outputs=[current_state]
|
797 |
)
|
798 |
|
|
|
817 |
outputs=[current_state, export_status, download_jsonl, download_md, summary_display]
|
818 |
)
|
819 |
|
820 |
+
file_upload.change(
|
821 |
fn=load_file,
|
822 |
inputs=[file_upload],
|
823 |
outputs=[current_state]
|
824 |
)
|
825 |
|
826 |
+
# Move name input to welcome screen on load
|
827 |
demo.load(
|
828 |
+
fn=None,
|
829 |
+
js="""
|
830 |
+
() => {
|
831 |
+
// Move the name input to the welcome screen
|
832 |
+
const nameWrapper = document.querySelector('#name-input-wrapper');
|
833 |
+
const nameContainer = document.querySelector('#name-input-container');
|
834 |
+
if (nameWrapper && nameContainer) {
|
835 |
+
const nameField = nameWrapper.querySelector('.wrap');
|
836 |
+
if (nameField) {
|
837 |
+
nameContainer.appendChild(nameField);
|
838 |
+
}
|
839 |
+
nameWrapper.style.display = 'none';
|
840 |
+
}
|
841 |
+
|
842 |
+
// Style the start button
|
843 |
+
const startBtn = document.querySelector('#name-input-wrapper').nextElementSibling;
|
844 |
+
if (startBtn) {
|
845 |
+
const btn = startBtn.querySelector('button');
|
846 |
+
if (btn) {
|
847 |
+
btn.className = 'start-button';
|
848 |
+
btn.onclick = () => startEvaluation();
|
849 |
+
}
|
850 |
+
document.querySelector('#welcome-screen').appendChild(btn);
|
851 |
+
startBtn.style.display = 'none';
|
852 |
+
}
|
853 |
+
}
|
854 |
+
"""
|
855 |
)
|
856 |
|
857 |
# Create sample data if no file exists
|