feat: Add AI enrichment approval workflow
- Remove confusing "Zweryfikowano 2x | Jakość: 100%" badge - Create AiEnrichmentProposal model for pending AI suggestions - Modify AI enrichment to create proposals instead of direct saves - Add approve/reject API endpoints for proposals - Update frontend to show approval buttons after AI analysis - Proposals expire after 30 days if not reviewed The workflow now requires owner/admin approval before AI-generated data is applied to company profiles. This prevents unwanted data from being automatically added. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
69e71f1f05
commit
f166668f22
@ -18,8 +18,9 @@ from flask import jsonify, request, current_app
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from database import (
|
||||
SessionLocal, Company, User, Person, CompanyPerson, CompanyAIInsights
|
||||
SessionLocal, Company, User, Person, CompanyPerson, CompanyAIInsights, AiEnrichmentProposal
|
||||
)
|
||||
from datetime import timedelta
|
||||
import gemini_service
|
||||
import krs_api_service
|
||||
from . import bp
|
||||
@ -668,43 +669,40 @@ WAZNE:
|
||||
'error': 'Blad parsowania odpowiedzi AI. Sprobuj ponownie.'
|
||||
}), 500
|
||||
|
||||
# Save or update AI insights
|
||||
existing_insights = db.query(CompanyAIInsights).filter_by(company_id=company.id).first()
|
||||
# Create AI enrichment PROPOSAL (requires approval before applying)
|
||||
# Instead of directly saving, we create a proposal that needs to be reviewed
|
||||
|
||||
if existing_insights:
|
||||
# Update existing
|
||||
existing_insights.business_summary = ai_data.get('business_summary')
|
||||
existing_insights.services_list = ai_data.get('services_list', [])
|
||||
existing_insights.target_market = ai_data.get('target_market')
|
||||
existing_insights.unique_selling_points = ai_data.get('unique_selling_points', [])
|
||||
existing_insights.company_values = ai_data.get('company_values', [])
|
||||
existing_insights.certifications = ai_data.get('certifications', [])
|
||||
existing_insights.industry_tags = ai_data.get('industry_tags', [])
|
||||
existing_insights.suggested_category = ai_data.get('suggested_category')
|
||||
existing_insights.category_confidence = ai_data.get('category_confidence')
|
||||
existing_insights.ai_confidence_score = 0.85 # Default confidence
|
||||
existing_insights.processing_time_ms = processing_time
|
||||
existing_insights.analyzed_at = datetime.utcnow()
|
||||
else:
|
||||
# Create new
|
||||
new_insights = CompanyAIInsights(
|
||||
# Check for existing pending proposals
|
||||
existing_pending = db.query(AiEnrichmentProposal).filter_by(
|
||||
company_id=company.id,
|
||||
business_summary=ai_data.get('business_summary'),
|
||||
services_list=ai_data.get('services_list', []),
|
||||
target_market=ai_data.get('target_market'),
|
||||
unique_selling_points=ai_data.get('unique_selling_points', []),
|
||||
company_values=ai_data.get('company_values', []),
|
||||
certifications=ai_data.get('certifications', []),
|
||||
industry_tags=ai_data.get('industry_tags', []),
|
||||
suggested_category=ai_data.get('suggested_category'),
|
||||
category_confidence=ai_data.get('category_confidence'),
|
||||
ai_confidence_score=0.85,
|
||||
processing_time_ms=processing_time,
|
||||
analyzed_at=datetime.utcnow()
|
||||
status='pending'
|
||||
).first()
|
||||
|
||||
if existing_pending:
|
||||
# Update existing pending proposal
|
||||
existing_pending.proposed_data = ai_data
|
||||
existing_pending.data_source = company.website
|
||||
existing_pending.confidence_score = 0.85
|
||||
existing_pending.ai_explanation = f"AI przeanalizowało dane z {len(sources_used)} źródeł: {', '.join(sources_used)}"
|
||||
existing_pending.created_at = datetime.utcnow()
|
||||
existing_pending.expires_at = datetime.utcnow() + timedelta(days=30)
|
||||
proposal = existing_pending
|
||||
else:
|
||||
# Create new proposal
|
||||
proposal = AiEnrichmentProposal(
|
||||
company_id=company.id,
|
||||
status='pending',
|
||||
proposal_type='ai_enrichment',
|
||||
data_source=company.website,
|
||||
proposed_data=ai_data,
|
||||
ai_explanation=f"AI przeanalizowało dane z {len(sources_used)} źródeł: {', '.join(sources_used)}",
|
||||
confidence_score=0.85,
|
||||
expires_at=datetime.utcnow() + timedelta(days=30)
|
||||
)
|
||||
db.add(new_insights)
|
||||
db.add(proposal)
|
||||
|
||||
db.commit()
|
||||
proposal_id = proposal.id
|
||||
|
||||
# Count sources used
|
||||
sources_used = ['database']
|
||||
@ -713,16 +711,19 @@ WAZNE:
|
||||
if website_content:
|
||||
sources_used.append('website')
|
||||
|
||||
logger.info(f"AI enrichment completed for {company.name}. Processing time: {processing_time}ms. Sources: {sources_used}")
|
||||
logger.info(f"AI enrichment proposal created for {company.name}. Proposal ID: {proposal_id}. Sources: {sources_used}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Dane firmy "{company.name}" zostaly wzbogacone przez AI',
|
||||
'message': f'Propozycja wzbogacenia danych dla "{company.name}" została utworzona i oczekuje na akceptację',
|
||||
'proposal_id': proposal_id,
|
||||
'status': 'pending',
|
||||
'processing_time_ms': processing_time,
|
||||
'sources_used': sources_used,
|
||||
'brave_results_count': len(brave_results['news']) + len(brave_results['web']),
|
||||
'website_content_length': len(website_content),
|
||||
'insights': ai_data
|
||||
'proposed_data': ai_data,
|
||||
'requires_approval': True
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@ -736,6 +737,213 @@ WAZNE:
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# AI ENRICHMENT PROPOSALS API ROUTES
|
||||
# ============================================================
|
||||
|
||||
@bp.route('/company/<int:company_id>/proposals', methods=['GET'])
|
||||
@login_required
|
||||
def api_get_proposals(company_id):
|
||||
"""
|
||||
API: Get AI enrichment proposals for a company.
|
||||
|
||||
Returns pending, approved, and rejected proposals.
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
||||
|
||||
# Check permissions
|
||||
if not current_user.is_admin and current_user.company_id != company.id:
|
||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||
|
||||
proposals = db.query(AiEnrichmentProposal).filter_by(
|
||||
company_id=company_id
|
||||
).order_by(AiEnrichmentProposal.created_at.desc()).all()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'proposals': [{
|
||||
'id': p.id,
|
||||
'status': p.status,
|
||||
'proposal_type': p.proposal_type,
|
||||
'proposed_data': p.proposed_data,
|
||||
'ai_explanation': p.ai_explanation,
|
||||
'confidence_score': float(p.confidence_score) if p.confidence_score else None,
|
||||
'created_at': p.created_at.isoformat() if p.created_at else None,
|
||||
'reviewed_at': p.reviewed_at.isoformat() if p.reviewed_at else None,
|
||||
'reviewed_by': p.reviewed_by.email if p.reviewed_by else None,
|
||||
'review_comment': p.review_comment,
|
||||
'approved_fields': p.approved_fields
|
||||
} for p in proposals]
|
||||
})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/company/<int:company_id>/proposals/<int:proposal_id>/approve', methods=['POST'])
|
||||
@login_required
|
||||
def api_approve_proposal(company_id, proposal_id):
|
||||
"""
|
||||
API: Approve an AI enrichment proposal.
|
||||
|
||||
Optionally accepts 'fields' parameter to approve only specific fields.
|
||||
When approved, the data is applied to CompanyAIInsights.
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
||||
|
||||
# Check permissions - only admin or company owner
|
||||
if not current_user.is_admin and current_user.company_id != company.id:
|
||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||
|
||||
proposal = db.query(AiEnrichmentProposal).filter_by(
|
||||
id=proposal_id,
|
||||
company_id=company_id
|
||||
).first()
|
||||
|
||||
if not proposal:
|
||||
return jsonify({'success': False, 'error': 'Propozycja nie istnieje'}), 404
|
||||
|
||||
if proposal.status != 'pending':
|
||||
return jsonify({'success': False, 'error': f'Propozycja ma status: {proposal.status}'}), 400
|
||||
|
||||
data = request.get_json() or {}
|
||||
approved_fields = data.get('fields') # Optional: only approve specific fields
|
||||
comment = data.get('comment', '')
|
||||
|
||||
ai_data = proposal.proposed_data
|
||||
|
||||
# Apply to CompanyAIInsights
|
||||
existing_insights = db.query(CompanyAIInsights).filter_by(company_id=company.id).first()
|
||||
|
||||
# Determine which fields to apply
|
||||
if approved_fields:
|
||||
# Partial approval
|
||||
fields_to_apply = approved_fields
|
||||
else:
|
||||
# Full approval - all fields
|
||||
fields_to_apply = list(ai_data.keys())
|
||||
|
||||
if existing_insights:
|
||||
# Update existing
|
||||
if 'business_summary' in fields_to_apply:
|
||||
existing_insights.business_summary = ai_data.get('business_summary')
|
||||
if 'services_list' in fields_to_apply:
|
||||
existing_insights.services_list = ai_data.get('services_list', [])
|
||||
if 'target_market' in fields_to_apply:
|
||||
existing_insights.target_market = ai_data.get('target_market')
|
||||
if 'unique_selling_points' in fields_to_apply:
|
||||
existing_insights.unique_selling_points = ai_data.get('unique_selling_points', [])
|
||||
if 'company_values' in fields_to_apply:
|
||||
existing_insights.company_values = ai_data.get('company_values', [])
|
||||
if 'certifications' in fields_to_apply:
|
||||
existing_insights.certifications = ai_data.get('certifications', [])
|
||||
if 'industry_tags' in fields_to_apply:
|
||||
existing_insights.industry_tags = ai_data.get('industry_tags', [])
|
||||
if 'suggested_category' in fields_to_apply:
|
||||
existing_insights.suggested_category = ai_data.get('suggested_category')
|
||||
existing_insights.ai_confidence_score = proposal.confidence_score
|
||||
existing_insights.analyzed_at = datetime.utcnow()
|
||||
else:
|
||||
# Create new
|
||||
new_insights = CompanyAIInsights(
|
||||
company_id=company.id,
|
||||
business_summary=ai_data.get('business_summary') if 'business_summary' in fields_to_apply else None,
|
||||
services_list=ai_data.get('services_list', []) if 'services_list' in fields_to_apply else [],
|
||||
target_market=ai_data.get('target_market') if 'target_market' in fields_to_apply else None,
|
||||
unique_selling_points=ai_data.get('unique_selling_points', []) if 'unique_selling_points' in fields_to_apply else [],
|
||||
company_values=ai_data.get('company_values', []) if 'company_values' in fields_to_apply else [],
|
||||
certifications=ai_data.get('certifications', []) if 'certifications' in fields_to_apply else [],
|
||||
industry_tags=ai_data.get('industry_tags', []) if 'industry_tags' in fields_to_apply else [],
|
||||
suggested_category=ai_data.get('suggested_category') if 'suggested_category' in fields_to_apply else None,
|
||||
ai_confidence_score=proposal.confidence_score,
|
||||
analyzed_at=datetime.utcnow()
|
||||
)
|
||||
db.add(new_insights)
|
||||
|
||||
# Update proposal status
|
||||
proposal.status = 'approved'
|
||||
proposal.reviewed_at = datetime.utcnow()
|
||||
proposal.reviewed_by_id = current_user.id
|
||||
proposal.review_comment = comment
|
||||
proposal.approved_fields = fields_to_apply
|
||||
proposal.applied_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
logger.info(f"AI proposal {proposal_id} approved for company {company.name} by {current_user.email}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Propozycja została zaakceptowana i dane zastosowane do profilu',
|
||||
'approved_fields': fields_to_apply
|
||||
})
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error approving proposal {proposal_id}: {str(e)}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/company/<int:company_id>/proposals/<int:proposal_id>/reject', methods=['POST'])
|
||||
@login_required
|
||||
def api_reject_proposal(company_id, proposal_id):
|
||||
"""
|
||||
API: Reject an AI enrichment proposal.
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
||||
|
||||
# Check permissions
|
||||
if not current_user.is_admin and current_user.company_id != company.id:
|
||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||
|
||||
proposal = db.query(AiEnrichmentProposal).filter_by(
|
||||
id=proposal_id,
|
||||
company_id=company_id
|
||||
).first()
|
||||
|
||||
if not proposal:
|
||||
return jsonify({'success': False, 'error': 'Propozycja nie istnieje'}), 404
|
||||
|
||||
if proposal.status != 'pending':
|
||||
return jsonify({'success': False, 'error': f'Propozycja ma status: {proposal.status}'}), 400
|
||||
|
||||
data = request.get_json() or {}
|
||||
comment = data.get('comment', '')
|
||||
|
||||
# Update proposal status
|
||||
proposal.status = 'rejected'
|
||||
proposal.reviewed_at = datetime.utcnow()
|
||||
proposal.reviewed_by_id = current_user.id
|
||||
proposal.review_comment = comment
|
||||
|
||||
db.commit()
|
||||
|
||||
logger.info(f"AI proposal {proposal_id} rejected for company {company.name} by {current_user.email}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Propozycja została odrzucona'
|
||||
})
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error rejecting proposal {proposal_id}: {str(e)}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# UTILITY API ROUTES
|
||||
# ============================================================
|
||||
|
||||
@ -3,5 +3,9 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
### Jan 31, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #180 | 6:25 PM | 🔵 | Nordabiz project architecture analyzed revealing 16+ Flask blueprints with modular organization | ~831 |
|
||||
</claude-mem-context>
|
||||
68
database.py
68
database.py
@ -764,6 +764,7 @@ class Company(Base):
|
||||
# Website scraping and AI analysis
|
||||
website_content = relationship('CompanyWebsiteContent', back_populates='company', cascade='all, delete-orphan')
|
||||
ai_insights = relationship('CompanyAIInsights', back_populates='company', uselist=False)
|
||||
ai_enrichment_proposals = relationship('AiEnrichmentProposal', back_populates='company', cascade='all, delete-orphan')
|
||||
|
||||
|
||||
class Service(Base):
|
||||
@ -1136,6 +1137,73 @@ class CompanyAIInsights(Base):
|
||||
company = relationship('Company', back_populates='ai_insights')
|
||||
|
||||
|
||||
class AiEnrichmentProposal(Base):
|
||||
"""
|
||||
Propozycje wzbogacenia danych przez AI - wymagają akceptacji właściciela/admina.
|
||||
|
||||
Workflow:
|
||||
1. AI analizuje dane ze strony WWW
|
||||
2. Tworzy propozycję (status: pending)
|
||||
3. Właściciel/admin przegląda i akceptuje lub odrzuca
|
||||
4. Po akceptacji dane są dodawane do profilu firmy
|
||||
"""
|
||||
__tablename__ = 'ai_enrichment_proposals'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
company_id = Column(Integer, ForeignKey('companies.id'), nullable=False, index=True)
|
||||
|
||||
# Status: pending, approved, rejected, expired
|
||||
status = Column(String(20), default='pending', nullable=False, index=True)
|
||||
|
||||
# Typ propozycji (website_extraction, krs_update, manual_suggestion)
|
||||
proposal_type = Column(String(50), default='website_extraction', nullable=False)
|
||||
|
||||
# Źródło danych (URL strony, API, itp.)
|
||||
data_source = Column(String(500))
|
||||
|
||||
# Proponowane dane jako JSON
|
||||
proposed_data = Column(JSONB, nullable=False)
|
||||
# Struktura proposed_data:
|
||||
# {
|
||||
# "services": ["usługa 1", "usługa 2"],
|
||||
# "products": ["produkt 1"],
|
||||
# "keywords": ["keyword 1"],
|
||||
# "specializations": ["spec 1"],
|
||||
# "brands": ["marka 1"],
|
||||
# "target_customers": ["klient 1"],
|
||||
# "regions": ["region 1"],
|
||||
# "summary": "Opis firmy..."
|
||||
# }
|
||||
|
||||
# Komentarz AI wyjaśniający propozycję
|
||||
ai_explanation = Column(Text)
|
||||
|
||||
# Wskaźnik pewności AI (0.0 - 1.0)
|
||||
confidence_score = Column(Numeric(3, 2))
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
reviewed_at = Column(DateTime)
|
||||
applied_at = Column(DateTime)
|
||||
expires_at = Column(DateTime) # Propozycje wygasają po X dniach
|
||||
|
||||
# Kto przeglądał
|
||||
reviewed_by_id = Column(Integer, ForeignKey('users.id'))
|
||||
|
||||
# Komentarz przy akceptacji/odrzuceniu
|
||||
review_comment = Column(Text)
|
||||
|
||||
# Które pola zostały zaakceptowane (jeśli częściowa akceptacja)
|
||||
approved_fields = Column(JSONB) # ["services", "keywords"]
|
||||
|
||||
# Relationships
|
||||
company = relationship('Company', back_populates='ai_enrichment_proposals')
|
||||
reviewed_by = relationship('User', foreign_keys=[reviewed_by_id])
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AiEnrichmentProposal {self.id} for Company {self.company_id} ({self.status})>"
|
||||
|
||||
|
||||
class MaturityAssessment(Base):
|
||||
"""Historical tracking of digital maturity scores over time"""
|
||||
__tablename__ = 'maturity_assessments'
|
||||
|
||||
58
database/migrations/041_ai_enrichment_proposals.sql
Normal file
58
database/migrations/041_ai_enrichment_proposals.sql
Normal file
@ -0,0 +1,58 @@
|
||||
-- ============================================================
|
||||
-- 041_ai_enrichment_proposals.sql
|
||||
-- System akceptacji propozycji wzbogacenia danych przez AI
|
||||
-- ============================================================
|
||||
|
||||
-- Tabela propozycji wzbogacenia AI
|
||||
CREATE TABLE IF NOT EXISTS ai_enrichment_proposals (
|
||||
id SERIAL PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
||||
|
||||
-- Status workflow
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
|
||||
-- Typ propozycji
|
||||
proposal_type VARCHAR(50) NOT NULL DEFAULT 'website_extraction',
|
||||
|
||||
-- Źródło danych (URL strony, API, itp.)
|
||||
data_source VARCHAR(500),
|
||||
|
||||
-- Proponowane dane jako JSON
|
||||
proposed_data JSONB NOT NULL,
|
||||
|
||||
-- Komentarz AI wyjaśniający propozycję
|
||||
ai_explanation TEXT,
|
||||
|
||||
-- Wskaźnik pewności AI (0.00 - 1.00)
|
||||
confidence_score NUMERIC(3, 2),
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
reviewed_at TIMESTAMP,
|
||||
applied_at TIMESTAMP,
|
||||
expires_at TIMESTAMP,
|
||||
|
||||
-- Kto przeglądał
|
||||
reviewed_by_id INTEGER REFERENCES users(id),
|
||||
|
||||
-- Komentarz przy akceptacji/odrzuceniu
|
||||
review_comment TEXT,
|
||||
|
||||
-- Które pola zostały zaakceptowane (częściowa akceptacja)
|
||||
approved_fields JSONB
|
||||
);
|
||||
|
||||
-- Indeksy
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_proposals_company ON ai_enrichment_proposals(company_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_proposals_status ON ai_enrichment_proposals(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_proposals_created ON ai_enrichment_proposals(created_at DESC);
|
||||
|
||||
-- Komentarze
|
||||
COMMENT ON TABLE ai_enrichment_proposals IS 'Propozycje wzbogacenia danych przez AI wymagające akceptacji';
|
||||
COMMENT ON COLUMN ai_enrichment_proposals.status IS 'Status: pending, approved, rejected, expired';
|
||||
COMMENT ON COLUMN ai_enrichment_proposals.proposed_data IS 'JSON z proponowanymi danymi (services, keywords, summary, etc.)';
|
||||
COMMENT ON COLUMN ai_enrichment_proposals.approved_fields IS 'Lista zaakceptowanych pól przy częściowej akceptacji';
|
||||
|
||||
-- Grant permissions
|
||||
GRANT ALL ON TABLE ai_enrichment_proposals TO nordabiz_app;
|
||||
GRANT USAGE, SELECT ON SEQUENCE ai_enrichment_proposals_id_seq TO nordabiz_app;
|
||||
@ -568,15 +568,7 @@
|
||||
<span class="company-category-badge">{{ company.category.name }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if quality_data %}
|
||||
<span class="quality-badge {% if quality_data.quality_score >= 95 %}verified{% elif quality_data.quality_score < 80 %}needs-review{% endif %}">
|
||||
✓ Zweryfikowano {{ quality_data.verification_count }}x | Jakość: {{ quality_data.quality_score }}%
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="quality-badge needs-review">
|
||||
⏳ Niezweryfikowana
|
||||
</span>
|
||||
{% endif %}
|
||||
{# Badge jakości usunięty - zbędny i mylący dla użytkowników #}
|
||||
|
||||
<h1 class="company-name">{{ company.name }}</h1>
|
||||
|
||||
@ -3664,6 +3656,101 @@ function finishAiEnrichment(success) {
|
||||
}
|
||||
}
|
||||
|
||||
function showProposalApprovalButtons(companyId, proposalId, proposedData) {
|
||||
// Update modal title
|
||||
document.getElementById('aiModalTitle').textContent = 'Propozycja wymaga akceptacji';
|
||||
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>
|
||||
`;
|
||||
|
||||
// Handle approval
|
||||
document.getElementById('approveProposalBtn').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('approveProposalBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Zapisywanie...';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/company/${companyId}/proposals/${proposalId}/approve`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
}
|
||||
});
|
||||
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>
|
||||
`;
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
} else {
|
||||
addAiLogEntry('Błąd: ' + (data.error || 'Nieznany błąd'), 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✓ Akceptuj i dodaj do profilu';
|
||||
}
|
||||
} catch (error) {
|
||||
addAiLogEntry('Błąd połączenia: ' + error.message, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✓ Akceptuj i dodaj do profilu';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle rejection
|
||||
document.getElementById('rejectProposalBtn').addEventListener('click', async () => {
|
||||
const btn = document.getElementById('rejectProposalBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Odrzucanie...';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/company/${companyId}/proposals/${proposalId}/reject`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
}
|
||||
});
|
||||
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>
|
||||
`;
|
||||
setTimeout(() => closeAiModal(), 2000);
|
||||
} else {
|
||||
addAiLogEntry('Błąd: ' + (data.error || 'Nieznany błąd'), 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✕ Odrzuć propozycję';
|
||||
}
|
||||
} catch (error) {
|
||||
addAiLogEntry('Błąd połączenia: ' + error.message, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '✕ Odrzuć propozycję';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const aiEnrichBtn = document.getElementById('aiEnrichBtn');
|
||||
if (aiEnrichBtn && !aiEnrichBtn.disabled) {
|
||||
@ -3723,38 +3810,37 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
updateAiProgress(85, 'Zapisywanie wynikow...');
|
||||
updateAiProgress(85, 'Propozycja utworzona...');
|
||||
addAiLogEntry('Parsowanie odpowiedzi JSON', 'info');
|
||||
await sleep(200);
|
||||
|
||||
// Show what was generated
|
||||
const insights = data.insights;
|
||||
addAiLogEntry('Wygenerowane dane:', 'step');
|
||||
// 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, 60)}...`, 'success');
|
||||
addAiLogEntry(`Opis biznesowy: ${insights.business_summary.substring(0, 80)}...`, 'info');
|
||||
}
|
||||
if (insights.services_list && insights.services_list.length > 0) {
|
||||
addAiLogEntry(`Uslugi: ${insights.services_list.length} pozycji`, 'success');
|
||||
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(`Wyrozniki: ${insights.unique_selling_points.length} pozycji`, 'success');
|
||||
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 branzowe: ${insights.industry_tags.join(', ')}`, 'success');
|
||||
}
|
||||
if (insights.suggested_category) {
|
||||
addAiLogEntry(`Sugerowana kategoria: ${insights.suggested_category}`, 'success');
|
||||
addAiLogEntry(`Tagi branżowe: ${insights.industry_tags.join(', ')}`, 'info');
|
||||
}
|
||||
|
||||
updateAiProgress(95, 'Finalizacja...');
|
||||
addAiLogEntry('Zapisano do bazy danych', 'info');
|
||||
await sleep(300);
|
||||
|
||||
updateAiProgress(100, 'Oczekuje na akceptację');
|
||||
addAiLogEntry(`Czas przetwarzania: ${data.processing_time_ms}ms`, 'info');
|
||||
addAiLogEntry('Wzbogacanie zakonczone pomyslnie!', 'success', '★');
|
||||
addAiLogEntry('⚠️ PROPOZYCJA WYMAGA AKCEPTACJI', 'step');
|
||||
addAiLogEntry('Kliknij "Akceptuj" aby dodać dane do profilu lub "Odrzuć" aby porzucić', 'info');
|
||||
|
||||
finishAiEnrichment(true);
|
||||
// Show approval buttons
|
||||
showProposalApprovalButtons(companyId, proposalId, insights);
|
||||
return; // Don't close modal yet
|
||||
} else {
|
||||
addAiLogEntry('Blad: ' + (data.error || 'Nieznany blad'), 'error');
|
||||
finishAiEnrichment(false);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user