Files
Abschluss-Projekt/js/setup-training-project.js
2025-11-28 09:35:02 +01:00

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);
});
});
});
});