BC_praca/Frontend/JS/upload.js

272 lines
7.0 KiB
JavaScript

function show(el) { el.classList.remove("hidden"); }
function hide(el) { el.classList.add("hidden"); }
function escapeHtml(str) {
return String(str)
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function splitCsvLine(line) {
const out = [];
let cur = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
if (inQuotes && line[i + 1] === '"') {
cur += '"';
i++;
} else {
inQuotes = !inQuotes;
}
} else if (ch === "," && !inQuotes) {
out.push(cur);
cur = "";
} else {
cur += ch;
}
}
out.push(cur);
return out.map(v => v.trim());
}
const fileInput = document.getElementById("file_input");
const step2 = document.getElementById("step-2");
const step3 = document.getElementById("step-3");
const step4 = document.getElementById("step-4");
const step5 = document.getElementById("step-5");
const labelSelect = document.getElementById("label_select");
const labelError = document.getElementById("label_error");
const normalBox = document.getElementById("normal_values");
const normalError = document.getElementById("normal_error");
const featureList = document.getElementById("feature_list");
const featureCounter = document.getElementById("feature_counter");
const featureError = document.getElementById("feature_error");
const btnStep2Next = document.getElementById("btn_step2_next");
const btnStep3Next = document.getElementById("btn_step3_next");
const btnRun = document.getElementById("btn_run");
const MAX_FEATURES = 5;
let headers = [];
let rows = [];
let labelIndex = -1;
let numericColumnIdx = [];
fileInput.addEventListener("change", async () => {
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload-dataset", {
method: "POST",
body: formData
});
if (!fileInput.files.length) return;
const file = fileInput.files[0];
const text = await file.text();
const lines = text.split(/\r?\n/).filter(l => l.trim() !== "");
if (lines.length < 2) return;
headers = splitCsvLine(lines[0]);
rows = lines.slice(1).map(splitCsvLine);
labelSelect.innerHTML = `<option value="">-- select label column --</option>`;
headers.forEach((h, idx) => {
const opt = document.createElement("option");
opt.value = idx;
opt.textContent = h;
labelSelect.appendChild(opt);
});
numericColumnIdx = detectNumericColumns(headers, rows, 30);
show(step2);
hide(step3);
hide(step4);
hide(step5);
hide(labelError);
});
function detectNumericColumns(headers, rows, sampleCount) {
const result = [];
const samples = Math.min(sampleCount, rows.length);
for (let c = 0; c < headers.length; c++) {
let valid = 0;
let total = 0;
for (let r = 0; r < samples; r++) {
const v = rows[r][c];
if (v === undefined || v === "") continue;
total++;
if (!isNaN(Number(v))) valid++;
}
if (total > 0 && valid / total >= 0.9) {
result.push(c);
}
}
return result;
}
btnStep2Next.addEventListener("click", () => {
if (labelSelect.value === "") {
show(labelError);
return;
}
hide(labelError);
labelIndex = Number(labelSelect.value);
const uniques = new Set();
rows.forEach(r => {
const v = r[labelIndex];
if (v !== undefined && v !== "") uniques.add(v.trim());
});
normalBox.innerHTML = "";
uniques.forEach(val => {
const label = document.createElement("label");
label.className =
"flex items-center gap-3 bg-gray-900/40 border border-gray-700 rounded-lg px-4 py-2";
label.innerHTML = `
<input type="checkbox" value="${escapeHtml(val)}" class="accent-orange-500">
<span>${escapeHtml(val)}</span>
`;
normalBox.appendChild(label);
});
normalBox.querySelectorAll("input").forEach(cb => {
cb.addEventListener("change", () => {
if (cb.checked) {
normalBox.querySelectorAll("input").forEach(o => {
if (o !== cb) o.checked = false;
});
}
});
});
show(step3);
hide(step4);
hide(step5);
hide(normalError);
});
btnStep3Next.addEventListener("click", () => {
const checked = normalBox.querySelectorAll("input:checked");
if (checked.length !== 1) {
normalError.textContent = "Select exactly one NORMAL value.";
show(normalError);
return;
}
hide(normalError);
featureList.innerHTML = "";
numericColumnIdx
.filter(idx => idx !== labelIndex)
.forEach(idx => {
const label = document.createElement("label");
label.className =
"flex items-center justify-between bg-gray-900/40 border border-gray-700 rounded-lg px-4 py-2";
label.innerHTML = `
<div class="flex items-center gap-3">
<input type="checkbox" data-col="${idx}" class="accent-orange-500">
<span>${escapeHtml(headers[idx])}</span>
</div>
<span class="text-xs text-gray-500">numeric</span>
`;
featureList.appendChild(label);
});
featureList.querySelectorAll("input").forEach(cb =>
cb.addEventListener("change", updateCounter)
);
updateCounter();
show(step4);
hide(featureError);
});
function updateCounter() {
const all = featureList.querySelectorAll("input");
const selected = featureList.querySelectorAll("input:checked").length;
featureCounter.textContent = `${selected} / ${MAX_FEATURES} selected`;
all.forEach(cb => {
cb.disabled = selected >= MAX_FEATURES && !cb.checked;
cb.classList.toggle("opacity-60", cb.disabled);
});
}
btnRun.addEventListener("click", async () => {
const labelColumnName = headers[labelIndex];
let normalValue = normalBox.querySelector("input:checked").value;
if (!isNaN(normalValue)) {
normalValue = Number(normalValue);
}
const selectedFeatures = Array.from(
featureList.querySelectorAll("input:checked")
).map(cb => headers[cb.dataset.col]);
const config = {
dataset: {
file_path: "temp/upload.csv",
dataset_name: "custom_user_dataset"
},
labeling: {
label_column: labelColumnName,
normal_value: normalValue,
normal_label: 0,
anomaly_label: 1
},
features: {
selected_columns: selectedFeatures,
expected_feature_count: selectedFeatures.length
},
algorithm: {
name: "isolation_forest"
}
};
await fetch("/save-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(config)
});
const selected = featureList.querySelectorAll("input:checked").length;
if (selected !== MAX_FEATURES) {
featureError.textContent = `You must select exactly ${MAX_FEATURES} numeric features.`;
show(featureError);
return;
}
hide(featureError);
show(step5);
});