AI enrichment: show current vs proposed comparison with color coding
Some checks are pending
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Review panel now shows side-by-side comparison for each field:
- Red block "Obecne:" shows current value (what will be replaced)
- Green block "Propozycja:" shows AI suggestion
- Badge: NOWE (green) for empty fields, ZMIANA (yellow) for updates
- New fields are pre-checked, changes are unchecked (safer default)
- Unchanged fields are hidden (no noise)
- Backend now returns current_data alongside proposed_data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-25 17:16:19 +01:00
parent 25e34d254c
commit c2fc5abe6d
2 changed files with 56 additions and 18 deletions

View File

@ -1051,6 +1051,20 @@ WAZNE:
logger.info(f"AI enrichment proposal created for {company.name}. Proposal ID: {proposal_id}. Sources: {sources_used}")
# Get current AI insights for comparison
existing_insights = db.query(CompanyAIInsights).filter_by(company_id=company.id).first()
current_data = {}
if existing_insights:
current_data = {
'business_summary': existing_insights.business_summary or '',
'services_list': existing_insights.services_list or [],
'target_market': existing_insights.target_market or '',
'unique_selling_points': existing_insights.unique_selling_points or [],
'company_values': existing_insights.company_values or [],
'certifications': [c.name for c in (company.certifications or []) if c.is_active] if company.certifications else [],
'industry_tags': existing_insights.industry_tags or [],
}
return jsonify({
'success': True,
'message': f'Propozycja wzbogacenia danych dla "{company.name}" została utworzona i oczekuje na akceptację',
@ -1061,6 +1075,7 @@ WAZNE:
'brave_results_count': len(brave_results['news']) + len(brave_results['web']),
'website_content_length': len(website_content),
'proposed_data': ai_data,
'current_data': current_data,
'requires_approval': True
})

View File

@ -4734,7 +4734,7 @@ function finishAiEnrichment(success) {
}
}
function showProposalApprovalButtons(companyId, proposalId, proposedData) {
function showProposalApprovalButtons(companyId, proposalId, proposedData, currentData) {
// Update modal title
document.getElementById('aiModalTitle').textContent = 'Znalezione informacje — wybierz co dodać';
document.getElementById('aiCancelBtn').style.display = 'none';
@ -4759,37 +4759,59 @@ function showProposalApprovalButtons(companyId, proposalId, proposedData) {
'suggested_category': 'Sugerowana kategoria'
};
// Build review panel
// Helper: format value for display
function formatVal(val) {
if (!val) return '';
if (Array.isArray(val)) return val.join(', ');
return String(val);
}
// Build review panel with current vs proposed comparison
var reviewHtml = '<div style="max-height: 50vh; overflow-y: auto; padding: 0 4px;">';
var fieldCount = 0;
for (var key in fieldLabels) {
var val = proposedData[key];
if (!val || (Array.isArray(val) && val.length === 0)) continue;
var newVal = formatVal(proposedData[key]);
if (!newVal.trim()) continue;
var displayVal = '';
if (Array.isArray(val)) {
displayVal = val.join(', ');
} else if (typeof val === 'string') {
displayVal = val;
} else {
continue;
}
var curVal = formatVal(currentData[key]);
var isNew = !curVal.trim();
var isChanged = curVal.trim() && curVal.trim() !== newVal.trim();
var isSame = curVal.trim() === newVal.trim();
if (isSame) continue; // Skip unchanged fields
if (!displayVal.trim()) continue;
fieldCount++;
var statusBadge = isNew
? '<span style="display:inline-block;padding:1px 6px;background:#dcfce7;color:#166534;border-radius:4px;font-size:11px;font-weight:600;margin-left:6px;">NOWE</span>'
: '<span style="display:inline-block;padding:1px 6px;background:#fef3c7;color:#92400e;border-radius:4px;font-size:11px;font-weight:600;margin-left:6px;">ZMIANA</span>';
var currentHtml = '';
if (!isNew && curVal) {
currentHtml = '<div style="font-size:12px; color:#991b1b; background:#fef2f2; padding:6px 8px; border-radius:4px; margin-bottom:4px; border-left:3px solid #fca5a5;">' +
'<span style="font-weight:600; color:#7f1d1d;">Obecne:</span> ' +
curVal.substring(0, 200) + (curVal.length > 200 ? '...' : '') +
'</div>';
}
var proposedHtml = '<div style="font-size:13px; color:#166534; background:#f0fdf4; padding:6px 8px; border-radius:4px; border-left:3px solid #86efac;">' +
'<span style="font-weight:600; color:#14532d;">Propozycja:</span> ' +
newVal.substring(0, 300) + (newVal.length > 300 ? '...' : '') +
'</div>';
reviewHtml += '<label style="display:flex; gap:10px; padding:12px; margin-bottom:8px; background:#f9fafb; border:1px solid #e5e7eb; border-radius:8px; cursor:pointer; align-items:flex-start; line-height:1.5;">' +
'<input type="checkbox" checked data-field="' + key + '" style="margin-top:4px; width:18px; height:18px; flex-shrink:0;">' +
'<input type="checkbox" ' + (isNew ? 'checked' : '') + ' data-field="' + key + '" style="margin-top:4px; width:18px; height:18px; flex-shrink:0;">' +
'<div style="flex:1; min-width:0;">' +
'<div style="font-weight:600; font-size:13px; color:#1f2937; margin-bottom:2px;">' + fieldLabels[key] + '</div>' +
'<div style="font-size:13px; color:#4b5563; word-break:break-word;">' + displayVal.substring(0, 300) + (displayVal.length > 300 ? '...' : '') + '</div>' +
'<div style="font-weight:600; font-size:13px; color:#1f2937; margin-bottom:4px;">' + fieldLabels[key] + statusBadge + '</div>' +
currentHtml +
proposedHtml +
'</div>' +
'</label>';
}
if (fieldCount === 0) {
reviewHtml += '<p style="text-align:center; color:#64748b; padding:20px;">Nie znaleziono nowych informacji do dodania.</p>';
reviewHtml += '<p style="text-align:center; color:#64748b; padding:20px;">Nie znaleziono nowych informacji do dodania. Profil firmy jest aktualny.</p>';
}
reviewHtml += '</div>';
@ -4955,9 +4977,10 @@ document.addEventListener('DOMContentLoaded', function() {
const insights = data.proposed_data;
const proposalId = data.proposal_id;
const currentData = data.current_data || {};
// Show review panel with checkboxes
showProposalApprovalButtons(companyId, proposalId, insights);
showProposalApprovalButtons(companyId, proposalId, insights, currentData);
return;
} else {
addAiLogEntry('Blad: ' + (data.error || 'Nieznany blad'), 'error');