training fix. add global settings
This commit is contained in:
342
js/dashboard.js
342
js/dashboard.js
@@ -1,171 +1,171 @@
|
||||
function renderProjects(projects) {
|
||||
const projectsList = document.getElementById('projects-list');
|
||||
projectsList.innerHTML = '';
|
||||
|
||||
if (projects.length === 0) {
|
||||
projectsList.innerHTML = '<div>No projects found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const project of projects) {
|
||||
const labelCounts = project.labelCounts || {};
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
card.style.background = '#f5f5f5';
|
||||
card.style.borderRadius = '12px';
|
||||
card.style.overflow = 'hidden';
|
||||
card.style.boxShadow = '0 2px 8px rgba(0,0,0,0)';
|
||||
card.style.display = 'flex';
|
||||
card.style.background = 'white';
|
||||
card.style.cursor = 'pointer';
|
||||
card.tabIndex = 0;
|
||||
card.setAttribute('role', 'button');
|
||||
card.setAttribute('aria-label', `Open project ${project.title || project.id}`);
|
||||
card.style.position = 'relative'; // For absolute positioning of delete button
|
||||
card.addEventListener('click', (e) => {
|
||||
// Prevent click if delete button is pressed
|
||||
if (e.target.classList.contains('delete-btn')) return;
|
||||
if (project.hasTraining) {
|
||||
window.location.href = `/overview-training.html?id=${project.id}`;
|
||||
} else if (project.hasDetails) {
|
||||
// Find details for this project
|
||||
const detailsEntry = window._trainingProjectDetails?.find(d => d.project_id == project.id);
|
||||
if (detailsEntry && Array.isArray(detailsEntry.class_map) && detailsEntry.class_map.length > 0) {
|
||||
// If classes are assigned, skip to start-training.html
|
||||
window.location.href = `/edit-training.html?id=${project.id}`;
|
||||
} else {
|
||||
window.location.href = `/setup-training-project.html?id=${project.id}`;
|
||||
}
|
||||
} else {
|
||||
window.location.href = `/project-details.html?id=${project.id}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Image
|
||||
let imageHTML = '';
|
||||
if (project.project_image) {
|
||||
imageHTML = `<img src="${project.project_image}" alt="img" style="width:120px;height:120px;object-fit:cover;display:block;" />`;
|
||||
}
|
||||
const imgContainer = document.createElement('div');
|
||||
imgContainer.className = 'img-container';
|
||||
imgContainer.style.background = '#009eac2d'
|
||||
imgContainer.style.flex = '0 0 120px';
|
||||
imgContainer.style.display = 'flex';
|
||||
imgContainer.style.alignItems = 'center';
|
||||
imgContainer.style.justifyContent = 'center';
|
||||
imgContainer.innerHTML = imageHTML;
|
||||
|
||||
// Info
|
||||
const infoDiv = document.createElement('div');
|
||||
infoDiv.className = 'info';
|
||||
infoDiv.style.background = '#009eac2d'
|
||||
infoDiv.style.flex = '1';
|
||||
infoDiv.style.padding = '16px';
|
||||
infoDiv.innerHTML = `
|
||||
<h3 style="margin:0 0 8px 0;font-size:1.5em;font-weight:bold;">${project.id ?? 'N/A'}     ${project.title || 'Untitled'}</h3>
|
||||
<div class="label-classes" style="font-size:1em;">
|
||||
${getClassesAsParagraphs(project, labelCounts)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Delete button
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.textContent = 'Delete';
|
||||
deleteBtn.style.width = '70px';
|
||||
deleteBtn.style.height = '28px';
|
||||
deleteBtn.className = 'button-red delete-btn';
|
||||
deleteBtn.style.position = 'absolute';
|
||||
deleteBtn.style.bottom = '0px';
|
||||
deleteBtn.style.right = '15px';
|
||||
deleteBtn.style.zIndex = '2';
|
||||
deleteBtn.style.fontSize = '14px';
|
||||
deleteBtn.style.padding = '0';
|
||||
deleteBtn.style.borderRadius = '6px';
|
||||
deleteBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)';
|
||||
deleteBtn.style.display = 'flex';
|
||||
deleteBtn.style.alignItems = 'center';
|
||||
deleteBtn.style.justifyContent = 'center';
|
||||
deleteBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (confirm('Are you sure you want to delete this training project?')) {
|
||||
fetch(`/api/training-projects/${project.id}`, { method: 'DELETE' })
|
||||
.then(res => {
|
||||
if (res.ok) {
|
||||
card.remove();
|
||||
} else {
|
||||
alert('Failed to delete project.');
|
||||
}
|
||||
})
|
||||
.catch(() => alert('Failed to delete project.'));
|
||||
}
|
||||
});
|
||||
card.appendChild(imgContainer);
|
||||
card.appendChild(infoDiv);
|
||||
card.appendChild(deleteBtn);
|
||||
projectsList.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to render classes as <p> elements
|
||||
function getClassesAsParagraphs(project, labelCounts) {
|
||||
let classes = [];
|
||||
let labelConfig = project.parsed_label_config;
|
||||
if (typeof labelConfig === 'string') {
|
||||
try { labelConfig = JSON.parse(labelConfig); } catch { labelConfig = null; }
|
||||
}
|
||||
if (labelConfig) {
|
||||
Object.values(labelConfig).forEach(cfg => {
|
||||
if (cfg.labels && Array.isArray(cfg.labels)) {
|
||||
cfg.labels.forEach(label => {
|
||||
classes.push(label);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (classes.length === 0 && project.prompts && project.prompts.length > 0) {
|
||||
const prompt = project.prompts[0];
|
||||
if (prompt.output_classes && prompt.output_classes.length > 0) {
|
||||
classes = prompt.output_classes;
|
||||
}
|
||||
}
|
||||
if (classes.length === 0 && Object.keys(labelCounts).length > 0) {
|
||||
classes = Object.keys(labelCounts);
|
||||
}
|
||||
return classes.map(cls => `<p>${cls}${labelCounts && labelCounts[cls] !== undefined ? ' ' : ''}</p>`).join('');
|
||||
}
|
||||
|
||||
// Fetch and render TrainingProjects from the backend
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
Promise.all([
|
||||
fetch('/api/training-projects').then(res => res.json()),
|
||||
fetch('/api/training-project-details').then(res => res.json()),
|
||||
fetch('/api/trainings').then(res => res.json())
|
||||
]).then(([projects, details, trainings]) => {
|
||||
window._trainingProjectDetails = details; // Store globally for click handler
|
||||
// Build a set of project IDs that have details
|
||||
const detailsProjectIds = new Set(details.map(d => d.project_id));
|
||||
// Build a set of project IDs that have trainings
|
||||
const detailsIdToProjectId = {};
|
||||
details.forEach(d => { detailsIdToProjectId[d.id] = d.project_id; });
|
||||
const trainingProjectIds = new Set(trainings.map(t => detailsIdToProjectId[t.project_details_id]));
|
||||
// Map project_id to id for frontend compatibility
|
||||
projects.forEach(project => {
|
||||
if (project.project_id !== undefined) project.id = project.project_id;
|
||||
if (Array.isArray(project.classes)) {
|
||||
project.labelCounts = {};
|
||||
project.classes.forEach(cls => project.labelCounts[cls] = 0);
|
||||
}
|
||||
// Attach a flag for details existence
|
||||
project.hasDetails = detailsProjectIds.has(project.id);
|
||||
// Attach a flag for training existence
|
||||
project.hasTraining = trainingProjectIds.has(project.id);
|
||||
});
|
||||
renderProjects(projects);
|
||||
}).catch(err => {
|
||||
document.getElementById('projects-list').innerHTML = '<div>Error loading projects</div>';
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function renderProjects(projects) {
|
||||
const projectsList = document.getElementById('projects-list');
|
||||
projectsList.innerHTML = '';
|
||||
|
||||
if (projects.length === 0) {
|
||||
projectsList.innerHTML = '<div>No projects found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const project of projects) {
|
||||
const labelCounts = project.labelCounts || {};
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
card.style.background = '#f5f5f5';
|
||||
card.style.borderRadius = '12px';
|
||||
card.style.overflow = 'hidden';
|
||||
card.style.boxShadow = '0 2px 8px rgba(0,0,0,0)';
|
||||
card.style.display = 'flex';
|
||||
card.style.background = 'white';
|
||||
card.style.cursor = 'pointer';
|
||||
card.tabIndex = 0;
|
||||
card.setAttribute('role', 'button');
|
||||
card.setAttribute('aria-label', `Open project ${project.title || project.id}`);
|
||||
card.style.position = 'relative'; // For absolute positioning of delete button
|
||||
card.addEventListener('click', (e) => {
|
||||
// Prevent click if delete button is pressed
|
||||
if (e.target.classList.contains('delete-btn')) return;
|
||||
if (project.hasTraining) {
|
||||
window.location.href = `/overview-training.html?id=${project.id}`;
|
||||
} else if (project.hasDetails) {
|
||||
// Find details for this project
|
||||
const detailsEntry = window._trainingProjectDetails?.find(d => d.project_id == project.id);
|
||||
if (detailsEntry && Array.isArray(detailsEntry.class_map) && detailsEntry.class_map.length > 0) {
|
||||
// If classes are assigned, skip to start-training.html
|
||||
window.location.href = `/edit-training.html?id=${project.id}`;
|
||||
} else {
|
||||
window.location.href = `/setup-training-project.html?id=${project.id}`;
|
||||
}
|
||||
} else {
|
||||
window.location.href = `/project-details.html?id=${project.id}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Image
|
||||
let imageHTML = '';
|
||||
if (project.project_image) {
|
||||
imageHTML = `<img src="${project.project_image}" alt="img" style="width:120px;height:120px;object-fit:cover;display:block;" />`;
|
||||
}
|
||||
const imgContainer = document.createElement('div');
|
||||
imgContainer.className = 'img-container';
|
||||
imgContainer.style.background = '#009eac2d'
|
||||
imgContainer.style.flex = '0 0 120px';
|
||||
imgContainer.style.display = 'flex';
|
||||
imgContainer.style.alignItems = 'center';
|
||||
imgContainer.style.justifyContent = 'center';
|
||||
imgContainer.innerHTML = imageHTML;
|
||||
|
||||
// Info
|
||||
const infoDiv = document.createElement('div');
|
||||
infoDiv.className = 'info';
|
||||
infoDiv.style.background = '#009eac2d'
|
||||
infoDiv.style.flex = '1';
|
||||
infoDiv.style.padding = '16px';
|
||||
infoDiv.innerHTML = `
|
||||
<h3 style="margin:0 0 8px 0;font-size:1.5em;font-weight:bold;">${project.id ?? 'N/A'}     ${project.title || 'Untitled'}</h3>
|
||||
<div class="label-classes" style="font-size:1em;">
|
||||
${getClassesAsParagraphs(project, labelCounts)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Delete button
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.textContent = 'Delete';
|
||||
deleteBtn.style.width = '70px';
|
||||
deleteBtn.style.height = '28px';
|
||||
deleteBtn.className = 'button-red delete-btn';
|
||||
deleteBtn.style.position = 'absolute';
|
||||
deleteBtn.style.bottom = '0px';
|
||||
deleteBtn.style.right = '15px';
|
||||
deleteBtn.style.zIndex = '2';
|
||||
deleteBtn.style.fontSize = '14px';
|
||||
deleteBtn.style.padding = '0';
|
||||
deleteBtn.style.borderRadius = '6px';
|
||||
deleteBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)';
|
||||
deleteBtn.style.display = 'flex';
|
||||
deleteBtn.style.alignItems = 'center';
|
||||
deleteBtn.style.justifyContent = 'center';
|
||||
deleteBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (confirm('Are you sure you want to delete this training project?')) {
|
||||
fetch(`/api/training-projects/${project.id}`, { method: 'DELETE' })
|
||||
.then(res => {
|
||||
if (res.ok) {
|
||||
card.remove();
|
||||
} else {
|
||||
alert('Failed to delete project.');
|
||||
}
|
||||
})
|
||||
.catch(() => alert('Failed to delete project.'));
|
||||
}
|
||||
});
|
||||
card.appendChild(imgContainer);
|
||||
card.appendChild(infoDiv);
|
||||
card.appendChild(deleteBtn);
|
||||
projectsList.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to render classes as <p> elements
|
||||
function getClassesAsParagraphs(project, labelCounts) {
|
||||
let classes = [];
|
||||
let labelConfig = project.parsed_label_config;
|
||||
if (typeof labelConfig === 'string') {
|
||||
try { labelConfig = JSON.parse(labelConfig); } catch { labelConfig = null; }
|
||||
}
|
||||
if (labelConfig) {
|
||||
Object.values(labelConfig).forEach(cfg => {
|
||||
if (cfg.labels && Array.isArray(cfg.labels)) {
|
||||
cfg.labels.forEach(label => {
|
||||
classes.push(label);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (classes.length === 0 && project.prompts && project.prompts.length > 0) {
|
||||
const prompt = project.prompts[0];
|
||||
if (prompt.output_classes && prompt.output_classes.length > 0) {
|
||||
classes = prompt.output_classes;
|
||||
}
|
||||
}
|
||||
if (classes.length === 0 && Object.keys(labelCounts).length > 0) {
|
||||
classes = Object.keys(labelCounts);
|
||||
}
|
||||
return classes.map(cls => `<p>${cls}${labelCounts && labelCounts[cls] !== undefined ? ' ' : ''}</p>`).join('');
|
||||
}
|
||||
|
||||
// Fetch and render TrainingProjects from the backend
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
Promise.all([
|
||||
fetch('/api/training-projects').then(res => res.json()),
|
||||
fetch('/api/training-project-details').then(res => res.json()),
|
||||
fetch('/api/trainings').then(res => res.json())
|
||||
]).then(([projects, details, trainings]) => {
|
||||
window._trainingProjectDetails = details; // Store globally for click handler
|
||||
// Build a set of project IDs that have details
|
||||
const detailsProjectIds = new Set(details.map(d => d.project_id));
|
||||
// Build a set of project IDs that have trainings
|
||||
const detailsIdToProjectId = {};
|
||||
details.forEach(d => { detailsIdToProjectId[d.id] = d.project_id; });
|
||||
const trainingProjectIds = new Set(trainings.map(t => detailsIdToProjectId[t.project_details_id]));
|
||||
// Map project_id to id for frontend compatibility
|
||||
projects.forEach(project => {
|
||||
if (project.project_id !== undefined) project.id = project.project_id;
|
||||
if (Array.isArray(project.classes)) {
|
||||
project.labelCounts = {};
|
||||
project.classes.forEach(cls => project.labelCounts[cls] = 0);
|
||||
}
|
||||
// Attach a flag for details existence
|
||||
project.hasDetails = detailsProjectIds.has(project.id);
|
||||
// Attach a flag for training existence
|
||||
project.hasTraining = trainingProjectIds.has(project.id);
|
||||
});
|
||||
renderProjects(projects);
|
||||
}).catch(err => {
|
||||
document.getElementById('projects-list').innerHTML = '<div>Error loading projects</div>';
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user