diff --git a/site/config.py b/site/config.py index 12634ed..c54ef97 100644 --- a/site/config.py +++ b/site/config.py @@ -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", "") diff --git a/site/static/files.js b/site/static/files.js index ef7736f..90cf7d9 100644 --- a/site/static/files.js +++ b/site/static/files.js @@ -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 ''; 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); }); diff --git a/site/templates/index.html b/site/templates/index.html index 4592b20..287b6b7 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -73,7 +73,7 @@
Nubes - Сверка договоров — LLM AI-driven Event Sourcing v2.0.7 + Сверка договоров — LLM AI-driven Event Sourcing v2.0.8
○ Загрузка ○ Классификация @@ -216,11 +216,11 @@
- - - - - - + + + + + +