Two-step AI enrichment: preview with checkboxes before saving
Some checks are pending
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Redesigned AI enrichment flow: 1. Button renamed to "Zbierz informacje o firmie" with tooltip explaining what will happen (search web, show results for approval) 2. After AI analysis: show clean review panel with checkboxes for each field (description, services, USPs, tags, etc.) instead of tech log 3. User selects which fields to keep, clicks "Zapisz wybrane (N)" 4. Only checked fields are sent to /approve endpoint 5. Progress messages simplified to non-technical Polish Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f8fdb6a106
commit
25e34d254c
@ -962,6 +962,7 @@
|
||||
id="aiEnrichBtn"
|
||||
class="ai-enrich-btn"
|
||||
data-company-id="{{ company.id }}"
|
||||
title="Przeszukamy internet i stronę WWW firmy, żeby znaleźć brakujące informacje. Wyniki pokażemy do akceptacji — nic nie zostanie zmienione bez Twojej zgody."
|
||||
{% if not can_enrich %}disabled title="Tylko administrator lub właściciel firmy może wzbogacić dane"{% endif %}
|
||||
>
|
||||
<span class="spinner"></span>
|
||||
@ -970,7 +971,7 @@
|
||||
<path d="M2 17l10 5 10-5"/>
|
||||
<path d="M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
<span class="btn-text">Wzbogac dane AI</span>
|
||||
<span class="btn-text">Zbierz informacje o firmie</span>
|
||||
</button>
|
||||
{% if is_admin %}
|
||||
<button
|
||||
@ -4735,27 +4736,106 @@ function finishAiEnrichment(success) {
|
||||
|
||||
function showProposalApprovalButtons(companyId, proposalId, proposedData) {
|
||||
// Update modal title
|
||||
document.getElementById('aiModalTitle').textContent = 'Propozycja wymaga akceptacji';
|
||||
document.getElementById('aiModalTitle').textContent = 'Znalezione informacje — wybierz co dodać';
|
||||
document.getElementById('aiCancelBtn').style.display = 'none';
|
||||
document.getElementById('aiSpinner').style.display = 'none';
|
||||
|
||||
// Create approval buttons container
|
||||
const footer = document.getElementById('aiProgressFooter');
|
||||
footer.style.display = 'flex';
|
||||
footer.innerHTML = `
|
||||
<div style="display: flex; gap: var(--spacing-md); width: 100%; justify-content: center; flex-wrap: wrap;">
|
||||
<button id="approveProposalBtn" class="btn btn-success" style="padding: 12px 24px; font-weight: 600;">
|
||||
✓ Akceptuj i dodaj do profilu
|
||||
</button>
|
||||
<button id="rejectProposalBtn" class="btn btn-danger" style="padding: 12px 24px; font-weight: 600;">
|
||||
✕ Odrzuć propozycję
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
// Hide the technical log, show review panel instead
|
||||
var logContainer = document.getElementById('aiLogContainer');
|
||||
if (logContainer) logContainer.style.display = 'none';
|
||||
var progressBar = document.querySelector('.ai-progress-bar');
|
||||
if (progressBar) progressBar.style.display = 'none';
|
||||
|
||||
// Handle approval
|
||||
// Field labels in Polish
|
||||
var fieldLabels = {
|
||||
'business_summary': 'Opis firmy',
|
||||
'services_list': 'Usługi',
|
||||
'target_market': 'Grupa docelowa',
|
||||
'unique_selling_points': 'Wyróżniki firmy',
|
||||
'company_values': 'Wartości firmy',
|
||||
'certifications': 'Certyfikaty',
|
||||
'industry_tags': 'Tagi branżowe',
|
||||
'recent_news': 'Ostatnie wiadomości',
|
||||
'suggested_category': 'Sugerowana kategoria'
|
||||
};
|
||||
|
||||
// Build review panel
|
||||
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 displayVal = '';
|
||||
if (Array.isArray(val)) {
|
||||
displayVal = val.join(', ');
|
||||
} else if (typeof val === 'string') {
|
||||
displayVal = val;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!displayVal.trim()) continue;
|
||||
fieldCount++;
|
||||
|
||||
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;">' +
|
||||
'<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>' +
|
||||
'</label>';
|
||||
}
|
||||
|
||||
if (fieldCount === 0) {
|
||||
reviewHtml += '<p style="text-align:center; color:#64748b; padding:20px;">Nie znaleziono nowych informacji do dodania.</p>';
|
||||
}
|
||||
|
||||
reviewHtml += '</div>';
|
||||
|
||||
// Insert review panel before footer
|
||||
var footer = document.getElementById('aiProgressFooter');
|
||||
var reviewDiv = document.createElement('div');
|
||||
reviewDiv.id = 'aiReviewPanel';
|
||||
reviewDiv.innerHTML = reviewHtml;
|
||||
footer.parentElement.insertBefore(reviewDiv, footer);
|
||||
|
||||
// Show action buttons
|
||||
footer.style.display = 'flex';
|
||||
if (fieldCount > 0) {
|
||||
footer.innerHTML = '<div style="display:flex; gap:var(--spacing-md); width:100%; justify-content:center; flex-wrap:wrap; padding-top:12px; border-top:1px solid #e5e7eb;">' +
|
||||
'<button id="approveProposalBtn" class="btn btn-success" style="padding:12px 24px; font-weight:600;">Zapisz wybrane (' + fieldCount + ')</button>' +
|
||||
'<button id="rejectProposalBtn" class="btn btn-danger" style="padding:12px 24px; font-weight:600;">Odrzuć wszystko</button>' +
|
||||
'</div>';
|
||||
} else {
|
||||
footer.innerHTML = '<div style="text-align:center; padding:12px;"><button onclick="closeAiModal()" class="btn btn-outline" style="padding:12px 24px;">Zamknij</button></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Update counter when checkboxes change
|
||||
var checkboxes = reviewDiv.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(function(cb) {
|
||||
cb.addEventListener('change', function() {
|
||||
var checked = reviewDiv.querySelectorAll('input[type="checkbox"]:checked').length;
|
||||
var btn = document.getElementById('approveProposalBtn');
|
||||
if (btn) {
|
||||
btn.textContent = 'Zapisz wybrane (' + checked + ')';
|
||||
btn.disabled = checked === 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle approval (only selected fields)
|
||||
document.getElementById('approveProposalBtn').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('approveProposalBtn');
|
||||
var selectedFields = [];
|
||||
reviewDiv.querySelectorAll('input[type="checkbox"]:checked').forEach(function(cb) {
|
||||
selectedFields.push(cb.dataset.field);
|
||||
});
|
||||
|
||||
if (selectedFields.length === 0) return;
|
||||
|
||||
var btn = document.getElementById('approveProposalBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Zapisywanie...';
|
||||
|
||||
@ -4766,34 +4846,26 @@ function showProposalApprovalButtons(companyId, proposalId, proposedData) {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
body: JSON.stringify({ fields: selectedFields })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
addAiLogEntry('✓ Propozycja ZAAKCEPTOWANA - dane dodane do profilu!', 'success', '★');
|
||||
footer.innerHTML = `
|
||||
<div style="text-align: center; color: #10b981; font-weight: 600;">
|
||||
✓ Dane zostały dodane do profilu firmy
|
||||
<br><small style="font-weight: normal;">Odśwież stronę aby zobaczyć zmiany</small>
|
||||
</div>
|
||||
`;
|
||||
footer.innerHTML = '<div style="text-align:center; color:#10b981; font-weight:600; padding:12px;">Dane zostały dodane do profilu firmy<br><small style="font-weight:normal;">Strona się odświeży...</small></div>';
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
addAiLogEntry('Błąd: ' + (data.error || 'Nieznany błąd'), 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✓ Akceptuj i dodaj do profilu';
|
||||
btn.textContent = 'Zapisz wybrane (' + selectedFields.length + ')';
|
||||
}
|
||||
} catch (error) {
|
||||
addAiLogEntry('Błąd połączenia: ' + error.message, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✓ Akceptuj i dodaj do profilu';
|
||||
btn.textContent = 'Zapisz wybrane (' + selectedFields.length + ')';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle rejection
|
||||
document.getElementById('rejectProposalBtn').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('rejectProposalBtn');
|
||||
var btn = document.getElementById('rejectProposalBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Odrzucanie...';
|
||||
|
||||
@ -4809,18 +4881,11 @@ function showProposalApprovalButtons(companyId, proposalId, proposedData) {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
addAiLogEntry('✕ Propozycja ODRZUCONA - dane nie zostały dodane', 'info');
|
||||
footer.innerHTML = `
|
||||
<div style="text-align: center; color: #ef4444;">
|
||||
✕ Propozycja odrzucona
|
||||
<br><small>Możesz spróbować ponownie później</small>
|
||||
</div>
|
||||
`;
|
||||
footer.innerHTML = '<div style="text-align:center; color:#ef4444; padding:12px;">Propozycja odrzucona<br><small>Możesz spróbować ponownie później</small></div>';
|
||||
setTimeout(() => closeAiModal(), 2000);
|
||||
} else {
|
||||
addAiLogEntry('Błąd: ' + (data.error || 'Nieznany błąd'), 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✕ Odrzuć propozycję';
|
||||
btn.textContent = 'Odrzuć wszystko';
|
||||
}
|
||||
} catch (error) {
|
||||
addAiLogEntry('Błąd połączenia: ' + error.message, 'error');
|
||||
@ -4842,35 +4907,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Step 1: Initialize
|
||||
updateAiProgress(5, 'Inicjalizacja...');
|
||||
addAiLogEntry(`Rozpoczynam wzbogacanie danych dla: ${companyName}`, 'step');
|
||||
addAiLogEntry(`Szukam informacji o: ${companyName}`, 'step');
|
||||
await sleep(300);
|
||||
|
||||
if (aiEnrichmentCancelled) return;
|
||||
|
||||
// Step 2: Collecting data
|
||||
updateAiProgress(15, 'Zbieranie danych firmy...');
|
||||
addAiLogEntry('Pobieranie aktualnych danych firmy z bazy', 'info');
|
||||
updateAiProgress(15, 'Przeszukuję internet...');
|
||||
addAiLogEntry('Sprawdzam stronę WWW firmy i wyniki wyszukiwania', 'info');
|
||||
await sleep(400);
|
||||
|
||||
if (aiEnrichmentCancelled) return;
|
||||
|
||||
// Step 3: Preparing prompt
|
||||
updateAiProgress(25, 'Przygotowywanie zapytania AI...');
|
||||
addAiLogEntry('Budowanie kontekstu dla modelu AI', 'info');
|
||||
addAiLogEntry('Dane do analizy: nazwa, kategoria, opis, uslugi, kompetencje', 'info');
|
||||
updateAiProgress(30, 'Analizuję znalezione dane...');
|
||||
addAiLogEntry('Przygotowuję dane do analizy przez AI', 'info');
|
||||
await sleep(300);
|
||||
|
||||
if (aiEnrichmentCancelled) return;
|
||||
|
||||
// Step 4: Calling AI
|
||||
updateAiProgress(35, 'Wysylanie do Gemini AI...');
|
||||
addAiLogEntry('Laczenie z Google Gemini API...', 'step');
|
||||
updateAiProgress(40, 'AI analizuje informacje...');
|
||||
addAiLogEntry('To może potrwać do 30 sekund...', 'info');
|
||||
|
||||
try {
|
||||
aiEnrichmentController = new AbortController();
|
||||
|
||||
addAiLogEntry('Oczekiwanie na odpowiedz AI (moze potrwac do 30s)...', 'info');
|
||||
updateAiProgress(45, 'Analizowanie przez AI...');
|
||||
updateAiProgress(50, 'AI analizuje informacje...');
|
||||
|
||||
const response = await fetch(`/api/company/${companyId}/enrich-ai`, {
|
||||
method: 'POST',
|
||||
@ -4889,37 +4949,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
updateAiProgress(85, 'Propozycja utworzona...');
|
||||
addAiLogEntry('Parsowanie odpowiedzi JSON', 'info');
|
||||
await sleep(200);
|
||||
updateAiProgress(100, 'Gotowe!');
|
||||
addAiLogEntry('Znaleziono informacje — przygotowuję podgląd...', 'step');
|
||||
await sleep(500);
|
||||
|
||||
// Show what was proposed (not yet saved!)
|
||||
const insights = data.proposed_data;
|
||||
const proposalId = data.proposal_id;
|
||||
|
||||
addAiLogEntry('AI przygotowało propozycję danych:', 'step');
|
||||
|
||||
if (insights.business_summary) {
|
||||
addAiLogEntry(`Opis biznesowy: ${insights.business_summary.substring(0, 80)}...`, 'info');
|
||||
}
|
||||
if (insights.services_list && insights.services_list.length > 0) {
|
||||
addAiLogEntry(`Usługi (${insights.services_list.length}): ${insights.services_list.slice(0, 5).join(', ')}${insights.services_list.length > 5 ? '...' : ''}`, 'info');
|
||||
}
|
||||
if (insights.unique_selling_points && insights.unique_selling_points.length > 0) {
|
||||
addAiLogEntry(`Wyróżniki (${insights.unique_selling_points.length}): ${insights.unique_selling_points.slice(0, 3).join(', ')}`, 'info');
|
||||
}
|
||||
if (insights.industry_tags && insights.industry_tags.length > 0) {
|
||||
addAiLogEntry(`Tagi branżowe: ${insights.industry_tags.join(', ')}`, 'info');
|
||||
}
|
||||
|
||||
updateAiProgress(100, 'Oczekuje na akceptację');
|
||||
addAiLogEntry(`Czas przetwarzania: ${data.processing_time_ms}ms`, 'info');
|
||||
addAiLogEntry('⚠️ PROPOZYCJA WYMAGA AKCEPTACJI', 'step');
|
||||
addAiLogEntry('Kliknij "Akceptuj" aby dodać dane do profilu lub "Odrzuć" aby porzucić', 'info');
|
||||
|
||||
// Show approval buttons
|
||||
// Show review panel with checkboxes
|
||||
showProposalApprovalButtons(companyId, proposalId, insights);
|
||||
return; // Don't close modal yet
|
||||
return;
|
||||
} else {
|
||||
addAiLogEntry('Blad: ' + (data.error || 'Nieznany blad'), 'error');
|
||||
finishAiEnrichment(false);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user