v1.0.178: Фаза 3 — compare.js. applyCompareEvent, startCompareSSE, renderCompareOpsTable. ~160 строк дублирования SSE → ~60 строк общих + 2 тонких вызова в runCompareForGroup и llmBtn.

This commit is contained in:
2026-06-25 09:24:26 +04:00
parent e7917c8af5
commit c9d55b3d80
3 changed files with 301 additions and 148 deletions
+57 -148
View File
@@ -187,7 +187,8 @@ window.runCompareForGroup = async function(gi, btn) {
}
var cid = ad.contract_ids[0];
// Создать тело для результатов под кнопкой
// Создать контейнер для результатов сравнения
var cmpBody = document.getElementById('cmpBody_' + gi);
if (!cmpBody) {
cmpBody = document.createElement('div');
@@ -197,93 +198,53 @@ window.runCompareForGroup = async function(gi, btn) {
btn.parentNode.insertBefore(cmpBody, btn.nextSibling);
}
cmpBody.innerHTML = '';
var cmpBody = document.getElementById('cmpBody_' + gi);
cmpBody.style.display = 'block';
cmpBody.innerHTML = '';
btn.innerHTML = '<span class="spinner"></span> Сравнение...';
stepActive('stepCompare');
var statusEl = document.createElement('div');
statusEl.style.cssText = 'font-size:11px;color:var(--muted);margin-bottom:4px;';
statusEl.textContent = '⏳ 0.0с';
cmpBody.appendChild(statusEl);
var start = Date.now();
state._activeCompare.timer = setInterval(function() {
statusEl.textContent = '⏳ ' + ((Date.now() - start) / 1000).toFixed(1) + 'с';
}, 200);
var es = new EventSource(VM_API + '/process-v2?contract_id=' + cid);
state._activeCompare.es = es;
var sections = {};
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 = '<div class="diff-section-header" style="padding:8px 12px;background:var(--brand-grey-light);cursor:pointer;font-size:13px;font-weight:600;" onclick="var b=this.nextElementSibling;b.style.display=b.style.display===\'none\'?\'block\':\'none\';">⏳ ' + d.filename + '</div><div class="diff-section-body" style="display:none;padding:8px 12px;"></div>';
cmpBody.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)';
// Фаза 3: унифицированный SSE через startCompareSSE (compare.js)
var result = startCompareSSE(
VM_API + '/process-v2?contract_id=' + cid,
cmpBody,
statusEl,
{
onDone: function(d, sections) {
state._activeCompare.timer = null;
state._activeCompare.es = null;
group.compare = {
status: 'done',
totalTime: d.total_time_s + 'с',
bodyHTML: cmpBody.innerHTML,
sections: sections
};
renderGroups(state);
document.querySelectorAll('.cmp-btn').forEach(function(b) { b.disabled = false; });
stepDone('stepCompare');
},
onError: function(d) {
state._activeCompare.timer = null;
state._activeCompare.es = null;
group.compare = null;
cmpBody.innerHTML += '<div style="color:var(--destructive);">✗ ' + d.message + '</div>';
btn.innerHTML = '✗'; btn.disabled = false;
document.querySelectorAll('.cmp-btn').forEach(function(b) { b.disabled = false; });
},
onConnectionError: function() {
state._activeCompare.timer = null;
state._activeCompare.es = null;
group.compare = null;
btn.innerHTML = '✗ соединение'; btn.disabled = false;
document.querySelectorAll('.cmp-btn').forEach(function(b) { b.disabled = false; });
}
}
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 = '<div style="font-size:12px;margin-bottom:6px;">+' + (s.added||0) + ' ~' + (s.updated||0) + ' -' + (s.deleted||0) + '</div>';
html += '<div class="table-wrap"><table style="font-size:11px;"><thead><tr><th>Действие</th><th>Услуга</th><th>Цена</th><th>Кол-во</th><th>Сумма</th><th>Дата</th></tr></thead><tbody>';
d.ops.forEach(function(op) {
var cls = op.action === 'ADD' ? 'diff-added' : op.action === 'DELETE' ? 'diff-deleted' : 'diff-changed';
var nr = op.new_row || {};
html += '<tr class="' + cls + '"><td>' + op.action + '</td><td>' + (nr.name||'') + '</td><td class="num-cell">' + (nr.price!=null?nr.price:'') + '</td><td class="num-cell">' + (nr.qty!=null?nr.qty:'') + '</td><td class="num-cell">' + (nr.sum!=null?nr.sum:'') + '</td><td>' + (nr.date_start||'') + '</td></tr>';
});
html += '</tbody></table></div>';
body.innerHTML = html;
body.style.display = 'block';
}
}
else if (d.type === 'done') {
clearInterval(state._activeCompare.timer); state._activeCompare.timer = null;
es.close(); state._activeCompare.es = null;
statusEl.textContent = '✓ ' + d.total_time_s + 'с';
statusEl.style.color = 'var(--green)';
// Фаза 2: вместо markGroupDone — флаг в state + пересборка карточек
// renderGroups перестроит карточку как «готовую» (renderGroupCardDone)
group.compare = {
status: 'done',
totalTime: d.total_time_s + 'с',
bodyHTML: cmpBody.innerHTML
};
renderGroups(state);
// Разблокировать остальные кнопки
document.querySelectorAll('.cmp-btn').forEach(function(b) { b.disabled = false; });
stepDone('stepCompare');
}
else if (d.type === 'error') {
clearInterval(state._activeCompare.timer); state._activeCompare.timer = null;
es.close(); state._activeCompare.es = null;
group.compare = null; // Фаза 2: сбросить статус группы при ошибке
cmpBody.innerHTML += '<div style="color:var(--destructive);">✗ ' + d.message + '</div>';
btn.innerHTML = '✗'; btn.disabled = false;
document.querySelectorAll('.cmp-btn').forEach(function(b) { b.disabled = false; });
}
};
es.onerror = function() {
clearInterval(state._activeCompare.timer); state._activeCompare.timer = null;
es.close(); state._activeCompare.es = null;
group.compare = null; // Фаза 2: сбросить статус группы при ошибке
btn.innerHTML = '✗ соединение'; btn.disabled = false;
document.querySelectorAll('.cmp-btn').forEach(function(b) { b.disabled = false; });
};
);
state._activeCompare.es = result.es;
state._activeCompare.timer = result.timer;
};
// uploadFile → files.js (Фаза 1)
@@ -303,61 +264,14 @@ document.getElementById('llmBtn').addEventListener('click', function() {
compareStatus.textContent = '⏳ 0.0с';
var llmStart = Date.now();
var timerInterval = setInterval(function() {
compareStatus.textContent = '⏳ ' + ((Date.now() - llmStart) / 1000).toFixed(1) + 'с';
}, 200);
var order = state.files.map(function(f) { return f.supp_id; }).filter(Boolean).join(',');
var es = new EventSource('https://contracts.kube5s.ru/process-v2?contract_id=' + state.contractId + (order ? '&order=' + encodeURIComponent(order) : ''));
var sections = {}; // supplement_id → DOM element
var url = 'https://contracts.kube5s.ru/process-v2?contract_id=' + state.contractId +
(order ? '&order=' + encodeURIComponent(order) : '');
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 = '<div class="diff-section-header" style="padding:8px 12px;background:var(--brand-grey-light);cursor:pointer;font-size:13px;font-weight:600;" onclick="var b=this.nextElementSibling;b.style.display=b.style.display===\'none\'?\'block\':\'none\';">⏳ ' + d.filename + '</div><div class="diff-section-body" style="display:none;padding:8px 12px;"></div>';
compareBody.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 = '<div style="font-size:12px;margin-bottom:6px;">+' + (s.added||0) + ' ~' + (s.updated||0) + ' -' + (s.deleted||0) + ' ?' + (s.unresolved||0) + '</div>';
html += '<div class="table-wrap"><table style="font-size:11px;"><thead><tr><th>Действие</th><th>Услуга</th><th>Цена</th><th>Кол-во</th><th>Сумма</th><th>Дата</th></tr></thead><tbody>';
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 += '<tr class="' + cls + '"><td>' + action + '</td><td>' + name + '</td><td class="num-cell">' + price + '</td><td class="num-cell">' + qty + '</td><td class="num-cell">' + sum + '</td><td>' + ds + '</td></tr>';
});
html += '</tbody></table></div>';
body.innerHTML = html;
body.style.display = 'block';
}
}
else if (d.type === 'extract_error' || d.type === 'apply_error') {
compareBody.innerHTML += '<div style="font-size:12px;color:var(--destructive);">✗ ' + d.filename + ': ' + d.error + '</div>';
}
else if (d.type === 'done') {
clearInterval(timerInterval);
es.close();
compareStatus.textContent = '✓ ' + d.total_time_s + 'с';
// Фаза 3: унифицированный SSE через startCompareSSE (compare.js)
startCompareSSE(url, compareBody, compareStatus, {
onDone: function(d) {
btn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px;"></i> ✓ Готово';
stepDone('stepCompare');
if (d.total_unresolved > 0) {
@@ -365,26 +279,21 @@ document.getElementById('llmBtn').addEventListener('click', function() {
}
document.getElementById('chatCard').style.display = 'block';
lucide.createIcons();
}
else if (d.type === 'error') {
clearInterval(timerInterval);
es.close();
compareBody.innerHTML += '<div style=\"color:var(--destructive);margin-top:8px;\">✗ ' + d.message + '</div>';
btn.innerHTML = '<i data-lucide=\"scale\" style=\"width:16px;height:16px;\"></i> Общее сравнение';
},
onError: function(d) {
compareBody.innerHTML += '<div style="color:var(--destructive);margin-top:8px;">✗ ' + d.message + '</div>';
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Общее сравнение';
btn.disabled = false;
},
onConnectionError: function() {
if (compareBody.innerHTML === '') {
compareBody.innerHTML = '<div style="color:var(--destructive);">✗ Ошибка соединения</div>';
}
btn.innerHTML = '<i data-lucide="scale" style="width:16px;height:16px;"></i> Общее сравнение';
btn.disabled = false;
lucide.createIcons();
}
};
es.onerror = function() {
clearInterval(timerInterval);
es.close();
if (compareBody.innerHTML === '') {
compareBody.innerHTML = '<div style=\"color:var(--destructive);\">✗ Ошибка соединения</div>';
}
btn.innerHTML = '<i data-lucide=\"scale\" style=\"width:16px;height:16px;\"></i> Общее сравнение';
btn.disabled = false;
lucide.createIcons();
};
});
});
// refreshSupps → files.js (Фаза 1)