FelixFahne/Annotation_Tool
0
1// script.js2 3// --- state ---4let dialogues = [];5let selectedColor = '';6let selectedLabelName = '';7 8// --- predefined colors for each label ---9const labelColors = {10 'reference word': '#ffadad',11 'noun & verb collocation in proper form': '#ffd6a5',12 'code-switching for communicative purposes': '#fdffb6',13 'negotiation of meaning': '#caffbf',14 'tense choice to indicate interactive aims': '#9bf6ff',15 'routinized resources': '#a0c4ff',16 'subordinate clauses': '#bdb2ff',17 'backchannels': '#ff7eb9',18 'question-based responses': '#ff65a3',19 'formulaic responses': '#7afcff',20 'collaborative finishes': '#feff9c',21 'epistemic copulas': '#fff740',22 'epistemic modals': '#ff9ee7',23 'adjectives/adverbs expressing possibility': '#ffd700',24 'non-factive verb phrase structure': '#c1aff0',25 'impersonal subject + non-factive verb + NP': '#6eb5ff',26 'feedback in the next turn': '#ffc8a2',27 'topic extension with clear new context': '#a79aff',28 'topic extension under the previous direction': '#ffaaa5',29 'topic extension with the same content': '#a8e6cf',30 'repeat and no topic extension': '#dcedc1',31 'no topic extension and stop the topic at this point': '#ff8b94',32 'conversation opening': '#ffcccb',33 'conversation closing': '#f3d1f4',34 'overall tone choice: very formal': '#d5a6bd',35 'overall tone choice: quite formal and some expressions are not that formal': '#cfe2f3',36 'overall tone choice: relatively not formal, most expressions are quite informal': '#d9ead3',37 'overall tone choice: quite informal, but some expressions are still formal': '#fce5cd',38 'overall tone choice: very informal': '#fff2cc'39};40 41// --- helper to create one label button + wrapper ---42function addLabelButton(labelName) {43 const list = document.getElementById('labels-list');44 const wrapper = document.createElement('div');45 wrapper.className = 'label-wrapper';46 47 const btn = document.createElement('button');48 btn.className = 'label-btn';49 btn.textContent = labelName;50 const color = labelColors[labelName] || '#cccccc';51 btn.style.backgroundColor = color;52 // Schriftgröße, Innenabstand und Zeilenhöhe setzen für bessere Lesbarkeit53 btn.style.fontSize = '1.1rem'; // ca. 18px54 btn.style.lineHeight = '1.4'; // für mehr Abstand zwischen den Zeilen55 btn.style.padding = '0.5rem 0.8rem'; // oben/unten 8px, links/rechts 12px56 btn.style.fontWeight = '600'; // etwas fetter57 btn.dataset.color = color;58 btn.addEventListener('click', () => {59 selectedColor = color;60 selectedLabelName = labelName;61 });62 63 64 65 const deleteBtn = document.createElement('button');66 //deleteBtn.className = 'delete-label-btn';67 //deleteBtn.innerHTML = '<i class="fas fa-times"></i>';68 //deleteBtn.addEventListener('click', () => {69 //list.removeChild(wrapper);70 //if (selectedLabelName === labelName) {71 //selectedLabelName = '';72 // selectedColor = '';73 // }74 //});75 76 wrapper.append(btn, deleteBtn);77 list.appendChild(wrapper);78}79 80// --- build the three label sections ---81function addLabelsSection(titleText, labels) {82 const list = document.getElementById('labels-list');83 const title = document.createElement('h2');84 title.textContent = titleText;85 list.appendChild(title);86 labels.forEach(addLabelButton);87}88 89// --- show / hide the sidebar ---90document.addEventListener('DOMContentLoaded', () => {91 const labelsList = document.getElementById('labels-list');92 const toggleBtn = document.getElementById('toggle-labels-btn');93 let isVisible = true;94 95 labelsList.style.display = 'block';96 toggleBtn.textContent = 'Hide Labels';97 98 toggleBtn.addEventListener('click', () => {99 isVisible = !isVisible;100 labelsList.style.display = isVisible ? 'block' : 'none';101 toggleBtn.textContent = isVisible ? 'Hide Labels' : 'Show Labels';102 });103 104 // add all predefined labels105 addLabelsSection('Token level labels', [106 'reference word',107 'noun & verb collocation in proper form',108 'code-switching for communicative purposes',109 'negotiation of meaning',110 'tense choice to indicate interactive aims',111 'routinized resources',112 'subordinate clauses'113 ]);114 addLabelsSection('Utterance level labels', [115 'backchannels',116 'question-based responses',117 'formulaic responses',118 'collaborative finishes',119 'epistemic copulas',120 'epistemic modals',121 'adjectives/adverbs expressing possibility',122 'non-factive verb phrase structure',123 'impersonal subject + non-factive verb + NP',124 'feedback in the next turn'125 ]);126 addLabelsSection('Dialogue level labels', [127 'topic extension with clear new context',128 'topic extension under the previous direction',129 'topic extension with the same content',130 'repeat and no topic extension',131 'no topic extension and stop the topic at this point',132 'conversation opening',133 'conversation closing',134 'overall tone choice: very formal',135 'overall tone choice: quite formal and some expressions are not that formal',136 'overall tone choice: relatively not formal, most expressions are quite informal',137 'overall tone choice: quite informal, but some expressions are still formal',138 'overall tone choice: very informal'139 ]);140});141 142// --- file upload & parsing ---143document.getElementById('file-input').addEventListener('change', (e) => {144 const file = e.target.files[0];145 if (!file) return;146 const reader = new FileReader();147 reader.onload = evt => {148 try {149 const data = JSON.parse(evt.target.result);150 dialogues = [];151 data.forEach(item => {152 if (Array.isArray(item.messages)) {153 item.messages.forEach(m => {154 if (m.role && m.content && m.time) {155 dialogues.push({ role: m.role, content: m.content, time: m.time });156 }157 });158 }159 });160 } catch (err) {161 alert('Failed to parse JSON: ' + err.message);162 }163 };164 reader.readAsText(file);165});166 167// --- render table ---168document.getElementById('confirm-button').addEventListener('click', () => {169 const container = document.getElementById('dialogue-content');170 container.innerHTML = '';171 172 const table = document.createElement('table');173 const thead = document.createElement('thead');174 const headerRow = document.createElement('tr');175 ['role','content','time'].forEach(text => {176 const th = document.createElement('th');177 th.textContent = text;178 headerRow.appendChild(th);179 });180 thead.appendChild(headerRow);181 table.appendChild(thead);182 183 const tbody = document.createElement('tbody');184 dialogues.forEach(d => {185 const tr = document.createElement('tr');186 ['role','content','time'].forEach(key => {187 const td = document.createElement('td');188 td.textContent = d[key];189 tr.appendChild(td);190 });191 tbody.appendChild(tr);192 });193 table.appendChild(tbody);194 container.appendChild(table);195 196 document.getElementById('download-button').style.display = 'inline-block';197});198 199// --- highlighting logic ---200function clearHighlights() {201 document.querySelectorAll('.highlight').forEach(el => {202 const txt = document.createTextNode(el.textContent);203 el.parentNode.replaceChild(txt, el);204 });205}206 207document.getElementById('clear-highlights').addEventListener('click', clearHighlights);208 209let deleteMode = false;210function cancelHighlight(e) {211 if (e.target.classList.contains('highlight')) {212 const txt = document.createTextNode(e.target.textContent);213 e.target.parentNode.replaceChild(txt, e.target);214 }215}216 217document.getElementById('delete-highlight').addEventListener('click', function(){218 const container = document.getElementById('dialogue-content');219 deleteMode = !deleteMode;220 if (deleteMode) {221 container.addEventListener('click', cancelHighlight, true);222 this.textContent = 'Click highlighted text to cancel highlighting';223 this.style.backgroundColor = '#aaa';224 } else {225 container.removeEventListener('click', cancelHighlight, true);226 this.textContent = 'Cancel annotation';227 this.style.backgroundColor = '#0056b3';228 }229});230 231// apply highlight on mouseup232document.getElementById('dialogue-content').addEventListener('mouseup', () => {233 const sel = window.getSelection();234 if (!sel.isCollapsed && selectedColor && sel.toString().trim()) {235 const range = sel.getRangeAt(0);236 const span = document.createElement('span');237 span.className = 'highlight';238 span.style.backgroundColor = selectedColor;239 span.dataset.label = selectedLabelName;240 range.surroundContents(span);241 sel.removeAllRanges();242 }243});244 245// --- collect annotated data & download XLSX ---246function collectAnnotatedData() {247 const levels = {248 /* same mapping as before, omitted for brevity */249 };250 const out = [];251 document.querySelectorAll('.highlight').forEach(h => {252 const tr = h.closest('tr');253 if (!tr) return;254 const role = tr.cells[0].textContent;255 const label = h.dataset.label;256 const level = levels[label] || 'Unknown';257 const full = tr.cells[1].textContent;258 const snippet = h.textContent;259 const marked = full.replace(snippet, `&&&&${snippet}&&&&`);260 out.push({ Role: role, Label: label, LabelLevel: level, Content: marked });261 });262 return out;263}264 265function downloadExcel(data) {266 const ws = XLSX.utils.json_to_sheet(data);267 const wb = XLSX.utils.book_new();268 XLSX.utils.book_append_sheet(wb, ws, 'Annotations');269 XLSX.writeFile(wb, 'annotations.xlsx');270}271 272document.getElementById('download-button').addEventListener('click', () => {273 const data = collectAnnotatedData();274 if (data.length) downloadExcel(data);275 else alert('No annotations to download.');276});277 