Files
LORKT_AI_Service/client/templates/upload.html
T
2025-12-03 16:50:06 +05:00

615 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<link rel="icon" type="image/x-icon" href="static/favicon.ico">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Анализ DICOM файлов</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 800px;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: white;
color: #333;
padding: 25px;
text-align: center;
border-bottom: 2px solid #eee;
}
.logo-container {
background: white;
border-radius: 12px;
padding: 20px;
margin: 0 auto 20px;
display: inline-block;
}
.logo {
max-width: 200px;
height: auto;
display: block;
}
.header h1 {
font-size: 24px;
margin-bottom: 10px;
color: #2c3e50;
}
.header p {
color: #7f8c8d;
}
.content {
padding: 25px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2c3e50;
}
select {
width: 100%;
padding: 12px 15px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
select:focus {
border-color: #5ac6c8;
outline: none;
}
.demo-files {
background-color: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
.demo-files h3 {
margin-bottom: 12px;
color: #2c3e50;
font-size: 16px;
}
.file-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 10px;
}
.demo-file {
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 12px;
cursor: pointer;
transition: all 0.3s;
text-align: center;
}
.demo-file:hover {
border-color: #5ac6c8;
box-shadow: 0 5px 15px rgba(90, 198, 200, 0.2);
transform: translateY(-2px);
}
.demo-file.selected {
border-color: #5ac6c8;
background-color: #e8fcfb;
}
.drop-zone {
border: 2px dashed #5ac6c8;
border-radius: 8px;
padding: 40px 20px;
text-align: center;
margin-bottom: 20px;
cursor: pointer;
transition: all 0.3s;
background-color: #f8f9fa;
}
.drop-zone.highlight {
background-color: #e8fcfb;
border-color: #c182b9;
}
.drop-zone p {
margin-bottom: 15px;
color: #7f8c8d;
}
.drop-zone .icon {
font-size: 48px;
color: #5ac6c8;
margin-bottom: 15px;
}
.btn {
display: block;
width: 100%;
padding: 15px;
background: linear-gradient(135deg, #c182b9 0%, #5ac6c8 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.3s;
}
.btn:hover {
opacity: 0.9;
}
.btn:disabled {
background: #bdc3c7;
cursor: not-allowed;
}
.file-info {
margin-top: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #5ac6c8;
}
.file-info h3 {
margin-bottom: 10px;
color: #2c3e50;
}
.file-details {
display: flex;
justify-content: space-between;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #e0e0e0;
}
.notification {
padding: 15px;
margin: 20px 0;
border-radius: 8px;
text-align: center;
display: none;
}
.notification.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
display: block !important;
}
.notification.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
display: block !important;
}
.upload-section {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.section-title {
font-size: 18px;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.section-title::before {
content: "•";
margin-right: 10px;
color: #5ac6c8;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 600px) {
.file-list {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 20px;
}
.logo {
max-width: 150px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo-container">
<img src="static/logo.png" alt="Логотип" class="logo">
</div>
<h1>Выявление патологий в лучевых исследованиях</h1>
<p>Загрузите DICOM файл или выберите демонстрационный пример</p>
</div>
<div class="content">
{% if error %}
<div class="notification error">
{{ error }}
</div>
{% endif %}
<form id="upload-form" action="/upload-study" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="pathology">Выберите искомую патологию:</label>
<select id="pathology" name="pathology">
<option value="sinus">Синусит</option>
<option value="wrist">Перелом костей лучезапястного сустава</option>
<option value="shoulder">Перелом костей плечевого сустава</option>
</select>
<div class="demo-files">
<h3>Демонстрационные файлы:</h3>
<div class="file-list" id="demo-files-list">
</div>
</div>
</div>
<div class="upload-section">
<div class="section-title">Или загрузите свой файл</div>
<div class="drop-zone" id="drop-zone">
<div class="icon">📁</div>
<p>Перетащите DICOM файл сюда или нажмите для выбора</p>
<small>Поддерживаются только файлы в формате .dcm</small>
</div>
<input type="file" id="file-input" name="file" accept=".dcm" style="display: none;">
<input type="hidden" id="demo-filename" name="demo_filename" value="">
<div id="notification" class="notification"></div>
<button type="submit" id="analyze-btn" class="btn" disabled>
<span id="btn-text">Запустить анализ</span>
<span id="btn-loading" class="loading" style="display: none;"></span>
</button>
<div id="file-info" class="file-info" style="display: none;">
<h3>Информация о файле</h3>
<div id="file-details" class="file-details">
</div>
</div>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('upload-form');
const pathologySelect = document.getElementById('pathology');
const demoFilesList = document.getElementById('demo-files-list');
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const demoFilenameInput = document.getElementById('demo-filename');
const analyzeBtn = document.getElementById('analyze-btn');
const btnText = document.getElementById('btn-text');
const btnLoading = document.getElementById('btn-loading');
const fileInfo = document.getElementById('file-info');
const fileDetails = document.getElementById('file-details');
const notification = document.getElementById('notification');
let selectedFile = null;
let selectedDemoFile = null;
const demoFiles = {
sinus: [
{ name: "Рентген пазух носа (норма)", filename: "sinus_normal.dcm" },
{ name: "Рентген пазух носа (синусит)", filename: "sinusitis.dcm" }
],
wrist: [
{ name: "Рентген запястья (норма)", filename: "wrist_normal.dcm" },
{ name: "Рентген запястья (перелом)", filename: "wrist_fracture.dcm" }
],
shoulder: [
{ name: "Рентген плеча (норма)", filename: "shoulder_normal.dcm" },
{ name: "Рентген плеча (перелом)", filename: "shoulder_fracture.dcm" }
]
};
function showDemoFiles() {
const pathology = pathologySelect.value;
const files = demoFiles[pathology];
demoFilesList.innerHTML = '';
files.forEach(file => {
const fileElement = document.createElement('div');
fileElement.className = 'demo-file';
fileElement.textContent = file.name;
fileElement.dataset.filename = file.filename;
fileElement.addEventListener('click', function() {
document.querySelectorAll('.demo-file').forEach(f => {
f.classList.remove('selected');
});
this.classList.add('selected');
fileInput.value = '';
selectedFile = null;
selectedDemoFile = {
name: file.name,
filename: file.filename,
pathology: pathology
};
demoFilenameInput.value = file.filename;
showFileInfo(file.name, 'Демонстрационный файл');
analyzeBtn.disabled = false;
showNotification('Демонстрационный файл выбран. Нажмите "Запустить анализ" для обработки.', 'success');
});
demoFilesList.appendChild(fileElement);
});
}
function showFileInfo(name, info) {
fileInfo.style.display = 'block';
fileDetails.innerHTML = `
<div>
<strong>Имя файла:</strong> ${name}
</div>
<div>
<strong>Тип:</strong> ${info}
</div>
`;
}
function showNotification(message, type) {
notification.textContent = message;
notification.className = `notification ${type}`;
notification.style.display = 'block';
}
function hideNotification() {
notification.style.display = 'none';
notification.className = 'notification';
notification.textContent = '';
}
function restoreDemoFileState() {
const demoFilename = demoFilenameInput.value;
if (demoFilename) {
const pathology = pathologySelect.value;
const files = demoFiles[pathology];
const file = files.find(f => f.filename === demoFilename);
if (file) {
selectedDemoFile = {
name: file.name,
filename: file.filename,
pathology: pathology
};
document.querySelectorAll('.demo-file').forEach(f => {
f.classList.remove('selected');
if (f.dataset.filename === demoFilename) {
f.classList.add('selected');
}
});
showFileInfo(file.name, 'Демонстрационный файл');
analyzeBtn.disabled = false;
showNotification('Демонстрационный файл выбран. Нажмите "Запустить анализ" для обработки.', 'success');
}
}
}
function formatFileSize(bytes) {
if (!bytes || bytes === 0) return 'Неизвестно';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
pathologySelect.addEventListener('change', function() {
showDemoFiles();
selectedFile = null;
selectedDemoFile = null;
demoFilenameInput.value = '';
fileInfo.style.display = 'none';
analyzeBtn.disabled = true;
fileInput.value = '';
document.querySelectorAll('.demo-file').forEach(f => {
f.classList.remove('selected');
});
hideNotification();
});
dropZone.addEventListener('click', () => {
fileInput.click();
});
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('highlight');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('highlight');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('highlight');
if (e.dataTransfer.files.length) {
handleFileSelect(e.dataTransfer.files[0]);
document.querySelectorAll('.demo-file').forEach(f => {
f.classList.remove('selected');
});
selectedDemoFile = null;
demoFilenameInput.value = '';
}
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length) {
handleFileSelect(fileInput.files[0]);
document.querySelectorAll('.demo-file').forEach(f => {
f.classList.remove('selected');
});
selectedDemoFile = null;
demoFilenameInput.value = '';
}
});
function handleFileSelect(file) {
if (file.name.endsWith('.dcm')) {
selectedFile = file;
selectedDemoFile = null;
demoFilenameInput.value = '';
showFileInfo(file.name, formatFileSize(file.size));
analyzeBtn.disabled = false;
showNotification('Файл успешно загружен. Нажмите "Запустить анализ" для обработки.', 'success');
} else {
showNotification('Пожалуйста, выберите файл в формате DICOM (.dcm).', 'error');
}
}
form.addEventListener('submit', function(e) {
e.preventDefault();
btnText.style.display = 'none';
btnLoading.style.display = 'inline-block';
analyzeBtn.disabled = true;
hideNotification();
const formData = new FormData();
formData.append('pathology', pathologySelect.value);
if (selectedDemoFile) {
formData.append('demo_filename', selectedDemoFile.filename);
} else if (selectedFile) {
formData.append('file', selectedFile);
} else {
showNotification('Пожалуйста, выберите файл или демонстрационный пример.', 'error');
btnText.style.display = 'inline';
btnLoading.style.display = 'none';
analyzeBtn.disabled = false;
return;
}
fetch('/upload-study', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => {
if (response.ok) {
return response.json();
} else {
return response.json().then(err => { throw err; });
}
})
.then(data => {
if (data.redirect) {
window.location.href = data.redirect;
}
})
.catch(error => {
console.error('Ошибка:', error);
const message = error.error || 'Произошла неизвестная ошибка.';
showNotification(message, 'error');
btnText.style.display = 'inline';
btnLoading.style.display = 'none';
analyzeBtn.disabled = false;
});
});
window.addEventListener('pageshow', function(event) {
btnText.style.display = 'inline';
btnLoading.style.display = 'none';
analyzeBtn.disabled = false;
if (notification.textContent === 'Файл отправляется на анализ...') {
hideNotification();
}
restoreDemoFileState();
});
showDemoFiles();
});
</script>
</body>
</html>