Spaces:
Paused
Paused
| <html lang="en"> | |
| <!--- HEADER GOES BELOW HERE---> | |
| <head> | |
| <!-- <script src="jquery-3.3.1.min.js"></script> --> | |
| <script | |
| src="https://code.jquery.com/jquery-3.4.1.slim.min.js" | |
| integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" | |
| crossorigin="anonymous" | |
| ></script> | |
| <script | |
| src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" | |
| integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" | |
| crossorigin="anonymous" | |
| ></script> | |
| <script | |
| src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" | |
| integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" | |
| crossorigin="anonymous" | |
| ></script> | |
| <link | |
| rel="stylesheet" | |
| href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" | |
| integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" | |
| crossorigin="anonymous" | |
| /> | |
| <link rel="stylesheet" href="/static/styles/emphasis.css" /> | |
| <script | |
| src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" | |
| integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" | |
| crossorigin="anonymous" | |
| ></script> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| } | |
| .row { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* border:1px solid black; | |
| border-radius: 5px; */ | |
| /* Create two equal columns that sits next to each other */ | |
| .column { | |
| flex: 50%; | |
| padding: 15px; | |
| } | |
| .instance { | |
| border: 1px solid #070707; | |
| background-color: cccccc; | |
| border-radius: 5px; | |
| padding: 15px; | |
| display: block; | |
| justify-content: center; | |
| width: fit-content; | |
| max-width: 80%; | |
| text-align: left; | |
| margin: 0 auto; | |
| font-size: 15pt; | |
| } | |
| .instance p { | |
| font-weight: bold; | |
| display: block; | |
| } | |
| .annotation_schema { | |
| padding: 5px; | |
| display: flex; | |
| justify-content: center; | |
| width: 80%; | |
| text-align: left; | |
| margin: 0 auto; | |
| } | |
| fieldset { | |
| border: 1px solid #160085; | |
| background-color: cccccc; | |
| padding: 10px 15px 10px 15px; | |
| } | |
| legend { | |
| color: black; | |
| } | |
| input[type="text"] { | |
| -webkit-box-sizing: border-box; | |
| -moz-box-sizing: border-box; | |
| box-sizing: border-box; | |
| width: 100%; | |
| } | |
| input { | |
| margin: 5px; | |
| } | |
| .likert li { | |
| float: left; | |
| list-style-type: none; | |
| } | |
| /* The sidepanel menu */ | |
| .sidepanel { | |
| min-height: 250px; /* Specify a height */ | |
| width: 0; /* 0 width - change this with JavaScript */ | |
| position: fixed; /* Stay in place */ | |
| z-index: 1; /* Stay on top */ | |
| top: 95px; | |
| left: 1px; | |
| background-color: #111; /* Black*/ | |
| overflow-x: hidden; /* Disable horizontal scroll */ | |
| padding-top: 10px; /* Place content 60px from the top */ | |
| padding-bottom: 20px; | |
| transition: 0.5s; /* 0.5 second transition effect to slide in the sidepanel */ | |
| } | |
| /* The sidepanel links */ | |
| .sidepanel a { | |
| padding: 8px 8px 8px 32px; | |
| text-decoration: none; | |
| font-size: 55px; | |
| color: #ffffff; /* #818181; */ | |
| display: block; | |
| transition: 0.3s; | |
| } | |
| /* When you mouse over the navigation links, change their color */ | |
| .sidepanel a:hover { | |
| color: #f1f1f1; | |
| } | |
| /* When you mouse over the navigation links, change their color */ | |
| .sidepanel table { | |
| color: #ffffff; | |
| font-size: 20px; | |
| /*padding: 8px 8px 18px 62px;*/ | |
| margin-left: 20px; | |
| margin-top: 10px; | |
| border-spacing: 10px; | |
| } | |
| .sidepanel td, | |
| th { | |
| color: #ffffff; | |
| padding: 5px; | |
| } | |
| .sidepanel tr:nth-child(even) { | |
| background-color: #333333; | |
| } | |
| /* Position and style the close button (top right corner) */ | |
| .sidepanel .closebtn { | |
| position: absolute; | |
| top: 0; | |
| right: 25px; | |
| font-size: 36px; | |
| margin-left: 50px; | |
| } | |
| /* Style the button that is used to open the sidepanel */ | |
| .openbtn { | |
| cursor: pointer; | |
| /* | |
| font-size: 20px; | |
| background-color: #111; | |
| color: white; | |
| padding: 10px 15px; | |
| border: none;*/ | |
| } | |
| .openbtn:hover { | |
| background-color: #444; | |
| } | |
| /* The instructions */ | |
| .instructions { | |
| /* width: 0; */ /* 0 width - change this with JavaScript */ | |
| transition: 0.5s; /* 0.5 second transition effect to slide in the sidepanel */ | |
| overflow-y: hidden; /* Disable horizontal scroll */ | |
| border: 1px solid #070707; | |
| background-color: cccccc; | |
| border-radius: 5px; | |
| padding: 15px; | |
| display: block; | |
| justify-content: center; | |
| width: fit-content; | |
| max-width: 80%; | |
| text-align: left; | |
| margin: 0 auto; | |
| font-size: 14pt; | |
| } | |
| /* Position and style the close button (top right corner) */ | |
| .instructions .closebtn { | |
| position: absolute; | |
| top: 0; | |
| right: 25px; | |
| font-size: 36px; | |
| margin-left: 50px; | |
| } | |
| /* Style the button that is used to open the sidepanel */ | |
| .instructions .openbtn { | |
| cursor: pointer; | |
| /* | |
| font-size: 20px; | |
| background-color: #111; | |
| color: white; | |
| padding: 10px 15px; | |
| border: none;*/ | |
| } | |
| .span_container { | |
| border-width: 3px; | |
| border-style: solid; | |
| border: 2px solid black; | |
| position: relative; | |
| border-radius: 5px; | |
| padding: 2px 5px; | |
| background-color: pink; | |
| } | |
| .span_label { | |
| position: absolute; | |
| top: -12px; | |
| left: 5px; | |
| font-size: 7px; | |
| padding: 1px 1px; | |
| border-radius: 3px; | |
| background-color: pink; | |
| border: 2px solid red; | |
| text-align: center; | |
| } | |
| .span_close { | |
| position: absolute; | |
| top: -7px; | |
| right: -7px; | |
| width: 14px; | |
| height: 14px; | |
| font-size: 7px; | |
| padding: 2px 2px; | |
| border-radius: 50%; | |
| background-color: gray; | |
| border: 2px solid black; | |
| text-align: center; | |
| font: 7pt Arial, sans-serif; | |
| } | |
| </style> | |
| </head> | |
| <!--- HEADER GOES ABOVE HERE---> | |
| <style> | |
| :root { | |
| --background: #ffffff; | |
| --foreground: #09090b; | |
| --card: #ffffff; | |
| --card-foreground: #09090b; | |
| --popover: #ffffff; | |
| --popover-foreground: #09090b; | |
| --primary: #6e56cf; | |
| --primary-foreground: #ffffff; | |
| --secondary: #f4f4f5; | |
| --secondary-foreground: #18181b; | |
| --muted: #f4f4f5; | |
| --muted-foreground: #71717a; | |
| --accent: #f4f4f5; | |
| --accent-foreground: #18181b; | |
| --destructive: #ef4444; | |
| --destructive-foreground: #fafafa; | |
| --border: #e4e4e7; | |
| --input: #e4e4e7; | |
| --ring: #6e56cf; | |
| --radius: 0.5rem; | |
| --primary-color: #6e56cf; /* Shadcn purple */ | |
| --secondary-color: #7c66ce; | |
| --accent-color: #a18fff; | |
| --dark-color: #09090b; | |
| --light-color: #fafafa; | |
| --gray-color: #71717a; | |
| --light-gray: #f4f4f5; | |
| --light-bg: #f5f7fb; /* Light background for sections */ | |
| --heading-color: white; /* For headings */ | |
| --text-color: #555555; /* For regular text */ | |
| --success-color: #10b981; | |
| --border-radius: 0.5rem; | |
| --box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), | |
| 0 1px 2px -1px rgb(0 0 0 / 0.1); | |
| --transition: all 0.2s ease; | |
| --section-padding-top: 100px; | |
| } | |
| /* Shadcn-style button */ | |
| .shadcn-button { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: var(--radius); | |
| font-weight: 500; | |
| font-size: 0.875rem; | |
| height: 2.5rem; | |
| padding-left: 1rem; | |
| padding-right: 1rem; | |
| transition: var(--transition); | |
| cursor: pointer; | |
| text-decoration: none; | |
| } | |
| .shadcn-button-primary { | |
| background-color: var(--primary); | |
| color: var(--primary-foreground); | |
| border: 1px solid var(--primary); | |
| } | |
| .shadcn-button-primary:hover { | |
| background-color: var(--secondary-color); | |
| color: var(--primary-foreground); | |
| } | |
| .shadcn-button-outline { | |
| background-color: transparent; | |
| color: var(--primary); | |
| border: 1px solid var(--border); | |
| } | |
| .shadcn-button-outline:hover { | |
| background-color: var(--secondary); | |
| color: var(--primary); | |
| } | |
| /* Sidepanel styling */ | |
| .sidepanel { | |
| height: 100%; | |
| width: 0; | |
| position: fixed; | |
| z-index: 1000; | |
| top: 0; | |
| right: 0; | |
| background-color: var(--card); | |
| overflow-x: hidden; | |
| transition: 0.3s; | |
| padding-top: 60px; | |
| box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1); | |
| } | |
| .sidepanel a { | |
| padding: 8px 8px 8px 32px; | |
| text-decoration: none; | |
| font-size: 16px; | |
| color: var(--foreground); | |
| display: block; | |
| transition: 0.3s; | |
| } | |
| .sidepanel .closebtn { | |
| position: absolute; | |
| top: 0; | |
| right: 25px; | |
| font-size: 36px; | |
| margin-left: 50px; | |
| color: var(--muted-foreground); | |
| } | |
| /* Span annotation styling */ | |
| .span_container { | |
| position: relative; | |
| display: inline-block; | |
| border-radius: var(--radius); | |
| } | |
| .span_label { | |
| position: absolute; | |
| top: -18px; | |
| left: 0; | |
| font-size: 10px; | |
| border-radius: 3px; | |
| padding: 0 3px; | |
| color: var(--primary); | |
| } | |
| .span_close { | |
| position: absolute; | |
| top: -18px; | |
| right: 0; | |
| font-size: 12px; | |
| border-radius: 3px; | |
| padding: 0 3px; | |
| cursor: pointer; | |
| color: var(--destructive); | |
| } | |
| /* Card styling */ | |
| .shadcn-card { | |
| height: 65vh; | |
| background-color: var(--card); | |
| border-radius: var(--radius); | |
| border: 1px solid var(--border); | |
| box-shadow: var(--box-shadow); | |
| overflow: hidden; | |
| } | |
| .shadcn-card-header { | |
| padding: 1.5rem; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .shadcn-card-content { | |
| padding: 1.5rem; | |
| } | |
| fieldset { | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| padding: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| legend { | |
| font-weight: 500; | |
| color: var(--heading-color); | |
| padding: 0 0.5rem; | |
| } | |
| /* Form elements */ | |
| input[type="text"], | |
| input[type="number"], | |
| textarea, | |
| select { | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| padding: 0.5rem; | |
| background-color: var(--background); | |
| color: var(--foreground); | |
| transition: var(--transition); | |
| } | |
| input[type="text"]:focus, | |
| input[type="number"]:focus, | |
| textarea:focus, | |
| select:focus { | |
| outline: none; | |
| border-color: var(--ring); | |
| box-shadow: 0 0 0 2px var(--ring-opacity); | |
| } | |
| </style> | |
| {{ custom_js | safe }} | |
| <body style="background-color: var(--background); color: var(--foreground)"> | |
| <input type="hidden" name="username" id="username" value="{{username}}" /> | |
| <input | |
| type="hidden" | |
| name="instance_id" | |
| id="instance_id" | |
| value="{{instance_id}}" | |
| /> | |
| <input | |
| type="hidden" | |
| name="alert_time_each_instance" | |
| id="alert_time_each_instance" | |
| value="{{alert_time_each_instance}}" | |
| /> | |
| <p id="timecounter" hidden>Time spent:</p> | |
| <div id="mySidepanel" class="sidepanel"> | |
| <a | |
| href="javascript:void(0)" | |
| class="closebtn" | |
| target_id="mySidepanel" | |
| onclick="closeNav(this.getAttribute('target_id'))" | |
| >×</a | |
| > | |
| <table><tr><th>Key</th><th>Description</th></tr><tr><td style="text-align: center;">←</td><td>Move backward</td></tr><tr><td style="text-align: center;">→</td><td>Move forward</td></tr><tr><td style="text-align: center;">1</td><td>relevance: 1</td></tr><tr><td style="text-align: center;">2</td><td>relevance: 2</td></tr><tr><td style="text-align: center;">3</td><td>relevance: 3</td></tr><tr><td style="text-align: center;">4</td><td>relevance: 4</td></tr><tr><td style="text-align: center;">5</td><td>relevance: 5</td></tr><tr><td style="text-align: center;">1</td><td>coherence: 1</td></tr><tr><td style="text-align: center;">2</td><td>coherence: 2</td></tr><tr><td style="text-align: center;">3</td><td>coherence: 3</td></tr><tr><td style="text-align: center;">4</td><td>coherence: 4</td></tr><tr><td style="text-align: center;">5</td><td>coherence: 5</td></tr></table> | |
| </div> | |
| <div id="Statpanel" class="sidepanel"> | |
| <a | |
| href="javascript:void(0)" | |
| class="closebtn" | |
| target_id="Statpanel" | |
| onclick="closeNav(this.getAttribute('target_id'))" | |
| >×</a | |
| > | |
| [[statistics_nav]] | |
| </div> | |
| <script> | |
| // this is for ai hints | |
| document.addEventListener("click", function (event) { | |
| const hintEl = event.target.closest(".hint"); | |
| if (!hintEl) return; // Not a .hint click | |
| const aiHelp = hintEl.closest(".ai-help"); | |
| const tooltip = aiHelp.querySelector(".tooltip"); | |
| const tooltipText = tooltip.querySelector(".tooltip-text"); | |
| const annotationId = aiHelp.closest("[data-annotation-id]")?.dataset | |
| .annotationId; | |
| const text = document.getElementById("instance-text").innerText; | |
| fetch("get_ai_hint", { | |
| method: "POST", | |
| credentials: "same-origin", | |
| body: JSON.stringify({ annotation_id: annotationId, text: text }), | |
| headers: { "Content-Type": "application/json" }, | |
| }) | |
| .then((response) => response.json()) | |
| .then((data) => { | |
| tooltipText.innerHTML = `<span class="reasoning">Reasoning:</span> ${data.reasoning}`; | |
| tooltip.classList.toggle("active"); | |
| }) | |
| .catch((error) => { | |
| tooltipText.innerHTML = `<span class="reasoning">Error:</span> Could not load hint`; | |
| tooltip.classList.toggle("active"); | |
| console.error("Error fetching AI hint:", error); | |
| }); | |
| }); | |
| document.addEventListener("click", function (event) { | |
| if (!event.target.closest(".ai-help")) { | |
| document.querySelectorAll(".tooltip.active").forEach((tooltip) => { | |
| tooltip.classList.remove("active"); | |
| }); | |
| } | |
| }); | |
| </script> | |
| <script> | |
| document.addEventListener("keyup", function (event) { | |
| var active_id = document.activeElement.id; | |
| var active_type = document.activeElement.getAttribute("type"); | |
| if ((active_id == "go_to") | (active_type == "text")) return; | |
| //first check whether this keyboard input is a shortcut for checkboxes | |
| var checkboxes = document.querySelectorAll("input[type=checkbox]"); | |
| var radios = document.querySelectorAll("input[type=radio]"); | |
| var x = event.key.toLowerCase(); | |
| for (var i = 0; i < checkboxes.length; i++) { | |
| //alert(checkboxes[i].value) | |
| if (x === checkboxes[i].value) { | |
| checkboxes[i].checked = !checkboxes[i].checked; | |
| if (checkboxes[i].onclick != null) | |
| checkboxes[i].onclick.apply(checkboxes[i]); | |
| return; | |
| } | |
| } | |
| for (var i = 0; i < radios.length; i++) { | |
| //alert(checkboxes[i].value) | |
| if (x === radios[i].value) { | |
| radios[i].checked = !radios[i].checked; | |
| if (radios[i].onclick != null) radios[i].onclick.apply(radios[i]); | |
| return; | |
| } | |
| } | |
| // Each time we process a user's key presses, track who is doing | |
| // it by grabbing the hidden firstname and lastname fields | |
| get_new_instance(event); | |
| }); | |
| function getFieldSetState(element) { | |
| // console.dir('saw annotation' + element.value); | |
| // console.dir(element.parentElement); | |
| //var fieldSet = element.parentElement; | |
| // Find the closeset ancestor fieldset element from this input | |
| var fieldSet = element.closest("fieldset"); | |
| // Get all the inputs in this field set (what are the annotation options) | |
| var inputs = fieldSet.querySelectorAll("input"); | |
| // Get state of all the in this group | |
| var state = Array.from(inputs).map((input) => { | |
| return { | |
| name: input.getAttribute("label_name"), | |
| value: input.checked, | |
| // checked: input.value // unused?? | |
| }; | |
| }); | |
| return state; | |
| } | |
| function registerAnnotation(element) { | |
| // If the element is a radio button, we need to uncheck all the other which | |
| // requires sending the state of all the other buttons in the group | |
| sc = element.getAttribute("selection_constraint"); | |
| if (sc && sc == "single") { | |
| state = getFieldSetState(element); | |
| } else if (element.getAttribute("type") == "range") { | |
| state = [ | |
| { | |
| name: element.getAttribute("label_name"), | |
| value: element.value, | |
| }, | |
| ]; | |
| } | |
| // This is a checkbox so we can just send the state of this checkbox | |
| else { | |
| state = [ | |
| { | |
| name: element.getAttribute("label_name"), | |
| value: element.checked, | |
| // checked: element.value // unused?? | |
| }, | |
| ]; | |
| } | |
| // Get which annotation group this change belongs to | |
| var schema = element.getAttribute("schema"); | |
| // Get the instance id | |
| var instance_id = document.getElementById("instance_id").value; | |
| // Package this all up in a post request to the server's updateinstance endpoint | |
| var post_req = { | |
| type: "label", | |
| schema: schema, | |
| state: state, | |
| instance_id: instance_id, | |
| }; | |
| // Send the post request | |
| fetch("/updateinstance", { | |
| method: "POST", | |
| body: JSON.stringify(post_req), | |
| credentials: "same-origin", | |
| headers: { | |
| "Content-type": "application/json; charset=UTF-8", | |
| }, | |
| }); | |
| } | |
| function registerTextAnnotation(element) { | |
| // Get which annotation group this change belongs to | |
| var schema = element.getAttribute("schema"); | |
| // Get the instance id | |
| var instance_id = document.getElementById("instance_id").value; | |
| state = [ | |
| { | |
| name: element.getAttribute("label_name"), | |
| value: element.value, | |
| }, | |
| ]; | |
| // Package this all up in a post request to the server's updateinstance endpoint | |
| var post_req = { | |
| type: "label", | |
| schema: schema, | |
| state: state, | |
| instance_id: instance_id, | |
| }; | |
| // Send the post request | |
| fetch("/updateinstance", { | |
| method: "POST", | |
| body: JSON.stringify(post_req), | |
| credentials: "same-origin", | |
| headers: { | |
| "Content-type": "application/json; charset=UTF-8", | |
| }, | |
| }); | |
| } | |
| function getSelectedText() { | |
| var text = ""; | |
| if (typeof window.getSelection != "undefined") { | |
| text = window.getSelection().toString(); | |
| } else if ( | |
| typeof document.selection != "undefined" && | |
| document.selection.type == "Text" | |
| ) { | |
| text = document.selection.createRange().text; | |
| } | |
| return text; | |
| } | |
| function deleteSpanAnnotation( | |
| spanElem, | |
| schema, | |
| labelName, | |
| title, | |
| start, | |
| end | |
| ) { | |
| // Get the instance id | |
| var instance_id = document.getElementById("instance_id").value; | |
| // Get the text of the span | |
| var spanText = spanElem.innerText; | |
| // Package this all up in a post request to the server's updateinstance endpoint | |
| var post_req = { | |
| type: "span", | |
| schema: schema, | |
| state: [ | |
| { | |
| name: labelName, | |
| start: start, | |
| end: end, | |
| title: title, | |
| value: null, | |
| }, | |
| ], | |
| instance_id: instance_id, | |
| }; | |
| // Send the post request | |
| fetch("/updateinstance", { | |
| method: "POST", | |
| body: JSON.stringify(post_req), | |
| credentials: "same-origin", | |
| headers: { | |
| "Content-type": "application/json; charset=UTF-8", | |
| }, | |
| }); | |
| // Remove the outer most div tag that has the span's annotation title (the small text above) | |
| $(spanElem).find("div").first().remove(); | |
| // Remove the close tag | |
| $(spanElem).find("div").last().remove(); | |
| // Remove the span highlight by unwrapping the inner text | |
| $(spanElem.firstChild).unwrap(); | |
| } | |
| function getEarlierChildren(n, skipMe) { | |
| var r = []; | |
| for (; n; n = n.nextSibling) | |
| if (n.nodeType == 1 && n != skipMe) r.push(n); | |
| else if (n == skipMe) break; | |
| return r; | |
| } | |
| function getEarlierSiblings(n) { | |
| return getEarlierChildren(n.parentNode.firstChild, n); | |
| } | |
| function getTextOfSpans(elems) { | |
| text = ""; | |
| for (i = 0; i < elems.length; i++) { | |
| //console.log(elems[i].textContent) | |
| e = elems[i].cloneNode(true); | |
| console.log("e", e); | |
| // Delete all the nested <div> elements with jquery to get the plain text | |
| $(e).find("div").remove(); | |
| console.log("sib text", e.textContent); | |
| text += " " + e.textContent; | |
| } | |
| return text.trim(); | |
| } | |
| function getSelectionIndices() { | |
| // Get the user selection | |
| var selection = window.getSelection(); | |
| if (selection.rangeCount === 0) { | |
| return { start: -1, end: -1 }; // No selection | |
| } | |
| // Get the range object representing the selected portion | |
| var range = selection.getRangeAt(0); | |
| // Get the left most element in the selection | |
| var leftMost = range.startContainer; | |
| var leftSibs = getEarlierSiblings(leftMost); | |
| console.log("leftSibs", leftSibs); | |
| // Get the text of the left elements | |
| var leftText = getTextOfSpans(leftSibs); | |
| // Find the parent div element with name "instance_text" using jquery | |
| var parentDiv = $(range.commonAncestorContainer).closest( | |
| 'div[name="instance_text"]' | |
| )[0]; | |
| //console.log('parentDiv', parentDiv) | |
| if (!parentDiv) { | |
| return { start: -2, end: -2 }; // Not within a <div class="context_text"> | |
| } | |
| //console.log('parentDiv') | |
| //console.dir(parentDiv.innerHTML) | |
| // Make a copy of the parent div to manipulate | |
| parentDiv = parentDiv.cloneNode(true); | |
| // Create a temporary Range to find the offset relative to the parent div | |
| console.log( | |
| "before delete", | |
| range.startContainer, | |
| range.startOffset, | |
| range.endOffset | |
| ); | |
| var tempRange = document.createRange(); | |
| tempRange.setStart(parentDiv, 0); | |
| tempRange.setEnd(range.startContainer, range.startOffset); | |
| // Delete all the nested <div> elements with jquery to get the plain text | |
| $(parentDiv).find("div").remove(); | |
| console.log("after delete"); | |
| console.dir(parentDiv.innerHTML); | |
| console.log("text"); | |
| console.dir(parentDiv.textContent); | |
| console.log( | |
| "after delete", | |
| range.startContainer, | |
| range.startOffset, | |
| range.endOffset | |
| ); | |
| // Get the starting offset of the selection within the parent div | |
| var div = document.createElement("div"); | |
| div.appendChild(tempRange.cloneContents()); | |
| var s = range.startContainer.textContent.toString(); | |
| //console.log(`text: "${text}"`); | |
| var whitespaceOffset = s.length - s.trimStart().length; | |
| var startOffset = range.startOffset - whitespaceOffset; | |
| if (leftText.length > 0) { | |
| // Add 1 for space between the left text and the selected text | |
| startOffset += leftText.length + 1; | |
| } | |
| // Get the length of the selected text | |
| var selectedTextLength = range.toString().length; | |
| // Calculate the ending offset of the selection within the parent div | |
| var endOffset = startOffset + selectedTextLength; | |
| return { start: startOffset, end: endOffset }; | |
| } | |
| function surroundSelection(schema, labelName, title, selectionColor) { | |
| //var span = document.createElement("span"); | |
| //span.style.fontWeight = "bold"; | |
| //span.style.color = "green"; | |
| // Check that this wasn't a spurious click or the click for the delete button which | |
| // also seems to trigger this selection event | |
| if (window.getSelection().rangeCount == 0) { | |
| return; | |
| } | |
| var range = window.getSelection().getRangeAt(0); | |
| // console.dir(window.getSelection()) | |
| // console.log(range.startOffset, range.endOffset) | |
| if (range.startOffset == range.endOffset) { | |
| return; | |
| } | |
| // Get the instance id | |
| var instance_id = document.getElementById("instance_id").value; | |
| if (window.getSelection) { | |
| var sel = window.getSelection(); | |
| // console.dir(sel); | |
| // Check that we're labeling something in the instance text that | |
| // we want to annotate | |
| if (!sel.anchorNode.parentElement) { | |
| return; | |
| } | |
| // TODO: check that we're not nesting a span of the same time here | |
| // Otherwise, we're going to be adding a new span annotation, if | |
| // the user has selected some non-empty part of the text | |
| if (sel.rangeCount && sel.toString().trim().length > 0) { | |
| // Get the selection text as a string | |
| var selText = window.getSelection().toString().trim(); | |
| console.log("selText1", selText); | |
| // Get the start and end offsets of the selection | |
| var range = sel.getRangeAt(0); | |
| var start = range.startOffset; | |
| var end = range.endOffset; | |
| // Get the generic text between the start and end offsets | |
| var text = range.startContainer.textContent.substring(start, end); | |
| var s = range.startContainer.textContent.toString(); | |
| //console.log(`text: "${text}"`); | |
| var whitespaceOffset = s.length - s.trimStart().length; | |
| //console.log('whitespaceOffset', whitespaceOffset); | |
| /* | |
| console.log(`full range text "${range.startContainer.textContent}`); | |
| console.log('start', start); | |
| console.log('end', end); | |
| console.log('start', start - whitespaceOffset); | |
| console.log('end', end - whitespaceOffset); | |
| console.log(`ranged substring "${text}"`); | |
| */ | |
| // Get the raw text of the selection without any HTML | |
| var sel = window.getSelection(); | |
| var range = sel.getRangeAt(0); | |
| var div = document.createElement("div"); | |
| div.appendChild(range.cloneContents()); | |
| var selText = div.innerHTML; | |
| //console.log(`selText2: "${selText}"`); | |
| tsc = selectionColor.replace(")", ", 0.25)"); | |
| var span = document.createElement("span"); | |
| span.className = "span_container"; | |
| span.setAttribute("selection_label", labelName); | |
| span.setAttribute("style", "background-color:rgb" + tsc + ";"); | |
| var label = document.createElement("div"); | |
| label.className = "span_label"; | |
| label.textContent = title; | |
| label.setAttribute( | |
| "style", | |
| "background-color:white;" + | |
| "border:2px solid rgb" + | |
| selectionColor + | |
| ";" | |
| ); | |
| var delLabel = document.createElement("div"); | |
| delLabel.className = "span_close"; | |
| delLabel.textContent = "×"; | |
| delLabel.setAttribute("style", "background-color:white;"); | |
| // + "border:2px solid rgb" + selectionColor + ";"); | |
| delLabel.onclick = function () { | |
| deleteSpanAnnotation( | |
| span, | |
| schema, | |
| labelName, | |
| title, | |
| range.startOffset, | |
| range.endOffset | |
| ); | |
| }; | |
| // Get the offsets | |
| startEnd = getSelectionIndices(); | |
| console.log("startEnd", startEnd); | |
| state = [ | |
| { | |
| name: labelName, | |
| start: startEnd["start"], | |
| end: startEnd["end"], | |
| title: title, | |
| value: sel.toString(), | |
| }, | |
| ]; | |
| // console.log('adding new annotation', range.startOffset, range.endOffset, sel.toString()) | |
| // Package this all up in a post request to the server's updateinstance endpoint | |
| var post_req = { | |
| type: "span", | |
| schema: schema, | |
| state: state, | |
| instance_id: instance_id, | |
| }; | |
| // Send the post request | |
| fetch("/updateinstance", { | |
| method: "POST", | |
| body: JSON.stringify(post_req), | |
| credentials: "same-origin", | |
| headers: { | |
| "Content-type": "application/json; charset=UTF-8", | |
| }, | |
| }); | |
| var range = sel.getRangeAt(0).cloneRange(); | |
| range.surroundContents(span); | |
| sel.removeAllRanges(); | |
| sel.addRange(range); | |
| span.appendChild(label); | |
| span.appendChild(delLabel); | |
| // Clear the current selection | |
| sel.empty(); | |
| } | |
| } | |
| } | |
| function changeSpanLabel( | |
| checkbox, | |
| schema, | |
| spanLabel, | |
| spanTitle, | |
| spanColor | |
| ) { | |
| // Listen for when the user has highlighted some text (only when the label is checked) | |
| document.onmouseup = function (e) { | |
| var senderElement = e.target; | |
| // Avoid the case where the user clicks the delete button | |
| if (senderElement.getAttribute("class") == "span_close") { | |
| e.stopPropagation(); | |
| return true; | |
| } | |
| if (checkbox.checked) { | |
| surroundSelection(schema, spanLabel, spanTitle, spanColor); | |
| } | |
| }; | |
| } | |
| // Listen for when the user has highlighted some text | |
| // document.onmouseup = function() { surroundSelection("Undefined"); } | |
| </script> | |
| <script> | |
| function barValue(range, sibling) { | |
| // function used to obtain value for range input | |
| sibling.value = range.value; | |
| //var x = document.getElementsByClassName(range.className); | |
| //var i; | |
| //for (i=0: i < x.length; i++) { | |
| // if(x[i].value != range.value) x[i] | |
| //} | |
| } | |
| </script> | |
| <script> | |
| function onlyOne(checkbox) { | |
| // this function is used for the single-choice setting | |
| //alert(checkbox.className) | |
| var x = document.getElementsByClassName(checkbox.className); | |
| var i; | |
| for (i = 0; i < x.length; i++) { | |
| if (x[i].value != checkbox.value) x[i].checked = false; | |
| } | |
| } | |
| </script> | |
| <script> | |
| function whetherNone(checkbox) { | |
| // this function is used to uncheck all the other labels when "None" is checked | |
| //alert(checkbox.className) | |
| var x = document.getElementsByClassName(checkbox.className); | |
| var i; | |
| for (i = 0; i < x.length; i++) { | |
| if (checkbox.value == "None" && x[i].value != "None") | |
| x[i].checked = false; | |
| if (checkbox.value != "None" && x[i].value == "None") | |
| x[i].checked = false; | |
| } | |
| } | |
| </script> | |
| <script> | |
| function click_to_next() { | |
| // Gacky code to simulate the submit button as a keyboard event | |
| // and not have two separate paths to handle keyboard and mouse | |
| // events | |
| var e = $.Event("keyup"); | |
| e.key = "ArrowRight"; | |
| get_new_instance(e); | |
| } | |
| function click_to_prev() { | |
| // Gacky code to simulate the submit button as a keyboard event | |
| // and not have two separate paths to handle keyboard and mouse | |
| // events | |
| var e = $.Event("keyup"); | |
| e.key = "ArrowLeft"; | |
| get_new_instance(e); | |
| } | |
| </script> | |
| {{annotation}} | |
| <script> | |
| /* | |
| window.onunload = check_close; | |
| function check_close() { | |
| // console.error("session closed"); | |
| var post_req = { | |
| is_close: "closed" | |
| } | |
| post(post_req); | |
| } | |
| */ | |
| </script> | |
| <script> | |
| // Set the date we're counting down to | |
| var countDownDate = new Date().getTime(); | |
| // Update the count down every 1 second | |
| var x = setInterval(function () { | |
| // Get today's date and time | |
| var now = new Date().getTime(); | |
| // Find the distance between now and the count down date | |
| var distance = now - countDownDate; | |
| // Time calculations for days, hours, minutes and seconds | |
| var days = Math.floor(distance / (1000 * 60 * 60 * 24)); | |
| var hours = Math.floor( | |
| (distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) | |
| ); | |
| var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); | |
| var seconds = Math.floor((distance % (1000 * 60)) / 1000); | |
| var total_seconds = Math.floor(distance / 1000); | |
| // Output the result in an element with id="timecounter" | |
| //document.getElementById("timecounter").innerHTML = "Time spent: " + days + "d " + hours + "h " | |
| // + minutes + "m " + seconds + "s "; | |
| // TODO: add configurations for alert message and maximum time spent on each instance | |
| // If the count down is over, write some text | |
| var alert_time_each_instance = document.getElementById( | |
| "alert_time_each_instance" | |
| ).value; | |
| if (total_seconds % alert_time_each_instance == 0) { | |
| //clearInterval(x); | |
| //document.getElementById("timecounter").innerHTML = "EXPIRED"; | |
| alert( | |
| "You have spent " + total_seconds + " seconds on this instance" | |
| ); | |
| } | |
| }, 1000); | |
| </script> | |
| <script> | |
| // We submit a new post to the same (user/annotate) endpoint | |
| function post(params) { | |
| // Package this all up in a post request to the server's updateinstance endpoint | |
| var post_req = { | |
| instance_id: instance_id, | |
| }; | |
| for (var key in params) { | |
| if (params.hasOwnProperty(key)) { | |
| post_req[key] = params[key]; | |
| } | |
| } | |
| // Extract inputs from the DOM and add to post_req | |
| $("form input, form select").each(function () { | |
| var input = $(this); | |
| var type = input.attr("type"); | |
| var name = input.attr("name"); | |
| if (!name) return; // skip unnamed inputs | |
| if (type === "checkbox" || type === "radio") { | |
| if (input.is(":checked")) { | |
| post_req[name] = true; | |
| } | |
| } else if (type === "text" || input.is("select")) { | |
| post_req[name] = input.val(); | |
| } else { | |
| console.log("unknown input type:", type); | |
| } | |
| }); | |
| // Send the post request and re-render the page with the new instance | |
| // | |
| // TODO: fix this the server-client protocol so we're just sending over | |
| // the new instance data and not the entire page | |
| fetch("/annotate", { | |
| method: "POST", | |
| body: JSON.stringify(post_req), | |
| credentials: "same-origin", | |
| headers: { | |
| "Content-type": "application/json; charset=UTF-8", | |
| }, | |
| }) | |
| .then((response) => { | |
| if (!response.ok) { | |
| throw new Error("Network response was not ok"); | |
| } | |
| return response.text(); // Assuming the response is text/html | |
| }) | |
| .then((html) => { | |
| // 2. Update the page content | |
| document.body.innerHTML = html; | |
| // Add all the listeners back to the new page | |
| watch_text_boxes(); | |
| }) | |
| .catch((error) => { | |
| console.error("Error:", error); | |
| // Handle errors appropriately, e.g., display an error message | |
| }); | |
| /* | |
| // The rest of this code assumes you are not using a library. | |
| // It can be made less wordy if you use one. | |
| var form = document.createElement("form"); | |
| form.setAttribute("method", "post"); | |
| form.setAttribute("action", "/"); | |
| var hiddenField = document.createElement("input"); | |
| hiddenField.setAttribute("type", "hidden"); | |
| hiddenField.setAttribute("name", "email"); | |
| hiddenField.setAttribute("value", document.getElementById('username').value); | |
| form.appendChild(hiddenField); | |
| for (var key in params) { | |
| if (params.hasOwnProperty(key)) { | |
| var hiddenField = document.createElement("input"); | |
| hiddenField.setAttribute("type", "hidden"); | |
| hiddenField.setAttribute("name", key); | |
| hiddenField.setAttribute("value", params[key]); | |
| form.appendChild(hiddenField); | |
| } | |
| } | |
| // Stuff all the current annotations into attributes for processing on the server side | |
| $('form input, form select, form textarea').each( | |
| function(index){ | |
| var input = $(this); | |
| if (input.attr('type') == 'checkbox' || input.attr('type') == 'radio') { | |
| if (input.is(":checked")) { | |
| // Stuff all the input fields into something for the post | |
| var hiddenField = document.createElement("input"); | |
| hiddenField.setAttribute("type", "hidden"); | |
| hiddenField.setAttribute("name", input.attr('name')); | |
| hiddenField.setAttribute("value", input.attr('value')); | |
| form.appendChild(hiddenField); | |
| } | |
| } | |
| else if (input.attr('type') == 'text' || input.attr('type') == 'number') { | |
| var hiddenField = document.createElement("input"); | |
| hiddenField.setAttribute("type", "hidden"); | |
| hiddenField.setAttribute("name", input.attr('name')); | |
| hiddenField.setAttribute("value", input[0].value); | |
| form.appendChild(hiddenField); | |
| } | |
| else if (input.attr('type') == 'range') { | |
| var hiddenField = document.createElement("input"); | |
| hiddenField.setAttribute("type", "hidden"); | |
| hiddenField.setAttribute("name", input.attr('name')); | |
| hiddenField.setAttribute("value", input[0].value); | |
| form.appendChild(hiddenField); | |
| } | |
| else if (input.attr('type') == 'select-one') { | |
| //alert(input[0].value) | |
| var hiddenField = document.createElement("input"); | |
| hiddenField.setAttribute("type", "hidden"); | |
| hiddenField.setAttribute("name", input.attr('name')); | |
| hiddenField.setAttribute("value", input[0].value); | |
| form.appendChild(hiddenField); | |
| } | |
| else { | |
| console.log("unknown form type: \"" + input.attr('type') + "\"") | |
| } | |
| } | |
| ); | |
| */ | |
| /* | |
| // Get all the highlighted text for this instance and marshall that | |
| // into some kind of representation for the server side | |
| $(".span_container").first().each( | |
| function(index) { | |
| // we save the outerHTML to accomadate user-defined HTML inputs, | |
| // otherwise we just save the plain text | |
| if ($(this).parent().parent().attr("name") == "instance_text"){ | |
| var annotated_spans = $(this).parent().prop('outerHTML'); | |
| } else { | |
| var annotated_spans = $(this).parent().prop('innerHTML'); | |
| } | |
| // Due to the DJ's inability to write decent Javascript, we're | |
| // fully punting on the idea of doing label preprocessing here | |
| // and instead shuttling the entire HTML of the instance to | |
| // the server for python-based processing. The main issue is | |
| // figuring out the precise text offsets of the annotated | |
| // spans while dealing with nested DOM elements. | |
| var hiddenField = document.createElement("input"); | |
| hiddenField.setAttribute("type", "hidden"); | |
| hiddenField.setAttribute("name", "span-annotation"); | |
| hiddenField.setAttribute("value", annotated_spans); | |
| form.appendChild(hiddenField); | |
| //console.log(annotated_spans) | |
| } | |
| ); | |
| */ | |
| //document.body.appendChild(form); | |
| //form.submit(); | |
| } | |
| /** | |
| * Validate if the user has completed each row for multi-rate schema | |
| */ | |
| function validateForm() { | |
| var rows = document.querySelectorAll("tr[schema='multirate']"); | |
| for (var i = 0; i < rows.length; i++) { | |
| var inputs = rows[i].querySelectorAll( | |
| "input[type='radio'][validation='required']" | |
| ); | |
| if (inputs.length > 0) { | |
| var checked = Array.from(inputs).some((input) => input.checked); | |
| } else { | |
| var checked = true; | |
| } | |
| if (!checked) { | |
| alert("Please complete all the require fields"); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Validate if the input answers meet certain rules | |
| * along with any relevant key presses to request a new instance to | |
| * annotate. | |
| */ | |
| function validate_answers() { | |
| // Test whether all the required labels are checked | |
| var inputs = document.querySelectorAll( | |
| "input[validation=required_label]" | |
| ); | |
| for (var i = 0; i < inputs.length; ++i) { | |
| if ( | |
| inputs[i].getAttribute("validation") == "required_label" && | |
| inputs[i].checked == false | |
| ) { | |
| // Get the text of the legend element that contains the question | |
| var legend = inputs[i].closest("fieldset").querySelector("legend"); | |
| var question = legend.textContent; | |
| // Get the text of the answer | |
| var neededAnswer = inputs[i].getAttribute("label_name"); | |
| // Generate an alert that shows the question text and the required label | |
| // so the user knows what they missed | |
| alert( | |
| 'The question "' + | |
| question + | |
| '" requires the label "' + | |
| neededAnswer + | |
| '" to be selected to proceed' | |
| ); | |
| return false; | |
| } | |
| } | |
| // identify all the fieldsets and check if all the required forms are filled | |
| var fields = document.getElementsByTagName("fieldset"); | |
| for (var i = 0; i < fields.length; ++i) { | |
| var inputs = fields[i].querySelectorAll( | |
| "input[validation=required], select[validation=required], textarea[validation=required]" | |
| ); | |
| // continue if all there's no required inputs in the current field set | |
| if (inputs.length == 0) { | |
| continue; | |
| } | |
| //var required = true; | |
| // check if the current form requires inputs | |
| //if (inputs[0].getAttribute('validation') == 'required'){ | |
| // required= true; | |
| //} | |
| checked_flag = false; | |
| for (var j = 0; j < inputs.length; ++j) { | |
| // if a right_label is not selected, display an error msg and return false | |
| //if (inputs[j].getAttribute('validation') == 'right_label' && inputs[j].checked == false) { | |
| //alert(inputs[j].name + " must be selected to proceed"); | |
| // return false; | |
| //} | |
| // if the input is for a span annotation schema, check if the at least some span is annotated or if the | |
| // bad_text label is selected | |
| if (inputs[j].getAttribute("for_span") == "True") { | |
| //alert($(".span_container").length); | |
| if ($(".span_container").length > 0) { | |
| checked_flag = true; | |
| break; | |
| } else if ( | |
| inputs[j].name.slice(-8) == "bad_text" && | |
| inputs[j].checked == true | |
| ) { | |
| checked_flag = true; | |
| break; | |
| } | |
| } | |
| // if any of the labels is checked, set checked_flag as true; | |
| if ( | |
| inputs[j].getAttribute("for_span") != "True" && | |
| inputs[j].checked == true | |
| ) { | |
| checked_flag = true; | |
| break; | |
| } | |
| // if the input_type is number, text, select or textarea, check if it's empty | |
| // todo: the current way might not work well if there are mixed textinput and radio buttons under a sample fieldset | |
| if ( | |
| inputs[j].type == "text" || | |
| inputs[j].tagName == "TEXTAREA" || | |
| inputs[j].type == "number" || | |
| inputs[j].type == "select-one" | |
| ) { | |
| if (inputs[j].value.length == 0) { | |
| alert(inputs[j].name + " must be completed to proceed"); | |
| return false; | |
| } else { | |
| checked_flag = true; | |
| } | |
| } | |
| } | |
| // Get the text of the legend element that contains the question | |
| // | |
| // TODO: check that this works with questions for the text input fields | |
| var legend = inputs[i].closest("fieldset").querySelector("legend"); | |
| var question = legend.textContent; | |
| // if this form requires inputs, but nothing is checked, display an error msg and return false | |
| if (checked_flag == false) { | |
| alert("You must answer the following item to proceed: " + question); | |
| return false; | |
| } | |
| //alert(instance_obj.id) | |
| } | |
| return true; | |
| } | |
| /** | |
| * Sends the current state of the instance's annotation to the server, | |
| * along with any relevant key presses to request a new instance to | |
| * annotate. | |
| */ | |
| function get_new_instance(event) { | |
| var x = event.key; | |
| var action = ""; | |
| var ism = ""; | |
| //console.log(x) | |
| //console.log(firstname) | |
| //console.log(lastname) | |
| if (x == "ArrowLeft") { | |
| // Spacebar | |
| action = "prev_instance"; | |
| } else if (x == "ArrowRight") { | |
| //console.log("validateForm", validateForm()) | |
| //console.log("validate_answers", validate_answers()) | |
| if (validateForm() == true && validate_answers() == true) { | |
| action = "next_instance"; | |
| } else { | |
| return; | |
| } | |
| } else { | |
| // console.log("Unknown key press", event) | |
| return; | |
| } | |
| var instance_id = document.getElementById("instance_id").value; | |
| var time_string = document.getElementById("timecounter").innerHTML; //get time spent on this instance | |
| //time_string = '-1' | |
| var post_req = { | |
| label: ism, | |
| action: action, | |
| instance_id: instance_id, | |
| behavior_time_string: time_string, | |
| }; | |
| console.log("post_req: ", post_req); | |
| // Sends the post message to the server which will let us update the | |
| // currently displayed content | |
| post(post_req); | |
| } | |
| </script> | |
| <script> | |
| $(document).ready(function () { | |
| $('[data-toggle="tooltip"]').tooltip(); | |
| }); | |
| </script> | |
| {{var_elems | safe}} | |
| <!-- Header navigation bar --> | |
| <nav | |
| class="navbar sticky-top shadow-sm" | |
| style=" | |
| background-color: var(--primary-color); | |
| padding: 0.75rem 1rem; | |
| margin-bottom: 1.5rem; | |
| " | |
| > | |
| <div class="container-fluid d-flex align-items-center"> | |
| <!-- Task name (left aligned) --> | |
| <div class="navbar-brand"> | |
| <span | |
| class="fw-bold" | |
| style="color: var(--light-color); font-size: 1.25rem" | |
| >Simple Likert Scale Example</span | |
| > | |
| </div> | |
| <!-- Middle section with three elements --> | |
| <div | |
| class="d-flex align-items-center justify-content-center flex-grow-1 gap-4" | |
| > | |
| <!-- 1. Annotation codebook link --> | |
| <div></div> | |
| <!-- 2. Progress counter --> | |
| <div | |
| class="d-flex align-items-center" | |
| style="color: var(--light-color); margin-right: 40px" | |
| > | |
| <span | |
| >Progress: | |
| <span class="fw-medium">{{finished}}/{{total_count}}</span></span | |
| > | |
| </div> | |
| <!-- 3. Jump to item form --> | |
| <div style="margin-left: 40px"> | |
| <form | |
| action="/go_to" | |
| method="post" | |
| class="d-flex align-items-center" | |
| > | |
| <input | |
| type="hidden" | |
| name="firstname" | |
| id="a" | |
| value="{{firstname}}" | |
| /> | |
| <input | |
| type="hidden" | |
| name="lastname" | |
| id="b" | |
| value="{{lastname}}" | |
| /> | |
| <input type="hidden" name="email" id="c" value="{{username}}" /> | |
| <input type="hidden" name="src" id="src" value="go_to" /> | |
| <label | |
| for="go_to" | |
| class="me-2" | |
| style="color: var(--light-color); margin-bottom: 0" | |
| >Go to:</label | |
| > | |
| <input | |
| type="number" | |
| name="go_to" | |
| id="go_to" | |
| class="form-control form-control-sm me-2" | |
| style=" | |
| width: 70px; | |
| height: 31px; | |
| border: none; | |
| border-radius: var(--radius); | |
| " | |
| value="" | |
| onfocusin="user_input()" | |
| onfocusout="user_input_leave()" | |
| max="{{total_count}}" | |
| min="0" | |
| required | |
| /> | |
| <button | |
| type="submit" | |
| class="shadcn-button" | |
| style=" | |
| height: 31px; | |
| line-height: 1; | |
| padding: 0 0.75rem; | |
| background-color: var(--light-color); | |
| color: var(--primary-color); | |
| border: none; | |
| " | |
| > | |
| Go | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- User info and logout (right aligned) --> | |
| <div | |
| class="d-flex align-items-center" | |
| style=" | |
| background-color: rgba(255, 255, 255, 0.15); | |
| padding: 0.5rem 1rem; | |
| border-radius: var(--radius); | |
| " | |
| > | |
| <div class="me-3" style="color: var(--light-color)"> | |
| <span | |
| >Logged in as <span class="fw-medium">{{username}}</span></span | |
| > | |
| </div> | |
| <div | |
| class="border-start" | |
| style=" | |
| padding-left: 20px; | |
| height: 24px; | |
| display: flex; | |
| align-items: center; | |
| " | |
| > | |
| <a | |
| href="/logout" | |
| class="shadcn-button" | |
| style=" | |
| background-color: var(--light-color); | |
| color: var(--primary-color); | |
| height: auto; | |
| padding: 0.25rem 0.75rem !important; | |
| font-size: 0.875rem; | |
| min-width: 60px; | |
| text-align: center; | |
| border: none; | |
| text-decoration: none; | |
| " | |
| >Logout</a | |
| > | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <div class="container-fluid"> | |
| <div id="task_layout" class="shadcn-card mb-4"> | |
| <div class="shadcn-card-content"> | |
| <!--- LAYOUT GOES BELOW HERE ---> | |
| <div class="row"> | |
| <div class="col-md-12"> | |
| <div class="row"> | |
| <div class="col-md-12"> | |
| <div name="context_text" class="instance"> | |
| <div id="instance-text" name="instance_text"> | |
| {{instance | safe}} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="height: 15px;" /> | |
| </div> | |
| <div class="col-md-12"> | |
| <div class="annotation_schema"> | |
| <style> | |
| .shadcn-likert-container { | |
| border: 1px solid #E5E5EA; | |
| border-radius: 18px 0 18px 18px; | |
| padding: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| width: fit-content; | |
| max-width: 100%; | |
| margin: 1.5rem auto; | |
| font-family: ui-sans-serif, system-ui, sans-serif; | |
| position: relative; | |
| } | |
| .shadcn-likert-title { | |
| font-size: 1rem; | |
| font-weight: 500; | |
| color: black; | |
| text-align: left; | |
| width: 100%; | |
| } | |
| .shadcn-likert-scale { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| width: 100%; | |
| } | |
| .shadcn-likert-endpoint { | |
| display: flex; | |
| align-items: center; | |
| height: 2.5rem; | |
| flex: 0 0 auto; | |
| font-size: 0.875rem; | |
| color: var(--muted-foreground); | |
| padding: 0 0.5rem; | |
| max-width: 30%; | |
| text-align: center; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| hyphens: auto; | |
| } | |
| .shadcn-likert-options { | |
| display: flex; | |
| flex: 1; | |
| justify-content: space-between; | |
| position: relative; | |
| padding: 0 0.5rem; | |
| gap: 0.5rem; | |
| } | |
| .shadcn-likert-track { | |
| position: absolute; | |
| height: 2px; | |
| background-color: var(--border); | |
| left: 0.5rem; | |
| right: 0.5rem; | |
| top: 0.625rem; | |
| transform: none; | |
| z-index: 0; | |
| } | |
| .shadcn-likert-option { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .shadcn-likert-input { | |
| opacity: 0; | |
| position: absolute; | |
| width: 0; | |
| height: 0; | |
| } | |
| .shadcn-likert-button { | |
| width: 1.25rem; | |
| height: 1.25rem; | |
| border-radius: 50%; | |
| background-color: var(--secondary); | |
| border: 2px solid var(--border); | |
| margin-bottom: 0.5rem; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .shadcn-likert-input:checked + .shadcn-likert-button { | |
| background-color: var(--primary); | |
| border-color: var(--primary); | |
| transform: scale(1.1); | |
| box-shadow: 0 0 0 3px rgba(110, 86, 207, 0.2); | |
| } | |
| .shadcn-likert-input:focus + .shadcn-likert-button { | |
| box-shadow: 0 0 0 3px rgba(110, 86, 207, 0.2); | |
| } | |
| .shadcn-likert-input:hover + .shadcn-likert-button { | |
| border-color: var(--primary); | |
| } | |
| .shadcn-likert-label { | |
| font-size: 0.75rem; | |
| color: var(--muted-foreground); | |
| margin-top: 0.25rem; | |
| text-align: center; | |
| } | |
| .shadcn-likert-input:checked ~ .shadcn-likert-label { | |
| color: var(--primary); | |
| font-weight: 500; | |
| } | |
| .shadcn-likert-bad-text { | |
| display: flex; | |
| align-items: center; | |
| margin-top: 0.75rem; | |
| padding: 0.5rem 0; | |
| } | |
| .shadcn-likert-bad-text-label { | |
| margin-left: 0.5rem; | |
| font-size: 0.875rem; | |
| color: var(--text-color); | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| flex: 1; | |
| } | |
| fieldset[schema] { | |
| width: fit-content; | |
| max-width: 100%; | |
| overflow: visible; | |
| } | |
| .ai-help { | |
| width: 9.375rem; | |
| height: 2.063rem; | |
| border: 1px solid #E5E5EA; | |
| border-radius: 18px; | |
| position: absolute; | |
| top: -0.75rem; | |
| right: -0.05rem; | |
| background-color: white; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .ai-help-word { | |
| font-size: 1rem; | |
| margin: 0; | |
| } | |
| .hint { | |
| cursor: pointer; | |
| } | |
| .hint:hover { | |
| color: #6E56CF; | |
| } | |
| .tooltip { | |
| position: absolute; | |
| top: 120%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background-color: white; | |
| padding: 1rem; | |
| border-radius: .5rem; | |
| white-space: nowrap; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.2); | |
| z-index: 1000; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: opacity 0.3s, visibility 0.3s; | |
| min-width: 20rem; | |
| max-width: 24rem; | |
| overflow-wrap: break-word; | |
| word-wrap: break-word; | |
| white-space: normal; | |
| max-height: 15rem; | |
| overflow: auto; | |
| } | |
| .tooltip.active { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .reasoning { | |
| font-weight: bold; | |
| } | |
| .tooltip-text{ | |
| margin: 0; | |
| } | |
| </style> | |
| <form id="relevance" class="annotation-form likert shadcn-likert-container" action="javascript:void(0)" data-annotation-id="0"> | |
| <div class="ai-help"> | |
| <h3 class="ai-help-word"><span class="hint">Hint</span> | <span>Keyword</span></h3> | |
| <div class="tooltip"> | |
| <p class="tooltip-text"> | |
| <span class="reasoning">Reasoning:</span> {ai} | |
| </p> | |
| </div> | |
| </div> | |
| <fieldset schema="relevance" style="border: none; padding: 0; margin: 0; width: auto; min-width: fit-content;"> | |
| <legend class="shadcn-likert-title">Relevance</legend> | |
| <div class="shadcn-likert-scale" style="max-width: min(100%, calc(300px + 5 * 40px + 250px));"> | |
| <div class="shadcn-likert-endpoint">Not Relevant</div> | |
| <div class="shadcn-likert-options"> | |
| <div class="shadcn-likert-track"></div> | |
| <div class="shadcn-likert-option"> | |
| <input class="relevance shadcn-likert-input" | |
| type="radio" | |
| id="relevance:::1" | |
| name="relevance:::1" | |
| value="1" | |
| schema="relevance" | |
| label_name="1" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="relevance:::1"></label> | |
| <span class="shadcn-likert-label">1</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="relevance shadcn-likert-input" | |
| type="radio" | |
| id="relevance:::2" | |
| name="relevance:::2" | |
| value="2" | |
| schema="relevance" | |
| label_name="2" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="relevance:::2"></label> | |
| <span class="shadcn-likert-label">2</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="relevance shadcn-likert-input" | |
| type="radio" | |
| id="relevance:::3" | |
| name="relevance:::3" | |
| value="3" | |
| schema="relevance" | |
| label_name="3" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="relevance:::3"></label> | |
| <span class="shadcn-likert-label">3</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="relevance shadcn-likert-input" | |
| type="radio" | |
| id="relevance:::4" | |
| name="relevance:::4" | |
| value="4" | |
| schema="relevance" | |
| label_name="4" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="relevance:::4"></label> | |
| <span class="shadcn-likert-label">4</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="relevance shadcn-likert-input" | |
| type="radio" | |
| id="relevance:::5" | |
| name="relevance:::5" | |
| value="5" | |
| schema="relevance" | |
| label_name="5" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="relevance:::5"></label> | |
| <span class="shadcn-likert-label">5</span> | |
| </div> | |
| </div> | |
| <div class="shadcn-likert-endpoint">Super Relevant</div> | |
| </div> | |
| </fieldset></form> | |
| <style> | |
| .shadcn-likert-container { | |
| border: 1px solid #E5E5EA; | |
| border-radius: 18px 0 18px 18px; | |
| padding: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| width: fit-content; | |
| max-width: 100%; | |
| margin: 1.5rem auto; | |
| font-family: ui-sans-serif, system-ui, sans-serif; | |
| position: relative; | |
| } | |
| .shadcn-likert-title { | |
| font-size: 1rem; | |
| font-weight: 500; | |
| color: black; | |
| text-align: left; | |
| width: 100%; | |
| } | |
| .shadcn-likert-scale { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| width: 100%; | |
| } | |
| .shadcn-likert-endpoint { | |
| display: flex; | |
| align-items: center; | |
| height: 2.5rem; | |
| flex: 0 0 auto; | |
| font-size: 0.875rem; | |
| color: var(--muted-foreground); | |
| padding: 0 0.5rem; | |
| max-width: 30%; | |
| text-align: center; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| hyphens: auto; | |
| } | |
| .shadcn-likert-options { | |
| display: flex; | |
| flex: 1; | |
| justify-content: space-between; | |
| position: relative; | |
| padding: 0 0.5rem; | |
| gap: 0.5rem; | |
| } | |
| .shadcn-likert-track { | |
| position: absolute; | |
| height: 2px; | |
| background-color: var(--border); | |
| left: 0.5rem; | |
| right: 0.5rem; | |
| top: 0.625rem; | |
| transform: none; | |
| z-index: 0; | |
| } | |
| .shadcn-likert-option { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .shadcn-likert-input { | |
| opacity: 0; | |
| position: absolute; | |
| width: 0; | |
| height: 0; | |
| } | |
| .shadcn-likert-button { | |
| width: 1.25rem; | |
| height: 1.25rem; | |
| border-radius: 50%; | |
| background-color: var(--secondary); | |
| border: 2px solid var(--border); | |
| margin-bottom: 0.5rem; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .shadcn-likert-input:checked + .shadcn-likert-button { | |
| background-color: var(--primary); | |
| border-color: var(--primary); | |
| transform: scale(1.1); | |
| box-shadow: 0 0 0 3px rgba(110, 86, 207, 0.2); | |
| } | |
| .shadcn-likert-input:focus + .shadcn-likert-button { | |
| box-shadow: 0 0 0 3px rgba(110, 86, 207, 0.2); | |
| } | |
| .shadcn-likert-input:hover + .shadcn-likert-button { | |
| border-color: var(--primary); | |
| } | |
| .shadcn-likert-label { | |
| font-size: 0.75rem; | |
| color: var(--muted-foreground); | |
| margin-top: 0.25rem; | |
| text-align: center; | |
| } | |
| .shadcn-likert-input:checked ~ .shadcn-likert-label { | |
| color: var(--primary); | |
| font-weight: 500; | |
| } | |
| .shadcn-likert-bad-text { | |
| display: flex; | |
| align-items: center; | |
| margin-top: 0.75rem; | |
| padding: 0.5rem 0; | |
| } | |
| .shadcn-likert-bad-text-label { | |
| margin-left: 0.5rem; | |
| font-size: 0.875rem; | |
| color: var(--text-color); | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| flex: 1; | |
| } | |
| fieldset[schema] { | |
| width: fit-content; | |
| max-width: 100%; | |
| overflow: visible; | |
| } | |
| .ai-help { | |
| width: 9.375rem; | |
| height: 2.063rem; | |
| border: 1px solid #E5E5EA; | |
| border-radius: 18px; | |
| position: absolute; | |
| top: -0.75rem; | |
| right: -0.05rem; | |
| background-color: white; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .ai-help-word { | |
| font-size: 1rem; | |
| margin: 0; | |
| } | |
| .hint { | |
| cursor: pointer; | |
| } | |
| .hint:hover { | |
| color: #6E56CF; | |
| } | |
| .tooltip { | |
| position: absolute; | |
| top: 120%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background-color: white; | |
| padding: 1rem; | |
| border-radius: .5rem; | |
| white-space: nowrap; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.2); | |
| z-index: 1000; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: opacity 0.3s, visibility 0.3s; | |
| min-width: 20rem; | |
| max-width: 24rem; | |
| overflow-wrap: break-word; | |
| word-wrap: break-word; | |
| white-space: normal; | |
| max-height: 15rem; | |
| overflow: auto; | |
| } | |
| .tooltip.active { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .reasoning { | |
| font-weight: bold; | |
| } | |
| .tooltip-text{ | |
| margin: 0; | |
| } | |
| </style> | |
| <form id="fluency" class="annotation-form likert shadcn-likert-container" action="javascript:void(0)" data-annotation-id="1"> | |
| <div class="ai-help"> | |
| <h3 class="ai-help-word"><span class="hint">Hint</span> | <span>Keyword</span></h3> | |
| <div class="tooltip"> | |
| <p class="tooltip-text"> | |
| <span class="reasoning">Reasoning:</span> {ai} | |
| </p> | |
| </div> | |
| </div> | |
| <fieldset schema="fluency" style="border: none; padding: 0; margin: 0; width: auto; min-width: fit-content;"> | |
| <legend class="shadcn-likert-title">Fluency</legend> | |
| <div class="shadcn-likert-scale" style="max-width: min(100%, calc(300px + 10 * 40px + 250px));"> | |
| <div class="shadcn-likert-endpoint">Not fluent</div> | |
| <div class="shadcn-likert-options"> | |
| <div class="shadcn-likert-track"></div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::1" | |
| name="fluency:::1" | |
| value="1" | |
| schema="fluency" | |
| label_name="1" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::1"></label> | |
| <span class="shadcn-likert-label">1</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::2" | |
| name="fluency:::2" | |
| value="2" | |
| schema="fluency" | |
| label_name="2" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::2"></label> | |
| <span class="shadcn-likert-label">2</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::3" | |
| name="fluency:::3" | |
| value="3" | |
| schema="fluency" | |
| label_name="3" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::3"></label> | |
| <span class="shadcn-likert-label">3</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::4" | |
| name="fluency:::4" | |
| value="4" | |
| schema="fluency" | |
| label_name="4" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::4"></label> | |
| <span class="shadcn-likert-label">4</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::5" | |
| name="fluency:::5" | |
| value="5" | |
| schema="fluency" | |
| label_name="5" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::5"></label> | |
| <span class="shadcn-likert-label">5</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::6" | |
| name="fluency:::6" | |
| value="6" | |
| schema="fluency" | |
| label_name="6" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::6"></label> | |
| <span class="shadcn-likert-label">6</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::7" | |
| name="fluency:::7" | |
| value="7" | |
| schema="fluency" | |
| label_name="7" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::7"></label> | |
| <span class="shadcn-likert-label">7</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::8" | |
| name="fluency:::8" | |
| value="8" | |
| schema="fluency" | |
| label_name="8" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::8"></label> | |
| <span class="shadcn-likert-label">8</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::9" | |
| name="fluency:::9" | |
| value="9" | |
| schema="fluency" | |
| label_name="9" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::9"></label> | |
| <span class="shadcn-likert-label">9</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="fluency shadcn-likert-input" | |
| type="radio" | |
| id="fluency:::10" | |
| name="fluency:::10" | |
| value="0" | |
| schema="fluency" | |
| label_name="0" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="fluency:::10"></label> | |
| <span class="shadcn-likert-label">10</span> | |
| </div> | |
| </div> | |
| <div class="shadcn-likert-endpoint">Super fluent</div> | |
| </div> | |
| </fieldset></form> | |
| <style> | |
| .shadcn-likert-container { | |
| border: 1px solid #E5E5EA; | |
| border-radius: 18px 0 18px 18px; | |
| padding: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| width: fit-content; | |
| max-width: 100%; | |
| margin: 1.5rem auto; | |
| font-family: ui-sans-serif, system-ui, sans-serif; | |
| position: relative; | |
| } | |
| .shadcn-likert-title { | |
| font-size: 1rem; | |
| font-weight: 500; | |
| color: black; | |
| text-align: left; | |
| width: 100%; | |
| } | |
| .shadcn-likert-scale { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| width: 100%; | |
| } | |
| .shadcn-likert-endpoint { | |
| display: flex; | |
| align-items: center; | |
| height: 2.5rem; | |
| flex: 0 0 auto; | |
| font-size: 0.875rem; | |
| color: var(--muted-foreground); | |
| padding: 0 0.5rem; | |
| max-width: 30%; | |
| text-align: center; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| hyphens: auto; | |
| } | |
| .shadcn-likert-options { | |
| display: flex; | |
| flex: 1; | |
| justify-content: space-between; | |
| position: relative; | |
| padding: 0 0.5rem; | |
| gap: 0.5rem; | |
| } | |
| .shadcn-likert-track { | |
| position: absolute; | |
| height: 2px; | |
| background-color: var(--border); | |
| left: 0.5rem; | |
| right: 0.5rem; | |
| top: 0.625rem; | |
| transform: none; | |
| z-index: 0; | |
| } | |
| .shadcn-likert-option { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .shadcn-likert-input { | |
| opacity: 0; | |
| position: absolute; | |
| width: 0; | |
| height: 0; | |
| } | |
| .shadcn-likert-button { | |
| width: 1.25rem; | |
| height: 1.25rem; | |
| border-radius: 50%; | |
| background-color: var(--secondary); | |
| border: 2px solid var(--border); | |
| margin-bottom: 0.5rem; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .shadcn-likert-input:checked + .shadcn-likert-button { | |
| background-color: var(--primary); | |
| border-color: var(--primary); | |
| transform: scale(1.1); | |
| box-shadow: 0 0 0 3px rgba(110, 86, 207, 0.2); | |
| } | |
| .shadcn-likert-input:focus + .shadcn-likert-button { | |
| box-shadow: 0 0 0 3px rgba(110, 86, 207, 0.2); | |
| } | |
| .shadcn-likert-input:hover + .shadcn-likert-button { | |
| border-color: var(--primary); | |
| } | |
| .shadcn-likert-label { | |
| font-size: 0.75rem; | |
| color: var(--muted-foreground); | |
| margin-top: 0.25rem; | |
| text-align: center; | |
| } | |
| .shadcn-likert-input:checked ~ .shadcn-likert-label { | |
| color: var(--primary); | |
| font-weight: 500; | |
| } | |
| .shadcn-likert-bad-text { | |
| display: flex; | |
| align-items: center; | |
| margin-top: 0.75rem; | |
| padding: 0.5rem 0; | |
| } | |
| .shadcn-likert-bad-text-label { | |
| margin-left: 0.5rem; | |
| font-size: 0.875rem; | |
| color: var(--text-color); | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| flex: 1; | |
| } | |
| fieldset[schema] { | |
| width: fit-content; | |
| max-width: 100%; | |
| overflow: visible; | |
| } | |
| .ai-help { | |
| width: 9.375rem; | |
| height: 2.063rem; | |
| border: 1px solid #E5E5EA; | |
| border-radius: 18px; | |
| position: absolute; | |
| top: -0.75rem; | |
| right: -0.05rem; | |
| background-color: white; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .ai-help-word { | |
| font-size: 1rem; | |
| margin: 0; | |
| } | |
| .hint { | |
| cursor: pointer; | |
| } | |
| .hint:hover { | |
| color: #6E56CF; | |
| } | |
| .tooltip { | |
| position: absolute; | |
| top: 120%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background-color: white; | |
| padding: 1rem; | |
| border-radius: .5rem; | |
| white-space: nowrap; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.2); | |
| z-index: 1000; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: opacity 0.3s, visibility 0.3s; | |
| min-width: 20rem; | |
| max-width: 24rem; | |
| overflow-wrap: break-word; | |
| word-wrap: break-word; | |
| white-space: normal; | |
| max-height: 15rem; | |
| overflow: auto; | |
| } | |
| .tooltip.active { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .reasoning { | |
| font-weight: bold; | |
| } | |
| .tooltip-text{ | |
| margin: 0; | |
| } | |
| </style> | |
| <form id="coherence" class="annotation-form likert shadcn-likert-container" action="javascript:void(0)" data-annotation-id="2"> | |
| <div class="ai-help"> | |
| <h3 class="ai-help-word"><span class="hint">Hint</span> | <span>Keyword</span></h3> | |
| <div class="tooltip"> | |
| <p class="tooltip-text"> | |
| <span class="reasoning">Reasoning:</span> {ai} | |
| </p> | |
| </div> | |
| </div> | |
| <fieldset schema="coherence" style="border: none; padding: 0; margin: 0; width: auto; min-width: fit-content;"> | |
| <legend class="shadcn-likert-title">Coherence</legend> | |
| <div class="shadcn-likert-scale" style="max-width: min(100%, calc(300px + 5 * 40px + 250px));"> | |
| <div class="shadcn-likert-endpoint">Not Coherent</div> | |
| <div class="shadcn-likert-options"> | |
| <div class="shadcn-likert-track"></div> | |
| <div class="shadcn-likert-option"> | |
| <input class="coherence shadcn-likert-input" | |
| type="radio" | |
| id="coherence:::1" | |
| name="coherence:::1" | |
| value="1" | |
| schema="coherence" | |
| label_name="1" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="coherence:::1"></label> | |
| <span class="shadcn-likert-label">1</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="coherence shadcn-likert-input" | |
| type="radio" | |
| id="coherence:::2" | |
| name="coherence:::2" | |
| value="2" | |
| schema="coherence" | |
| label_name="2" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="coherence:::2"></label> | |
| <span class="shadcn-likert-label">2</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="coherence shadcn-likert-input" | |
| type="radio" | |
| id="coherence:::3" | |
| name="coherence:::3" | |
| value="3" | |
| schema="coherence" | |
| label_name="3" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="coherence:::3"></label> | |
| <span class="shadcn-likert-label">3</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="coherence shadcn-likert-input" | |
| type="radio" | |
| id="coherence:::4" | |
| name="coherence:::4" | |
| value="4" | |
| schema="coherence" | |
| label_name="4" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="coherence:::4"></label> | |
| <span class="shadcn-likert-label">4</span> | |
| </div> | |
| <div class="shadcn-likert-option"> | |
| <input class="coherence shadcn-likert-input" | |
| type="radio" | |
| id="coherence:::5" | |
| name="coherence:::5" | |
| value="5" | |
| schema="coherence" | |
| label_name="5" | |
| selection_constraint="single" | |
| validation="required" | |
| onclick="onlyOne(this);registerAnnotation(this);"> | |
| <label class="shadcn-likert-button" for="coherence:::5"></label> | |
| <span class="shadcn-likert-label">5</span> | |
| </div> | |
| </div> | |
| <div class="shadcn-likert-endpoint">Super Coherent</div> | |
| </div> | |
| </fieldset></form> | |
| </div> | |
| </div> | |
| </div> | |
| <!--- LAYOUT GOES ABOVE ---> | |
| </div> | |
| </div> | |
| <!-- Test --> | |
| <!-- <div id="task_layout" class="shadcn-card mb-4"> | |
| <div class="shadcn-card-content"> | |
| <strong>AI Label Suggestion</strong> | |
| <div>{{ ai }}</div> | |
| </div> | |
| </div> --> | |
| <!-- Test --> | |
| <div class="my-4"> | |
| <!-- Reorganized button layout with 3 columns --> | |
| <div class="d-flex justify-content-between align-items-center"> | |
| <!-- Statistics button (left) --> | |
| <div> | |
| <a | |
| role="button" | |
| href="#" | |
| class="shadcn-button shadcn-button-outline openbtn" | |
| target_id="Statpanel" | |
| width="475px" | |
| onclick="openNav(this.getAttribute('target_id'),this.getAttribute('width'))" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="16" | |
| height="16" | |
| fill="currentColor" | |
| class="bi bi-bar-chart-line me-1" | |
| viewBox="0 0 16 16" | |
| > | |
| <path | |
| d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1V2zm1 12h2V2h-2v12zm-3 0V7H7v7h2zm-5 0v-3H2v3h2z" | |
| /> | |
| </svg> | |
| Statistics | |
| </a> | |
| </div> | |
| <!-- Previous/Next buttons (center) --> | |
| <div> | |
| <a | |
| class="shadcn-button shadcn-button-outline me-2" | |
| href="#" | |
| role="button" | |
| onclick="click_to_prev()" | |
| style="vertical-align: middle" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="16" | |
| height="16" | |
| fill="currentColor" | |
| class="bi bi-arrow-left" | |
| viewBox="0 0 16 16" | |
| style=" | |
| display: inline-block; | |
| vertical-align: middle; | |
| margin-right: 0.25rem; | |
| " | |
| > | |
| <path | |
| fill-rule="evenodd" | |
| d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z" | |
| /> | |
| </svg> | |
| <span style="display: inline-block; vertical-align: middle" | |
| >Previous</span | |
| > | |
| </a> | |
| <a | |
| class="shadcn-button shadcn-button-primary" | |
| href="#" | |
| role="button" | |
| onclick="click_to_next()" | |
| style="vertical-align: middle" | |
| > | |
| <span style="display: inline-block; vertical-align: middle" | |
| >Next</span | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="16" | |
| height="16" | |
| fill="currentColor" | |
| class="bi bi-arrow-right" | |
| viewBox="0 0 16 16" | |
| style=" | |
| display: inline-block; | |
| vertical-align: middle; | |
| margin-left: 0.25rem; | |
| " | |
| > | |
| <path | |
| fill-rule="evenodd" | |
| d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z" | |
| /> | |
| </svg> | |
| </a> | |
| </div> | |
| <!-- Help button (right) --> | |
| <div> | |
| <a | |
| role="button" | |
| href="#" | |
| class="shadcn-button shadcn-button-outline openbtn" | |
| target_id="mySidepanel" | |
| onclick="openNav(this.getAttribute('target_id'))" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="16" | |
| height="16" | |
| fill="currentColor" | |
| class="bi bi-question-circle me-1" | |
| viewBox="0 0 16 16" | |
| > | |
| <path | |
| d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" | |
| /> | |
| <path | |
| d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" | |
| /> | |
| </svg> | |
| Help | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| <hr style="color: var(--border)" /> | |
| <footer | |
| class="py-2 text-center" | |
| style="font-size: 0.75rem; color: var(--muted-foreground)" | |
| > | |
| Copyright © 2025 | |
| <a | |
| href="https://blablablab.si.umich.edu/" | |
| style="color: var(--primary); text-decoration: none" | |
| >Blablablab</a | |
| > | |
| <span class="mx-2">|</span> | |
| <a | |
| href="https://github.com/davidjurgens/potato" | |
| style="color: var(--primary); text-decoration: none" | |
| >Chat with us on GitHub</a | |
| > | |
| <span class="mx-2">|</span> | |
| <a | |
| href="https://github.com/davidjurgens/potato#cite-us" | |
| style="color: var(--primary); text-decoration: none" | |
| >Cite Us</a | |
| > | |
| </footer> | |
| </div> | |
| <script> | |
| /* Set the width of the sidebar to 250px (show it) */ | |
| function openNav(target_id, width = "350px") { | |
| document.getElementById(target_id).style.width = width; | |
| } | |
| /* Set the width of the sidebar to 0 (hide it) */ | |
| function closeNav(target_id) { | |
| document.getElementById(target_id).style.width = "0"; | |
| } | |
| function closeNav2(target_id) { | |
| // document.getElementById(target_id).style.height = "20px"; | |
| console.error(document.getElementById(target_id).style.display); | |
| if ( | |
| document.getElementById(target_id).style.display == "block" || | |
| document.getElementById(target_id).style.display == "" | |
| ) { | |
| document.getElementById(target_id).style.display = "none"; | |
| localStorage.setItem("show_instructions", "false"); | |
| } else { | |
| document.getElementById(target_id).style.display = "block"; | |
| localStorage.setItem("show_instructions", "true"); | |
| } | |
| } | |
| /* Keep the instructions hidden/shown across instance transitions based on | |
| what the user had selected */ | |
| window.addEventListener("load", function () { | |
| var show = localStorage.getItem("show_instructions"); | |
| if (document.getElementById("instructions")) { | |
| if (show === "true") { | |
| document.getElementById("instructions").style.display = "block"; | |
| } else { | |
| document.getElementById("instructions").style.display = "none"; | |
| } | |
| } | |
| }); | |
| function watch_text_boxes() { | |
| let timer; | |
| const waitTime = 1000; | |
| var text_boxes = document.querySelectorAll("input[type=text]"); | |
| for (var i = 0; i < text_boxes.length; i++) { | |
| //console.log('adding event listener to text box'); | |
| //console.log(text_boxes[i]); | |
| text_boxes[i].addEventListener("input", function (event) { | |
| console.log("text box input event"); | |
| clearTimeout(timer); | |
| timer = setTimeout(() => { | |
| registerTextAnnotation(event.target); | |
| }, waitTime); | |
| }); | |
| } | |
| var text_areas = document.querySelectorAll("textarea"); | |
| //console.log('num text_areas: ' + text_areas.length); | |
| for (var i = 0; i < text_areas.length; i++) { | |
| //console.log('adding event listener to text area'); | |
| //console.dir(text_areas[i]); | |
| text_areas[i].addEventListener("input", function (event) { | |
| //console.log('text area input event') | |
| clearTimeout(timer); | |
| timer = setTimeout(() => { | |
| registerTextAnnotation(event.target); | |
| }, waitTime); | |
| }); | |
| } | |
| } | |
| watch_text_boxes(); | |
| </script> | |
| </body> | |
| </html> | |