216 lines
9.0 KiB
JavaScript
216 lines
9.0 KiB
JavaScript
// Fetch and display training project name in nav bar
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const trainingProjectId = urlParams.get('id');
|
|
if (!trainingProjectId) return;
|
|
|
|
// Fetch training project, details, and all LabelStudioProjects
|
|
Promise.all([
|
|
fetch(`/api/training-projects`).then(res => res.json()),
|
|
fetch(`/api/training-project-details`).then(res => res.json()),
|
|
fetch(`/api/label-studio-projects`).then(res => res.json())
|
|
]).then(([projects, detailsList, labelStudioProjects]) => {
|
|
// Find the selected training project
|
|
const project = projects.find(p => p.project_id == trainingProjectId || p.id == trainingProjectId);
|
|
// Find the details entry for this project
|
|
const details = detailsList.find(d => d.project_id == trainingProjectId);
|
|
if (!project || !details) return;
|
|
// Get the stored classes from training project
|
|
const storedClasses = Array.isArray(project.classes) ? project.classes : [];
|
|
// Get related LabelStudioProject IDs
|
|
const relatedIds = Array.isArray(details.annotation_projects) ? details.annotation_projects : [];
|
|
// Filter LabelStudioProjects to only those related
|
|
const relatedProjects = labelStudioProjects.filter(lp => relatedIds.includes(lp.project_id));
|
|
|
|
// Render cards for each related LabelStudioProject
|
|
const detailsDiv = document.getElementById('details');
|
|
detailsDiv.innerHTML = '';
|
|
// Find the longest label name for sizing
|
|
let maxLabelLength = 0;
|
|
relatedProjects.forEach(lp => {
|
|
const classNames = Object.keys(lp.annotationCounts || {});
|
|
classNames.forEach(className => {
|
|
if (className && className.trim() !== '' && className.length > maxLabelLength) {
|
|
maxLabelLength = className.length;
|
|
}
|
|
});
|
|
});
|
|
// Use ch unit for width to fit the longest text
|
|
const labelWidth = `${maxLabelLength + 2}ch`;
|
|
|
|
// Find the longest project name for sizing
|
|
let maxProjectNameLength = 0;
|
|
relatedProjects.forEach(lp => {
|
|
const nameLength = (lp.title || String(lp.project_id)).length;
|
|
if (nameLength > maxProjectNameLength) maxProjectNameLength = nameLength;
|
|
});
|
|
const projectNameWidth = `${maxProjectNameLength + 2}ch`;
|
|
|
|
// Find the card with the most classes
|
|
let maxClassCount = 0;
|
|
relatedProjects.forEach(lp => {
|
|
const classNames = Object.keys(lp.annotationCounts || {});
|
|
if (classNames.length > maxClassCount) maxClassCount = classNames.length;
|
|
});
|
|
// Set a fixed width for the class rows container
|
|
const classRowHeight = 38; // px, adjust if needed
|
|
const classRowsWidth = `${maxClassCount * 180}px`;
|
|
|
|
relatedProjects.forEach(lp => {
|
|
// Get original class names from annotationCounts
|
|
const classNames = Object.keys(lp.annotationCounts || {});
|
|
const card = document.createElement('div');
|
|
card.className = 'card';
|
|
card.style.margin = '18px 0';
|
|
card.style.padding = '18px';
|
|
card.style.borderRadius = '12px';
|
|
card.style.background = '#f5f5f5';
|
|
card.style.boxShadow = '0 2px 8px rgba(0,0,0,0.04)';
|
|
// Extra div for project name
|
|
const nameDiv = document.createElement('div');
|
|
nameDiv.textContent = lp.title || lp.project_id;
|
|
nameDiv.style.fontSize = '1.2em';
|
|
nameDiv.style.fontWeight = 'bold';
|
|
nameDiv.style.marginBottom = '12px';
|
|
nameDiv.style.background = '#eaf7fa';
|
|
nameDiv.style.padding = '8px 16px';
|
|
nameDiv.style.borderRadius = '8px';
|
|
nameDiv.style.width = projectNameWidth;
|
|
nameDiv.style.minWidth = projectNameWidth;
|
|
nameDiv.style.maxWidth = projectNameWidth;
|
|
nameDiv.style.display = 'inline-block';
|
|
card.appendChild(nameDiv);
|
|
|
|
// Container for class rows
|
|
const classRowsDiv = document.createElement('div');
|
|
classRowsDiv.style.display = 'inline-block';
|
|
classRowsDiv.style.verticalAlign = 'top';
|
|
classRowsDiv.style.width = classRowsWidth;
|
|
|
|
classNames.forEach(className => {
|
|
// Row for class name and dropdown
|
|
const row = document.createElement('div');
|
|
row.className = 'class-row'; // Mark as class row
|
|
row.style.display = 'flex';
|
|
row.style.alignItems = 'center';
|
|
row.style.marginBottom = '10px';
|
|
// Original class name
|
|
const labelSpan = document.createElement('span');
|
|
labelSpan.textContent = className;
|
|
labelSpan.style.fontWeight = 'bold';
|
|
labelSpan.style.marginRight = '16px';
|
|
labelSpan.style.width = labelWidth;
|
|
labelSpan.style.minWidth = labelWidth;
|
|
labelSpan.style.maxWidth = labelWidth;
|
|
labelSpan.style.display = 'inline-block';
|
|
// Dropdown for reassigning
|
|
const select = document.createElement('select');
|
|
select.style.marginLeft = '8px';
|
|
select.style.padding = '4px 8px';
|
|
select.style.borderRadius = '6px';
|
|
select.style.border = '1px solid #009eac';
|
|
// Add blank item
|
|
const blankOption = document.createElement('option');
|
|
blankOption.value = '';
|
|
blankOption.textContent = '';
|
|
select.appendChild(blankOption);
|
|
storedClasses.forEach(cls => {
|
|
const option = document.createElement('option');
|
|
option.value = cls;
|
|
option.textContent = cls;
|
|
select.appendChild(option);
|
|
});
|
|
row.appendChild(labelSpan);
|
|
row.appendChild(select);
|
|
classRowsDiv.appendChild(row);
|
|
});
|
|
card.appendChild(classRowsDiv);
|
|
// Description field (right side, last element)
|
|
const descDiv = document.createElement('div');
|
|
descDiv.className = 'card-description';
|
|
descDiv.style.flex = '1';
|
|
descDiv.style.marginLeft = '32px';
|
|
descDiv.style.display = 'flex';
|
|
descDiv.style.flexDirection = 'column';
|
|
descDiv.style.justifyContent = 'flex-start';
|
|
descDiv.style.alignItems = 'flex-start';
|
|
descDiv.style.width = '220px';
|
|
// Add a label and textarea for description
|
|
const descLabel = document.createElement('label');
|
|
descLabel.textContent = 'Description:';
|
|
descLabel.style.fontWeight = 'bold';
|
|
descLabel.style.marginBottom = '4px';
|
|
const descTextarea = document.createElement('textarea');
|
|
descTextarea.style.width = '220px';
|
|
descTextarea.style.height = '48px';
|
|
descTextarea.style.borderRadius = '6px';
|
|
descTextarea.style.border = '1px solid #009eac';
|
|
descTextarea.style.padding = '6px';
|
|
descTextarea.style.resize = 'none';
|
|
descTextarea.value = lp.description || '';
|
|
descDiv.appendChild(descLabel);
|
|
descDiv.appendChild(descTextarea);
|
|
card.appendChild(descDiv);
|
|
detailsDiv.appendChild(card);
|
|
});
|
|
|
|
// Add Next button at the bottom right of the page
|
|
const nextBtn = document.createElement('button');
|
|
nextBtn.id = 'next-btn';
|
|
nextBtn.className = 'button';
|
|
nextBtn.textContent = 'Next';
|
|
nextBtn.style.position = 'fixed';
|
|
nextBtn.style.right = '32px';
|
|
nextBtn.style.bottom = '32px';
|
|
nextBtn.style.zIndex = '1000';
|
|
document.body.appendChild(nextBtn);
|
|
|
|
// Next button click handler: collect class mappings and update TrainingProjectDetails
|
|
nextBtn.addEventListener('click', () => {
|
|
// Array of arrays: [[labelStudioProjectId, [[originalClass, mappedClass], ...]], ...]
|
|
const mappings = [];
|
|
const descriptions = [];
|
|
detailsDiv.querySelectorAll('.card').forEach((card, idx) => {
|
|
const projectId = relatedProjects[idx].project_id;
|
|
const classMap = [];
|
|
// Only iterate over actual class rows
|
|
card.querySelectorAll('.class-row').forEach(row => {
|
|
const labelSpan = row.querySelector('span');
|
|
const select = row.querySelector('select');
|
|
if (labelSpan && select) {
|
|
const className = labelSpan.textContent.trim();
|
|
const mappedValue = select.value.trim();
|
|
if (className !== '' && mappedValue !== '') {
|
|
classMap.push([className, mappedValue]);
|
|
}
|
|
}
|
|
});
|
|
mappings.push([projectId, classMap]);
|
|
// Get description from textarea
|
|
const descTextarea = card.querySelector('textarea');
|
|
descriptions.push([projectId, descTextarea ? descTextarea.value : '']);
|
|
});
|
|
// Update TrainingProjectDetails in DB
|
|
fetch('/api/training-project-details', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
project_id: Number(trainingProjectId),
|
|
class_map: mappings,
|
|
description: descriptions // array of [projectId, description]
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
alert('Class assignments and descriptions updated!');
|
|
console.log(data);
|
|
// Redirect to start-training.html with id
|
|
window.location.href = `/edit-training.html?id=${trainingProjectId}`;
|
|
})
|
|
.catch(err => {
|
|
alert('Error updating class assignments or descriptions');
|
|
console.error(err);
|
|
});
|
|
});
|
|
});
|
|
}); |