diff --git a/requirements.txt b/requirements.txt index 574b557..8ca3c32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ flask -gunicorn -gevent python-docx pdfplumber httpx diff --git a/site/app.py b/site/app.py index 415dce4..7bad6e2 100644 --- a/site/app.py +++ b/site/app.py @@ -52,17 +52,8 @@ def create_app(): app = create_app() # Штурвал запускает: cd site && python app.py -# Внутри — gunicorn с gevent для долгих LLM-запросов if __name__ == "__main__": - import subprocess, sys - subprocess.run([ - sys.executable, "-m", "gunicorn", "app:app", - "--bind", "0.0.0.0:5000", - "--worker-class", "gevent", - "--workers", "1", - "--worker-connections", "1000", - "--timeout", "300", - ]) + app.run(host="0.0.0.0", port=5000) diff --git a/site/templates/index.html b/site/templates/index.html index b1a67d9..eb3a06f 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -170,39 +170,74 @@ fileInput.addEventListener('change', () => { renderFiles(); }); -function uploadFiles() { +async function uploadFiles() { if (selectedFiles.length === 0) return; - status.className = 'status info'; - status.textContent = 'Обфускация...'; uploadBtn.disabled = true; - const formData = new FormData(); - selectedFiles.forEach(f => formData.append('files', f)); + const total = selectedFiles.length; + let ok = 0; + let fail = 0; + let results = []; - fetch('/api/drhider', { method: 'POST', body: formData }) - .then(res => { - if (!res.ok) return res.json().then(e => { throw new Error(e.error || 'Ошибка сервера'); }); - const disp = res.headers.get('Content-Disposition') || ''; - const nameMatch = disp.match(/filename="?(.+?)"?$/); - const filename = nameMatch ? nameMatch[1] : 'drhider_output.zip'; - return res.blob().then(blob => ({ blob, filename })); - }) - .then(({ blob, filename }) => { - status.className = 'status success'; - status.textContent = 'Готово! Скачивание началось.'; + for (let i = 0; i < total; i++) { + const f = selectedFiles[i]; + const n = i + 1; + + // Показываем прогресс + status.className = 'status info'; + status.textContent = 'Файл ' + n + '/' + total + ': ' + f.name + ' ...'; + + const t0 = performance.now(); + const formData = new FormData(); + formData.append('files', f); + + try { + const res = await fetch('/api/drhider', { method: 'POST', body: formData }); + + if (!res.ok) { + const errData = await res.json().catch(() => ({})); + throw new Error(errData.error || 'Ошибка сервера'); + } + + const elapsed = ((performance.now() - t0) / 1000).toFixed(1); + const blob = await res.blob(); + + // Скачиваем файл const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = filename; + a.download = f.name.replace(/\.[^.]+$/, '') + '_obfuscated.zip'; a.click(); URL.revokeObjectURL(url); - uploadBtn.disabled = false; - }) - .catch(err => { + + ok++; + status.className = 'status success'; + status.textContent = 'Файл ' + n + '/' + total + ': ' + f.name + ' — ' + elapsed + ' сек ✓'; + results.push({ name: f.name, ok: true, time: elapsed }); + + } catch (err) { + fail++; status.className = 'status error'; - status.textContent = 'Ошибка: ' + err.message; - uploadBtn.disabled = false; - }); + status.textContent = 'Файл ' + n + '/' + total + ': ' + f.name + ' — ошибка: ' + err.message; + results.push({ name: f.name, ok: false, error: err.message }); + } + + // Пауза между файлами чтобы не заддосить LLM + if (i < total - 1) { + await new Promise(r => setTimeout(r, 500)); + } + } + + // Итог + if (fail === 0) { + status.className = 'status success'; + status.textContent = 'Готово! ' + ok + '/' + total + ' файлов обработано.'; + } else { + status.className = 'status error'; + status.textContent = 'Готово: ' + ok + ' OK, ' + fail + ' ошибок из ' + total; + } + + uploadBtn.disabled = false; }