diff --git a/deploy/app.js b/deploy/app.js new file mode 100644 index 0000000..a494b90 --- /dev/null +++ b/deploy/app.js @@ -0,0 +1,644 @@ +// Contracts App — весь JS на VM (contracts.kube5s.ru) +// Lucee: только домен + index.cfm-скелет +var VM_API = 'https://contracts.kube5s.ru'; +var UPLOAD_URL = VM_API + '/upload'; +var CONVERT_URL = VM_API + '/convert-doc'; +var UNZIP_URL = VM_API + '/unzip-upload'; +var UNZIP_URL = 'https://contracts.kube5s.ru/unzip-upload'; +var SITE_URL = ''; // same origin for api calls + +var fileInput = document.getElementById('fileInput'); +var fileTable = document.getElementById('fileTable'); +var fileQueue = []; +var contractId = null; + +function formatSize(bytes) { + if (!bytes || bytes === 0) return '—'; + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / 1048576).toFixed(1) + ' MB'; +} + +function formatDate(ts) { + if (!ts) return '—'; + var d = new Date(ts); + return d.toLocaleDateString('ru-RU') + ' ' + d.toLocaleTimeString('ru-RU', {hour:'2-digit',minute:'2-digit'}); +} + +function renderTable() { + if (fileQueue.length === 0) { + fileTable.innerHTML = 'Нет файлов — выберите .docx / .pdf'; + } else { + fileTable.innerHTML = fileQueue.map(function(f, i) { + var isFirst = (i === 0); + var isLast = (i === fileQueue.length - 1); + var arrows = '' + + ''; + return '' + + '' + f.name + '' + + '' + formatDate(f.lastModified) + '' + + '' + formatSize(f.size) + '' + + '' + (f.status || '') + '' + + '' + arrows + '' + + '' + + '' + + ''; + }).join(''); + } + lucide.createIcons(); +} + +function removeFile(i) { + fileQueue.splice(i, 1); + if (fileQueue.length === 0) contractId = null; + renderTable(); +} +window.removeFile = removeFile; + +function moveUp(i) { + if (i <= 0) return; + var tmp = fileQueue[i]; fileQueue[i] = fileQueue[i-1]; fileQueue[i-1] = tmp; + renderTable(); +} +function moveDown(i) { + if (i >= fileQueue.length - 1) return; + var tmp = fileQueue[i]; fileQueue[i] = fileQueue[i+1]; fileQueue[i+1] = tmp; + renderTable(); +} +window.moveUp = moveUp; +window.moveDown = moveDown; + +// ── О сервисе ──────────────────────────────────────────────── +function openAbout() { + document.getElementById('aboutModalOverlay').classList.add('open'); +} +function closeAbout(e) { + if (e && e.target !== document.getElementById('aboutModalOverlay')) return; + document.getElementById('aboutModalOverlay').classList.remove('open'); +} +window.openAbout = openAbout; +window.closeAbout = closeAbout; + +// ── Автозагрузка при выборе файлов ────────────────────────── +fileInput.addEventListener('change', async function() { + var newFiles = Array.from(fileInput.files); + if (newFiles.length === 0) return; + + fileInput.disabled = true; + + for (var i = 0; i < newFiles.length; i++) { + var f = newFiles[i]; + var isZip = f.name.toLowerCase().endsWith('.zip'); + + if (isZip) { + // ZIP: показать временную строку, распаковать, убрать + var zipEntry = { name: f.name, lastModified: f.lastModified, size: f.size, status: '⏳ распаковка...' }; + fileQueue.push(zipEntry); + var zipIdx = fileQueue.length - 1; + renderTable(); + + try { + var zipResp = await new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', UNZIP_URL); + xhr.responseType = 'json'; + xhr.onload = function() { resolve(xhr.response); }; + xhr.onerror = function() { reject(new Error('Сеть')); }; + xhr.ontimeout = function() { reject(new Error('Таймаут')); }; + xhr.timeout = 60000; + xhr.send(f); + }); + var zipData = zipResp; + if (!zipData.ok || !zipData.files) throw new Error('unzip failed'); + + // Убрать строку ZIP из таблицы + fileQueue.splice(zipIdx, 1); + renderTable(); + + // Добавить файлы из архива, пропуская дубликаты + for (var zi = 0; zi < zipData.files.length; zi++) { + var zf = zipData.files[zi]; + if (zf.error) continue; // пропускаем ошибки + + // Проверить дубликат по имени + var dup = -1; + for (var dj = 0; dj < fileQueue.length; dj++) { + if (fileQueue[dj].name === zf.filename) { dup = dj; break; } + } + if (dup >= 0) continue; // уже есть — не добавляем + + var zEntry = { + name: zf.filename, size: zf.size, + doc_id: zf.doc_id, uploaded: true, + status: '⏳ парсинг...' + }; + fileQueue.push(zEntry); + var zIdx = fileQueue.length - 1; + if (!contractId && zf.contract_id) contractId = zf.contract_id; + renderTable(); + + // Авто-парсинг: VM Python парсит ВСЁ + var zpr = zf.parsed; + if (zpr && zpr.status === 'parsed') { + fileQueue[zIdx].parsed = true; + fileQueue[zIdx].status = '✓ ' + zpr.element_count + ' эл.'; + } else if (zpr && zpr.status === 'error') { + fileQueue[zIdx].status = '✗ ' + (zpr.error || 'ошибка парсинга') + ''; + } else { + fileQueue[zIdx].status = '✗ Неизвестная ошибка'; + } + renderTable(); + } + } catch(ze) { + fileQueue[zipIdx].status = '✗ ZIP: ' + ze.message + ''; + renderTable(); + } + continue; // ZIP обработан, переходим к следующему файлу + } + + // Обычный файл (не ZIP) + var entry = { name: f.name, lastModified: f.lastModified, size: f.size, file: f, status: '↑ 0%' }; + var existingIdx = -1; + for (var j = 0; j < fileQueue.length; j++) { + if (fileQueue[j].name === f.name) { existingIdx = j; break; } + } + var rowIdx; + if (existingIdx >= 0) { + fileQueue[existingIdx] = entry; + rowIdx = existingIdx; + } else { + fileQueue.push(entry); + rowIdx = fileQueue.length - 1; + } + renderTable(); + + try { + var resp = await uploadFile(f, function(pct) { + fileQueue[rowIdx].status = '↑ ' + pct + '%'; + renderTable(); + }); + // Python API returns lowercase keys (doc_id, contract_id, parsed) + if (resp && resp.contract_id) contractId = resp.contract_id; + fileQueue[rowIdx].doc_id = resp.doc_id; + fileQueue[rowIdx].status = ''; + fileQueue[rowIdx].uploaded = true; + + // Авто-парсинг: VM Python парсит ВСЁ (PDF + DOCX + DOC) + var t0 = Date.now(); + var pr = resp.parsed; + if (pr && pr.status === 'parsed') { + var elapsed = ((Date.now() - t0) / 1000).toFixed(1); + fileQueue[rowIdx].parsed = true; + fileQueue[rowIdx].parseInfo = pr; + fileQueue[rowIdx].status = '✓ ' + pr.element_count + ' эл. (' + elapsed + 'с)'; + } else if (pr && pr.status === 'error') { + fileQueue[rowIdx].status = '✗ ' + (pr.error || 'ошибка парсинга') + ''; + fileQueue[rowIdx].parseInfo = pr; + } else { + fileQueue[rowIdx].status = '✗ Неизвестная ошибка'; + } + } catch(err) { + fileQueue[rowIdx].status = '✗ ' + err.message + ''; + } + renderTable(); + } + + await refreshSupps(); + + fileInput.value = ''; + fileInput.disabled = false; + // Показать кнопки LLM после авто-парсинга + var llmBtn = document.getElementById('llmBtn'); + llmBtn.style.display = 'flex'; + llmBtn.disabled = false; + lucide.createIcons(); +}); + +function uploadFile(file, onProgress) { + return new Promise(function(resolve, reject) { + // .doc → конвертация в docx + var isDoc = file.name.toLowerCase().endsWith('.doc') && !file.name.toLowerCase().endsWith('.docx'); + var uploadFile = file; + var uploadName = file.name; + + function doUpload() { + var xhr = new XMLHttpRequest(); + var fd = new FormData(); + fd.append('files', uploadFile, uploadName); + if (contractId) fd.append('contract_id', contractId); + xhr.open('POST', UPLOAD_URL); + xhr.upload.onprogress = function(e) { + if (e.lengthComputable && onProgress) onProgress(Math.round(e.loaded / e.total * 100)); + }; + xhr.onload = function() { + try { + var r = JSON.parse(xhr.responseText); + if (r.ok) resolve(r); + else reject(new Error(r.error || 'Неизвестная ошибка')); + } 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(); + } + }); +} + +// ── LLM: Сравнить (Event Sourcing) ──────────────────────── +document.getElementById('llmBtn').addEventListener('click', function() { + if (!contractId) return; + var btn = this; + btn.disabled = true; + btn.innerHTML = ' Сравнение...'; + + var diffCard = document.getElementById('diffCard'); + var diffBody = document.getElementById('diffBody'); + var diffStatus = document.getElementById('diffStatus'); + diffCard.style.display = 'block'; + diffBody.innerHTML = ''; + diffStatus.textContent = '⏳ 0.0с'; + + var llmStart = Date.now(); + var timerInterval = setInterval(function() { + diffStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с'; + }, 200); + + var order = fileQueue.map(function(f) { return f.supp_id; }).filter(Boolean).join(','); + var es = new EventSource('https://contracts.kube5s.ru/process-v2?contract_id=' + contractId + (order ? '&order=' + encodeURIComponent(order) : '')); + var sections = {}; // supplement_id → DOM element + + es.onmessage = function(e) { + var d = JSON.parse(e.data); + + if (d.type === 'extract_start') { + var sec = document.createElement('div'); + sec.style.cssText = 'margin-top:8px;border:1px solid var(--brand-gray);border-radius:6px;overflow:hidden;'; + sec.innerHTML = '
⏳ ' + d.filename + '
'; + sec.querySelector('.diff-section-header').addEventListener('click', function() { + var body = sec.querySelector('.diff-section-body'); + body.style.display = body.style.display === 'none' ? 'block' : 'none'; + }); + diffBody.appendChild(sec); + sections[d.supplement_id] = sec; + } + else if (d.type === 'llm_done') { + var sec = sections[d.supplement_id]; + if (sec) { + sec.querySelector('.diff-section-header').innerHTML = '✓ ' + d.filename + ' — ' + d.ops_count + ' оп., ' + d.mode + ' (' + d.time_s + 'с)'; + sec.querySelector('.diff-section-header').style.color = 'var(--green)'; + } + } + else if (d.type === 'applied') { + var sec = sections[d.supplement_id]; + if (sec && d.ops && d.ops.length > 0) { + var s = d.summary || {}; + var body = sec.querySelector('.diff-section-body'); + var html = '
+' + (s.added||0) + ' ~' + (s.updated||0) + ' -' + (s.deleted||0) + ' ?' + (s.unresolved||0) + '
'; + html += '
'; + d.ops.forEach(function(op) { + var action = op.action; + var cls = action === 'ADD' ? 'diff-added' : action === 'DELETE' ? 'diff-deleted' : action === 'UPDATE' ? 'diff-changed' : ''; + var nr = op.new_row || {}; + var name = nr.name || ''; + var price = nr.price != null ? nr.price : ''; + var qty = nr.qty != null ? nr.qty : ''; + var sum = nr.sum != null ? nr.sum : ''; + var ds = nr.date_start || ''; + html += ''; + }); + html += '
ДействиеУслугаЦенаКол-воСуммаДата
' + action + '' + name + '' + price + '' + qty + '' + sum + '' + ds + '
'; + body.innerHTML = html; + body.style.display = 'block'; + } + } + else if (d.type === 'extract_error' || d.type === 'apply_error') { + diffBody.innerHTML += '
✗ ' + d.filename + ': ' + d.error + '
'; + } + else if (d.type === 'done') { + clearInterval(timerInterval); + es.close(); + diffStatus.textContent = '✓ ' + d.total_time_s + 'с'; + btn.innerHTML = ' ✓ Готово'; + if (d.total_unresolved > 0) { + diffBody.innerHTML += '
⚠ ' + d.total_unresolved + ' неразрешённых строк — разобрать
'; + } + document.getElementById('chatCard').style.display = 'block'; + lucide.createIcons(); + } + else if (d.type === 'error') { + clearInterval(timerInterval); + es.close(); + diffBody.innerHTML += '
✗ ' + d.message + '
'; + btn.innerHTML = ' Сравнить'; + btn.disabled = false; + } + }; + + es.onerror = function() { + clearInterval(timerInterval); + es.close(); + if (diffBody.innerHTML === '') { + diffBody.innerHTML = '
✗ Ошибка соединения
'; + } + btn.innerHTML = ' Сравнить'; + btn.disabled = false; + lucide.createIcons(); + }; +}); + +// ── Обновить supplement_id/type для radio ────────────────── +async function refreshSupps() { + if (!contractId) { console.log('refreshSupps: no contractId'); return; } + try { + var resp = await fetch(VM_API + '/api/supplements?contract_id=' + contractId); + var data = await resp.json(); + console.log('refreshSupps: data=', data); + if (data.ok && data.supplements) { + data.supplements.forEach(function(supp) { + console.log('refreshSupps: supp=', supp); + for (var i = 0; i < fileQueue.length; i++) { + if (fileQueue[i].doc_id === supp.document_id) { + fileQueue[i].supp_id = supp.id; + fileQueue[i].supp_type = supp.type; + } + } + }); + renderTable(); + } + } catch(e) { console.log('refreshSupps error:', e); } +} + +// ── Чат ────────────────────────────────────────────────────── +document.getElementById('chatSend').addEventListener('click', function() { + var input = document.getElementById('chatInput'); + var q = input.value.trim(); + if (!q || !contractId) return; + var msgs = document.getElementById('chatMessages'); + msgs.innerHTML += '
Вы: ' + q + '
'; + input.value = ''; + input.disabled = true; + document.getElementById('chatSend').disabled = true; + msgs.innerHTML += '
⏳ Думаю...
'; + + var fd = new FormData(); + fd.append('question', q); + fetch('/chat.cfm?contract_id=' + contractId, { method: 'POST', body: fd }) + .then(function(r) { return r.json(); }) + .then(function(d) { + msgs.lastChild.remove(); + msgs.innerHTML += '
Ответ: ' + (d.OK ? d.ANSWER : (d.ERROR || '—')) + '
'; + input.disabled = false; + document.getElementById('chatSend').disabled = false; + input.focus(); + }).catch(function() { + msgs.lastChild.remove(); + msgs.innerHTML += '
Ошибка сети
'; + input.disabled = false; + document.getElementById('chatSend').disabled = false; + }); +}); +document.getElementById('chatInput').addEventListener('keydown', function(e) { + if (e.key === 'Enter') document.getElementById('chatSend').click(); +}); + +function closeModal(e) { + if (e && e.target !== document.getElementById('modalOverlay')) return; + document.querySelector('.modal').classList.remove('wide'); + document.getElementById('modalOverlay').classList.remove('open'); +} + +async function showText(i) { + var f = fileQueue[i]; + var docId = f.doc_id; + document.getElementById('modalTitle').textContent = f.name; + document.getElementById('modalBody').innerHTML = 'Загрузка...'; + var modal = document.querySelector('.modal'); + modal.classList.add('wide'); + document.getElementById('modalOverlay').classList.add('open'); + + var leftHtml = 'Загрузка...'; + var rightHtml = ''; + + if (docId) { + try { + var qResp = await fetch(VM_API + '/api/documents/' + docId); + var qData = await qResp.json(); + var elements = []; + if (qData.ok && qData.elements_json) { + var ej = qData.elements_json; + if (typeof ej === 'object' && ej.Value) ej = ej.Value; + if (typeof ej === 'string') elements = JSON.parse(ej); + else elements = ej; + } + + // Общий textify для обеих панелей + var textLines = []; + elements.forEach(function(el) { + var etype = el.type || el.TYPE || '?'; + if (etype === 'paragraph') { + textLines.push(el.text || el.TEXT || ''); + } else if (etype === 'table') { + var rows = el.rows || el.ROWS || []; + if (rows.length > 0) { + var ncols = (rows[0]||[]).length; + textLines.push('--- Таблица ' + rows.length + '×' + ncols + ' ---'); + rows.forEach(function(row) { + var cells = (row||[]).map(function(c){ return String(c||'').replace(/\n/g,' ').replace(/\|/g,'\\|'); }); + textLines.push('| ' + cells.join(' | ') + ' |'); + }); + textLines.push(''); + } + } + }); + var textify = textLines.join('\n').replace(//g,'>'); + + // Левая: сырой текст (textify) + leftHtml = '
' + textify + '
'; + + // Правая: статистика + textify + rightHtml += '
Размер' + formatSize(f.size) + '
'; + rightHtml += '
Изменён' + formatDate(f.lastModified) + '
'; + if (f.parseInfo) { + var d = f.parseInfo; + rightHtml += '
Элементов' + (d.ELEMENT_COUNT||0) + '
'; + rightHtml += '
Параграфов' + (d.PARAGRAPHS||0) + '
'; + rightHtml += '
Таблиц' + (d.TABLES||0) + '
'; + rightHtml += '
Строк таблиц' + (d.TABLE_ROWS||0) + '
'; + if (d.PAGES) rightHtml += '
Страниц' + d.PAGES + '
'; + if (d.PARSE_TIME_MS) { + rightHtml += '
Время парсинга' + (d.PARSE_TIME_MS > 1000 ? (d.PARSE_TIME_MS/1000).toFixed(1) + ' с' : d.PARSE_TIME_MS + ' мс') + '
'; + } + if (d.TEXT_LENGTH) rightHtml += '
Символов' + d.TEXT_LENGTH.toLocaleString() + '
'; + if (d.ERRORS && d.ERRORS.length > 0) { + rightHtml += '
Ошибки:
'; + rightHtml += '
' + d.ERRORS.join('\n') + '
'; + } + } + rightHtml += '
Все элементы:
'; + rightHtml += '
' + textify + '
'; + if (f.supp_id) { + rightHtml += '
Тип ДС' + (f.supp_type || '—') + '
'; + } + } catch(e) { + leftHtml = 'Ошибка'; + } + } else { + leftHtml = '(ещё не загружен)'; + } + + var html = '
' + + '
Сырой текст
' + leftHtml + '
' + + '
Распарсено
' + rightHtml + '
' + + '
'; + document.getElementById('modalBody').innerHTML = html; +} + +window.closeModal = closeModal; +window.showText = showText; + +// ── Промпт ─────────────────────────────────────────────────── +var promptEditor = document.getElementById('promptEditor'); +// ── Промпты (версионные) ───────────────────────────────────── +var promptRole = 'diff'; // текущая выбранная роль +var promptEditor = document.getElementById('promptEditor'); +var promptStatus = document.getElementById('promptStatus'); +var promptHistory = document.getElementById('promptHistory'); +var promptModalOverlay = document.getElementById('promptModalOverlay'); +var promptModalTitle = document.getElementById('promptModalTitle'); +var promptName = document.getElementById('promptName'); +var promptNotes = document.getElementById('promptNotes'); +var activePromptId = {extract: '', diff: ''}; + +function openPromptModal(role) { + promptRole = role; + promptModalTitle.textContent = 'Промпт: ' + (role === 'extract' ? 'Извлечение' : 'Сравнение'); + document.getElementById('promptTabExtract').style.background = role === 'extract' ? 'var(--brand-primary)' : ''; + document.getElementById('promptTabExtract').style.color = role === 'extract' ? '#fff' : ''; + document.getElementById('promptTabDiff').style.background = role === 'diff' ? 'var(--brand-primary)' : ''; + document.getElementById('promptTabDiff').style.color = role === 'diff' ? '#fff' : ''; + promptStatus.textContent = ''; + promptName.value = ''; + promptNotes.value = ''; + loadPrompt(role); + promptModalOverlay.classList.add('open'); +} + +function closePromptModal(e) { + if (e && e.target !== promptModalOverlay) return; + promptModalOverlay.classList.remove('open'); +} + +function loadPrompt(role) { + fetch('/prompt.cfm?action=get_active&role=' + role) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.OK) { + promptEditor.value = d.BODY || ''; + activePromptId[role] = d.ID || ''; + loadHistory(role); + } + }) + .catch(function(e) { console.error('loadPrompt:', e); }); +} + +function loadHistory(role) { + fetch('/prompt.cfm?action=list&role=' + role) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (!d.OK || !d.VERSIONS || !d.VERSIONS.length) { + promptHistory.innerHTML = '
нет версий
'; + return; + } + promptHistory.innerHTML = d.VERSIONS.map(function(v) { + var isActive = v.IS_ACTIVE ? '✓ ' : ''; + var style = v.IS_ACTIVE ? 'font-weight:600;' : ''; + return '
' + + '' + isActive + escHtml(v.NAME) + ' ' + fmtDate(v.CREATED_AT) + '' + + (!v.IS_ACTIVE ? '' : '') + + (!v.IS_ACTIVE ? '' : '') + + '
'; + }).join(''); + }) + .catch(function(e) { console.error('loadHistory:', e); }); +} + +function escHtml(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; } +function fmtDate(d) { if (!d) return ''; return d.replace('T', ' ').substring(0, 16); } + +function activatePrompt(id) { + fetch('/prompt.cfm?action=activate', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({id: id}) + }) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.OK) { loadPrompt(promptRole); promptStatus.textContent = '✓ активирован'; } + else { promptStatus.textContent = '✕ ' + (d.ERROR || 'ошибка'); } + }); +} + +function deletePrompt(id) { + if (!confirm('Удалить эту версию промпта?')) return; + fetch('/prompt.cfm?action=delete', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({id: id}) + }) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.OK) { loadHistory(promptRole); promptStatus.textContent = '✓ удалено'; } + else { promptStatus.textContent = '✕ ' + (d.ERROR || 'ошибка'); } + }); +} + +document.getElementById('promptSave').addEventListener('click', function() { + var body = promptEditor.value.trim(); + if (!body) { promptStatus.textContent = '✕ тело промпта пустое'; return; } + var name = promptName.value.trim() || ('v' + new Date().toISOString().replace(/[:.]/g,'-').substring(0,16)); + fetch('/prompt.cfm?action=save', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + role: promptRole, name: name, body: body, + parent_id: activePromptId[promptRole] || '', + notes: promptNotes.value.trim(), is_active: true + }) + }) + .then(function(r) { return r.json(); }) + .then(function(d) { + if (d.OK) { + promptStatus.textContent = '✓ сохранено (новая версия)'; + promptName.value = ''; promptNotes.value = ''; + loadPrompt(promptRole); + } else { + promptStatus.textContent = '✕ ' + (d.ERROR || 'ошибка'); + } + }); +}); + +document.getElementById('promptTabExtract').addEventListener('click', function() { openPromptModal('extract'); }); +document.getElementById('promptTabDiff').addEventListener('click', function() { openPromptModal('diff'); }); +document.getElementById('promptBtnExtract').addEventListener('click', function() { openPromptModal('extract'); }); +document.getElementById('promptBtnDiff').addEventListener('click', function() { openPromptModal('diff'); }); + diff --git a/deploy/nginx-contracts.conf b/deploy/nginx-contracts.conf index 509ce2f..9274524 100644 --- a/deploy/nginx-contracts.conf +++ b/deploy/nginx-contracts.conf @@ -10,6 +10,13 @@ server { proxy_read_timeout 120s; } + location = /app.js { + alias /home/naeel/contracts/app.js; + add_header Content-Type "application/javascript; charset=utf-8"; + add_header Access-Control-Allow-Origin "*"; + expires 0; + } + location /lucee/ { rewrite ^/lucee/(.*) /$1 break; proxy_pass https://contractor.luceek8s.dev.nubes.ru; diff --git a/index.cfm b/index.cfm index a4e4d8f..89f2190 100644 --- a/index.cfm +++ b/index.cfm @@ -75,7 +75,7 @@
Nubes - Сверка договоров — LLM AI-driven Event Sourcing v1.0.158 — Lucee + Сверка договоров — LLM AI-driven Event Sourcing v1.0.159 — Lucee
@@ -146,7 +146,6 @@
-