diff --git a/deploy/convert_server.py b/deploy/convert_server.py
index 7d20404..0c43ea5 100755
--- a/deploy/convert_server.py
+++ b/deploy/convert_server.py
@@ -346,14 +346,64 @@ class Handler(BaseHTTPRequestHandler):
json={"upload_id": upload_id, "contract_id": contract_id},
)
if resp.status_code == 200:
+ result = resp.json()
+ doc_id = result.get("DOC_ID", "")
+
+ # Парсинг на VM + сохранить в Lucee
+ parsed = self._parse_file(filename, file_data)
+ if parsed and parsed.get("status") == "parsed" and doc_id:
+ try:
+ escaped = json.dumps(parsed["elements"], ensure_ascii=False).replace("'", "''")
+ with httpx.Client(http2=True, timeout=15, verify=False) as c:
+ c.post(
+ f"{LUCEE_URL}/api.cfm",
+ data={
+ "action": "execute",
+ "sql": f"UPDATE documents SET elements_json='{escaped}'::jsonb, status='parsed' WHERE id='{doc_id}'"
+ }
+ )
+ except Exception:
+ pass
+
+ result["parsed"] = parsed
self.send_response(200)
self._send_cors()
self.send_header("Content-Type", "application/json; charset=utf-8")
self.end_headers()
- self.wfile.write(resp.content)
+ self.wfile.write(json.dumps(result, ensure_ascii=False).encode())
else:
self._send_error_cors(502, f"assemble failed: HTTP {resp.status_code}")
+ def _parse_file(self, filename, data):
+ """Распарсить файл на VM: PDF→PyPDF2, DOCX/DOC→python-docx, иначе plain text."""
+ import base64
+ ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else ''
+ try:
+ if ext == 'pdf':
+ from PyPDF2 import PdfReader
+ import io as io_mod
+ reader = PdfReader(io_mod.BytesIO(data))
+ elements = []
+ for page in reader.pages:
+ text = page.extract_text()
+ if text:
+ for line in text.split('\n'):
+ line = line.strip()
+ if line:
+ elements.append({"type": "paragraph", "text": line, "style": ""})
+ return {"status": "parsed", "element_count": len(elements), "elements": elements}
+ elif ext in ('docx', 'doc'):
+ # Для DOCX/DOC — оставляем Lucee (Apache POI работает)
+ return None
+ else:
+ # Plain text
+ text = data.decode('utf-8', errors='ignore')[:5000]
+ lines = [l.strip() for l in text.split('\n') if l.strip()]
+ return {"status": "parsed", "element_count": len(lines),
+ "elements": [{"type": "paragraph", "text": l, "style": ""} for l in lines]}
+ except Exception as e:
+ return {"status": "error", "error": str(e), "element_count": 0}
+
def _handle_parse_pdf(self):
"""Распарсить PDF через PyPDF2 (получает base64 тело от Lucee)."""
import base64
diff --git a/index.cfm b/index.cfm
index 1f8210b..d9eeea2 100644
--- a/index.cfm
+++ b/index.cfm
@@ -339,20 +339,14 @@ fileInput.addEventListener('change', async function() {
if (!contractId && zf.contract_id) contractId = zf.contract_id;
renderTable();
- // Авто-парсинг
+ // Авто-парсинг — VM уже распарсила
var zt0 = Date.now();
- try {
- var zpr = await fetch('/parser.cfm?doc_id=' + zf.doc_id);
- var zpd = await zpr.json();
- var zelapsed = ((Date.now() - zt0) / 1000).toFixed(1);
- if (zpd.OK && zpd.STATUS === 'parsed') {
- fileQueue[zIdx].parsed = true;
- fileQueue[zIdx].status = '✓ ' + zpd.ELEMENT_COUNT + ' эл. (' + zelapsed + 'с)';
- } else {
- fileQueue[zIdx].status = '✗ ' + (zpd.ERROR_COUNT > 0 ? (zpd.ERRORS && zpd.ERRORS[0] || 'ошибка') : 'ошибка') + '';
- }
- } catch(zpe) {
- fileQueue[zIdx].status = '✗ парсинг: сеть';
+ var zelapsed = ((Date.now() - zt0) / 1000).toFixed(1);
+ if (zf.PARSED && zf.PARSED.STATUS === 'parsed') {
+ fileQueue[zIdx].parsed = true;
+ fileQueue[zIdx].status = '✓ ' + zf.PARSED.ELEMENT_COUNT + ' эл. (' + zelapsed + 'с)';
+ } else {
+ fileQueue[zIdx].status = '✗ ' + ((zf.PARSED && zf.PARSED.ERROR) || 'ошибка') + '';
}
renderTable();
}
@@ -389,24 +383,17 @@ fileInput.addEventListener('change', async function() {
fileQueue[rowIdx].status = '✓';
fileQueue[rowIdx].uploaded = true;
- // Авто-парсинг сразу после загрузки
+ // Авто-парсинг — VM уже распарсила, берём из ответа
var t0 = Date.now();
- fileQueue[rowIdx].status = '⏳ парсинг...';
- renderTable();
- try {
- var pr = await fetch('/parser.cfm?doc_id=' + resp.DOC_ID);
- var pd = await pr.json();
- var elapsed = ((Date.now() - t0) / 1000).toFixed(1);
- if (pd.OK && pd.STATUS === 'parsed') {
- fileQueue[rowIdx].parsed = true;
- fileQueue[rowIdx].parseInfo = pd;
- fileQueue[rowIdx].status = '✓ ' + pd.ELEMENT_COUNT + ' эл. (' + elapsed + 'с)';
- } else {
- fileQueue[rowIdx].status = '✗ ' + (pd.ERROR_COUNT > 0 ? (pd.ERRORS && pd.ERRORS[0] || 'ошибка') : 'ошибка') + '';
- fileQueue[rowIdx].parseInfo = pd;
- }
- } catch(pe) {
- fileQueue[rowIdx].status = '✗ парсинг: сеть';
+ var elapsed = ((Date.now() - t0) / 1000).toFixed(1);
+ if (resp.PARSED && resp.PARSED.STATUS === 'parsed') {
+ fileQueue[rowIdx].parsed = true;
+ fileQueue[rowIdx].parseInfo = resp.PARSED;
+ fileQueue[rowIdx].status = '✓ ' + resp.PARSED.ELEMENT_COUNT + ' эл. (' + elapsed + 'с)';
+ } else {
+ var errMsg = (resp.PARSED && resp.PARSED.ERROR) || 'ошибка';
+ fileQueue[rowIdx].status = '✗ ' + errMsg + '';
+ fileQueue[rowIdx].parseInfo = resp.PARSED;
}
} catch(err) {
fileQueue[rowIdx].status = '✗ ' + err.message + '';