v2.0.8: конвертация .doc → .docx через convert-service перед upload
Deploy contracts-flask / validate (push) Successful in 0s

This commit is contained in:
2026-07-16 10:42:21 +04:00
parent c677206d03
commit de05d746cc
3 changed files with 65 additions and 10 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
"""Конфигурация приложения — все настройки в одном месте."""
import os
VERSION = "2.0.7"
VERSION = "2.0.8"
LLM_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions")
LLM_KEY = os.getenv("LLM_API_KEY", "")
+57 -2
View File
@@ -12,6 +12,8 @@
* - statusToHTML() — рендер статуса (↑ N%, ✓)
* - renderFiles() — рендер всей таблицы
var CONVERT_URL = '/convert-doc';
/**
* statusToHTML(status) — Чистая функция: структура → HTML (Фаза 1).
*
@@ -26,6 +28,7 @@ function statusToHTML(st) {
if (!st || !st.kind) return '';
switch (st.kind) {
case 'connecting': return '⏳ соединение... ' + (st.elapsed || 0) + 'с';
case 'converting': return '⏳ конвертация... ' + (st.elapsed || 0) + 'с';
case 'uploading': return '⏳ отправка... ' + (st.elapsed || 0) + 'с';
case 'uploaded': return '<span class="status-ok">✓</span>';
case 'unzipping': return '⏳ распаковка...';
@@ -203,6 +206,40 @@ window.toggleClassifyDetail = async function(i) {
}
};
/**
* convertDoc(file, onProgress) — .doc → .docx через внешний libreoffice-сервис.
* С честным счётчиком времени.
*/
function convertDoc(file, onProgress) {
var startTime = Date.now();
if (onProgress) onProgress({ kind: 'converting', elapsed: 0 });
var fd = new FormData();
fd.append('files', file, file.name);
var timer = setInterval(function() {
var elapsed = Math.floor((Date.now() - startTime) / 1000);
if (onProgress) onProgress({ kind: 'converting', elapsed: elapsed });
}, 1000);
return fetch(CONVERT_URL + '?_=' + Date.now(), { method: 'POST', body: fd })
.then(function(r) {
if (!r.ok) throw new Error('Конвертация: HTTP ' + r.status);
return r.blob();
})
.then(function(blob) {
clearInterval(timer);
if (blob.size === 0) throw new Error('Конвертация: пустой результат');
return new File([blob], file.name.replace(/\.doc$/i, '.docx'), {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
lastModified: Date.now()
});
})
.catch(function(e) {
clearInterval(timer);
throw e;
});
}
/**
* ⛔ НЕ МЕНЯТЬ ⛔ uploadFile — fetch-загрузка с честным счётчиком времени.
*
@@ -430,8 +467,17 @@ async function addRegularFile(file, zipSource) {
render(state);
try {
// .doc → конвертация через libreoffice-сервис
var uploadTarget = file;
if (file.name.toLowerCase().endsWith('.doc')) {
uploadTarget = await convertDoc(file, function(st) {
state.files[rowIdx].status = st;
render(state);
});
state.files[rowIdx].name = uploadTarget.name;
}
// fetch-загрузка с честным счётчиком времени
var resp = await uploadFile(file, function(st) {
var resp = await uploadFile(uploadTarget, function(st) {
state.files[rowIdx].status = st;
render(state);
}, zipSource);
@@ -523,7 +569,16 @@ async function onFilesSelected(newFiles) {
delete entry._pendingFile;
var f = pendingFile;
try {
var resp = await uploadFile(f, function(st) {
// .doc → конвертация через libreoffice-сервис
var uploadTarget = f;
if (f.name.toLowerCase().endsWith('.doc')) {
uploadTarget = await convertDoc(f, function(st) {
entry.status = st;
render(state);
});
entry.name = uploadTarget.name;
}
var resp = await uploadFile(uploadTarget, function(st) {
entry.status = st;
render(state);
});
+7 -7
View File
@@ -73,7 +73,7 @@
<body>
<div class="topbar">
<img src="/static/logo.svg" alt="Nubes">
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v2.0.7</span></span>
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v2.0.8</span></span>
<div id="pipelineStepper" style="display:flex;gap:8px;font-size:11px;align-items:center;color:var(--muted);">
<span id="stepUpload">○ Загрузка</span><span></span>
<span id="stepClassify">○ Классификация</span><span></span>
@@ -216,11 +216,11 @@
</div>
</div>
<script src="/static/state.js?v=2.0.7"></script>
<script src="/static/app_utils.js?v=2.0.7"></script>
<script src="/static/files.js?v=2.0.7"></script>
<script src="/static/groups.js?v=2.0.7"></script>
<script src="/static/compare.js?v=2.0.7"></script>
<script src="/static/app.js?v=2.0.7"></script>
<script src="/static/state.js?v=2.0.8"></script>
<script src="/static/app_utils.js?v=2.0.8"></script>
<script src="/static/files.js?v=2.0.8"></script>
<script src="/static/groups.js?v=2.0.8"></script>
<script src="/static/compare.js?v=2.0.8"></script>
<script src="/static/app.js?v=2.0.8"></script>
</body>
</html>