v1.0.42: конвертация .doc через ВМ + парсер .doc

This commit is contained in:
2026-06-19 07:26:36 +04:00
parent 70e1ce8b98
commit 836b0eca6b
2 changed files with 73 additions and 22 deletions
+46 -20
View File
@@ -61,7 +61,7 @@
<body> <body>
<div class="topbar"> <div class="topbar">
<img src="/nubes-logo.svg" alt="Nubes"> <img src="/nubes-logo.svg" alt="Nubes">
<span class="title">Сверка договоров — LLM <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.41 — Lucee</span></span> <span class="title">Сверка договоров — LLM <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.42 — Lucee</span></span>
</div> </div>
<div class="content"> <div class="content">
@@ -140,6 +140,7 @@
lucide.createIcons(); lucide.createIcons();
var UPLOAD_URL = 'https://contracts.kube5s.ru/lucee/upload.cfm'; var UPLOAD_URL = 'https://contracts.kube5s.ru/lucee/upload.cfm';
var CONVERT_URL = 'https://contracts.kube5s.ru/convert-doc';
var SITE_URL = ''; // same origin for api calls var SITE_URL = ''; // same origin for api calls
var fileInput = document.getElementById('fileInput'); var fileInput = document.getElementById('fileInput');
@@ -227,26 +228,51 @@ fileInput.addEventListener('change', async function() {
function uploadFile(file, onProgress) { function uploadFile(file, onProgress) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest(); // .doc → конвертация в docx
var fd = new FormData(); var isDoc = file.name.toLowerCase().endsWith('.doc') && !file.name.toLowerCase().endsWith('.docx');
fd.append('files', file); var uploadFile = file;
if (contractId) fd.append('contract_id', contractId); var uploadName = file.name;
xhr.open('POST', UPLOAD_URL); function doUpload() {
xhr.upload.onprogress = function(e) { var xhr = new XMLHttpRequest();
if (e.lengthComputable && onProgress) onProgress(Math.round(e.loaded / e.total * 100)); var fd = new FormData();
}; fd.append('files', uploadFile, uploadName);
xhr.onload = function() { if (contractId) fd.append('contract_id', contractId);
try { xhr.open('POST', UPLOAD_URL);
var r = JSON.parse(xhr.responseText); xhr.upload.onprogress = function(e) {
if (r.OK) resolve(r); if (e.lengthComputable && onProgress) onProgress(Math.round(e.loaded / e.total * 100));
else reject(new Error(r.ERROR || 'Неизвестная ошибка')); };
} catch(e) { reject(new Error('Некорректный ответ')); } xhr.onload = function() {
}; try {
xhr.onerror = function() { reject(new Error('Сеть')); }; var r = JSON.parse(xhr.responseText);
xhr.ontimeout = function() { reject(new Error('Таймаут')); }; if (r.OK) resolve(r);
xhr.timeout = 180000; else reject(new Error(r.ERROR || 'Неизвестная ошибка'));
xhr.send(fd); } catch(e) { reject(new Error('Некорректный ответ')); }
};
xhr.onerror = function() { reject(new Error('Сеть')); };
xhr.ontimeout = function() { reject(new Error('Таймаут')); };
xhr.timeout = 180000;
xhr.send(fd);
}
if (isDoc) {
var xhr = new XMLHttpRequest();
xhr.open('POST', CONVERT_URL);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200 && xhr.response.size > 100) {
uploadFile = xhr.response;
uploadName = file.name.replace(/\.doc$/i, '.docx');
doUpload();
} else {
reject(new Error('Конвертация .doc'));
}
};
xhr.onerror = function() { reject(new Error('Конвертер')); };
xhr.send(file);
} else {
doUpload();
}
}); });
} }
+27 -2
View File
@@ -163,6 +163,28 @@
<cfreturn out> <cfreturn out>
</cffunction> </cffunction>
<!--- ═══════════════════════════════════════════════════════════
Helper: parseDOC — грубый экстрактор текста из .doc
═══════════════════════════════════════════════════════════ --->
<cffunction name="parseDOC" access="private" returntype="struct">
<cfargument name="fileBytes" type="binary">
<cfset var out = {elements: [], errors: []}>
<cftry>
<cfset var raw = createObject("java", "java.lang.String").init(fileBytes, "windows-1251")>
<cfset var lines = raw.split("\n")>
<cfloop array="#lines#" index="var line">
<cfset line = trim(line)>
<cfif len(line) GT 3 AND (reFind("[а-яА-ЯёЁ]", line) OR reFind("\d{2,}", line))>
<cfset arrayAppend(out.elements, {type: "paragraph", style: "", text: line, page: 0})>
</cfif>
</cfloop>
<cfcatch>
<cfset arrayAppend(out.errors, ".doc: " & cfcatch.message)>
</cfcatch>
</cftry>
<cfreturn out>
</cffunction>
<!--- ═══════════════════════════════════════════════ MAIN ---> <!--- ═══════════════════════════════════════════════ MAIN --->
<cfset result = {ok: false, error: ""}> <cfset result = {ok: false, error: ""}>
<cftry> <cftry>
@@ -210,7 +232,7 @@
<cfset parsed = {elements: [], errors: []}> <cfset parsed = {elements: [], errors: []}>
<cfif zext EQ "pdf"> <cfif zext EQ "pdf">
<cfset parsed = parsePDF(entryBytes)> <cfset parsed = parsePDF(entryBytes)>
<cfelseif zext EQ "docx"> <cfelseif zext EQ "docx" OR zext EQ "doc">
<cfset parsed = parseDOCX(entryBytes)> <cfset parsed = parseDOCX(entryBytes)>
<cfelse> <cfelse>
<cfset arrayAppend(parsed.errors, "unsupported: .#zext#")> <cfset arrayAppend(parsed.errors, "unsupported: .#zext#")>
@@ -264,7 +286,10 @@
</cftry> </cftry>
<cfelseif doc.mime_type EQ "application/msword"> <cfelseif doc.mime_type EQ "application/msword">
<cfset arrayAppend(errors, ".doc not supported (use libreoffice)")> <cfset fileBytes = binaryDecode(doc.original_bytes, "base64")>
<cfset parsed = parseDOC(fileBytes)>
<cfset elements = parsed.elements>
<cfset errors = parsed.errors>
<cfelse> <cfelse>
<cfset arrayAppend(errors, "unsupported: " & doc.mime_type)> <cfset arrayAppend(errors, "unsupported: " & doc.mime_type)>
</cfif> </cfif>