nordabiz/templates/admin/chat_analytics.html
Maciej Pienczyn 9b9ae40932 refactor: Add ai-learning-status and chat-stats API to blueprints
- Add api_ai_learning_status and api_chat_stats to routes_insights.py
- Update chat_analytics template to use new API path
- Add endpoint aliases for backward compatibility

Phase 6.2b - AI API routes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 10:31:29 +01:00

394 lines
14 KiB
HTML
Executable File

{% extends "base.html" %}
{% block title %}Analityka Chatu AI - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.analytics-header {
margin-bottom: var(--spacing-xl);
}
.analytics-header h1 {
font-size: var(--font-size-3xl);
color: var(--text-primary);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
}
.stat-card {
background: var(--surface);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
text-align: center;
}
.stat-value {
font-size: var(--font-size-3xl);
font-weight: 700;
color: var(--primary);
}
.stat-value.positive { color: var(--success); }
.stat-value.negative { color: var(--error); }
.stat-value.neutral { color: var(--secondary); }
.stat-label {
color: var(--text-secondary);
font-size: var(--font-size-sm);
margin-top: var(--spacing-xs);
}
.section {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
margin-bottom: var(--spacing-xl);
}
.section h2 {
font-size: var(--font-size-xl);
margin-bottom: var(--spacing-lg);
color: var(--text-primary);
border-bottom: 2px solid var(--border);
padding-bottom: var(--spacing-sm);
}
.query-list {
list-style: none;
}
.query-item {
padding: var(--spacing-md);
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.query-item:last-child {
border-bottom: none;
}
.query-text {
flex: 1;
font-size: var(--font-size-base);
}
.query-meta {
color: var(--text-secondary);
font-size: var(--font-size-sm);
text-align: right;
}
.feedback-badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius);
font-size: var(--font-size-sm);
font-weight: 500;
}
.feedback-badge.positive {
background: rgba(16, 185, 129, 0.1);
color: var(--success);
}
.feedback-badge.negative {
background: rgba(239, 68, 68, 0.1);
color: var(--error);
}
.satisfaction-bar {
height: 8px;
background: var(--border);
border-radius: 4px;
overflow: hidden;
margin-top: var(--spacing-sm);
}
.satisfaction-fill {
height: 100%;
background: linear-gradient(90deg, var(--error), var(--warning), var(--success));
transition: width 0.3s ease;
}
.response-preview {
background: var(--background);
padding: var(--spacing-md);
border-radius: var(--radius);
margin-top: var(--spacing-sm);
font-size: var(--font-size-sm);
color: var(--text-secondary);
max-height: 100px;
overflow: hidden;
}
</style>
{% endblock %}
{% block content %}
<div class="analytics-header">
<h1>Analityka Chatu AI</h1>
<p class="text-muted">Monitoruj jakość odpowiedzi i zachowania użytkowników</p>
</div>
<!-- Stats Grid -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">{{ total_conversations }}</div>
<div class="stat-label">Rozmów</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ total_user_messages }}</div>
<div class="stat-label">Zapytań użytkowników</div>
</div>
<div class="stat-card">
<div class="stat-value positive">{{ positive_feedback }}</div>
<div class="stat-label">Pozytywnych ocen</div>
</div>
<div class="stat-card">
<div class="stat-value negative">{{ negative_feedback }}</div>
<div class="stat-label">Negatywnych ocen</div>
</div>
<div class="stat-card">
<div class="stat-value {% if satisfaction_rate >= 70 %}positive{% elif satisfaction_rate >= 40 %}neutral{% else %}negative{% endif %}">
{{ satisfaction_rate }}%
</div>
<div class="stat-label">Satysfakcja</div>
<div class="satisfaction-bar">
<div class="satisfaction-fill" style="width: {{ satisfaction_rate }}%"></div>
</div>
</div>
</div>
<!-- Query Statistics (Privacy Protected) -->
<div class="section">
<h2>Statystyki zapytań <span style="font-size: var(--font-size-sm); color: var(--text-secondary);">🔒 Dane zanonimizowane</span></h2>
<div class="stats-grid" style="margin-bottom: var(--spacing-lg);">
<div class="stat-card">
<div class="stat-value">{{ query_stats.total_today }}</div>
<div class="stat-label">Zapytań dzisiaj</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ query_stats.avg_length|int }}</div>
<div class="stat-label">Śr. długość (znaki)</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ query_stats.queries_with_company }}</div>
<div class="stat-label">O firmach</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ query_stats.queries_with_contact }}</div>
<div class="stat-label">O kontaktach</div>
</div>
</div>
<h3 style="margin-top: var(--spacing-lg);">Ostatnia aktywność (zanonimizowana)</h3>
{% if recent_queries %}
<ul class="query-list">
{% for query in recent_queries %}
<li class="query-item">
<div class="query-text">
<span style="color: var(--text-secondary);"></span>
Zapytanie ({{ query.length }} znaków)
{% if query.has_company_mention %}
<span class="badge" style="background: var(--primary-light); color: var(--primary); padding: 2px 6px; border-radius: 4px; font-size: 10px; margin-left: 8px;">FIRMA</span>
{% endif %}
{% if query.has_contact_request %}
<span class="badge" style="background: var(--success-light); color: var(--success); padding: 2px 6px; border-radius: 4px; font-size: 10px; margin-left: 4px;">KONTAKT</span>
{% endif %}
</div>
<div class="query-meta">
{{ query.created_at.strftime('%d.%m %H:%M') }}
</div>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">Brak zapytań</p>
{% endif %}
<div style="margin-top: var(--spacing-lg); padding: var(--spacing-md); background: var(--background); border-radius: var(--radius); font-size: var(--font-size-sm); color: var(--text-secondary);">
<strong>🔒 Ochrona prywatności:</strong> Treść zapytań użytkowników nie jest wyświetlana w panelu admina.
Widoczne są tylko zanonimizowane statystyki (długość, kategorie tematyczne).
</div>
</div>
<!-- Feedback Section -->
<div class="section">
<h2>Odpowiedzi z oceną</h2>
{% if recent_feedback %}
<ul class="query-list">
{% for msg in recent_feedback %}
<li class="query-item" style="flex-direction: column; align-items: flex-start;">
<div style="display: flex; justify-content: space-between; width: 100%; align-items: center;">
<span class="feedback-badge {% if msg.feedback_rating == 2 %}positive{% else %}negative{% endif %}">
{% if msg.feedback_rating == 2 %}
<svg width="16" height="16" fill="currentColor" viewBox="0 0 20 20"><path d="M2 10.5a1.5 1.5 0 113 0v6a1.5 1.5 0 01-3 0v-6zM6 10.333v5.43a2 2 0 001.106 1.79l.05.025A4 4 0 008.943 18h5.416a2 2 0 001.962-1.608l1.2-6A2 2 0 0015.56 8H12V4a2 2 0 00-2-2 1 1 0 00-1 1v.667a4 4 0 01-.8 2.4L6.8 7.933a4 4 0 00-.8 2.4z"/></svg>
Pomocne
{% else %}
<svg width="16" height="16" fill="currentColor" viewBox="0 0 20 20"><path d="M18 9.5a1.5 1.5 0 11-3 0v-6a1.5 1.5 0 013 0v6zM14 9.667v-5.43a2 2 0 00-1.105-1.79l-.05-.025A4 4 0 0011.055 2H5.64a2 2 0 00-1.962 1.608l-1.2 6A2 2 0 004.44 12H8v4a2 2 0 002 2 1 1 0 001-1v-.667a4 4 0 01.8-2.4l1.4-1.866a4 4 0 00.8-2.4z"/></svg>
Do poprawy
{% endif %}
</span>
<span class="query-meta">{{ msg.feedback_at.strftime('%d.%m %H:%M') if msg.feedback_at else '' }}</span>
</div>
<div class="response-preview">{{ msg.content[:300] }}{% if msg.content|length > 300 %}...{% endif %}</div>
{% if msg.feedback_comment %}
<div style="margin-top: var(--spacing-sm); font-size: var(--font-size-sm);">
<strong>Komentarz:</strong> {{ msg.feedback_comment }}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">Brak ocen - popros uzytkownikow o feedback!</p>
{% endif %}
</div>
<!-- AI Learning Status Section -->
<div class="section">
<h2>Uczenie AI z feedbacku</h2>
<div id="learningStatus">
<p class="text-muted">Ladowanie statusu uczenia...</p>
</div>
</div>
<style>
.learning-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.learning-card {
background: var(--background);
padding: var(--spacing-lg);
border-radius: var(--radius);
text-align: center;
}
.learning-card.active {
border-left: 4px solid var(--success);
}
.learning-card.seed {
border-left: 4px solid var(--warning);
}
.learning-value {
font-size: var(--font-size-2xl);
font-weight: 700;
color: var(--primary);
}
.learning-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.example-card {
background: var(--background);
padding: var(--spacing-md);
border-radius: var(--radius);
margin-bottom: var(--spacing-md);
border-left: 3px solid var(--success);
}
.example-card.negative {
border-left-color: var(--error);
}
.example-query {
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.example-response {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
</style>
{% endblock %}
{% block extra_js %}
// Load AI Learning Status
async function loadLearningStatus() {
try {
const response = await fetch('/admin/ai-learning-status');
const data = await response.json();
if (!data.success) {
document.getElementById('learningStatus').innerHTML =
'<p class="text-muted">Blad ladowania statusu</p>';
return;
}
if (!data.learning_active) {
document.getElementById('learningStatus').innerHTML =
'<p class="text-muted">Uczenie z feedbacku nieaktywne</p>';
return;
}
const stats = data.stats || {};
const usingSeed = data.using_seed_examples;
let html = `
<div class="learning-grid">
<div class="learning-card ${usingSeed ? 'seed' : 'active'}">
<div class="learning-value">${usingSeed ? 'Seed' : 'Aktywne'}</div>
<div class="learning-label">${usingSeed ? 'Uzywa przykladow startowych' : 'Uczy sie z feedbacku'}</div>
</div>
<div class="learning-card">
<div class="learning-value">${data.positive_examples_count}</div>
<div class="learning-label">Pozytywnych przykladow</div>
</div>
<div class="learning-card">
<div class="learning-value">${stats.feedback_rate || 0}%</div>
<div class="learning-label">Wskaznik feedbacku</div>
</div>
<div class="learning-card">
<div class="learning-value">${stats.positive_rate || 0}%</div>
<div class="learning-label">Pozytywnych ocen</div>
</div>
</div>
`;
// Show positive examples
if (data.positive_examples && data.positive_examples.length > 0) {
html += '<h3 style="margin: var(--spacing-lg) 0 var(--spacing-md);">Przyklady uzywane do nauki</h3>';
for (const ex of data.positive_examples.slice(0, 3)) {
html += `
<div class="example-card">
<div class="example-query">Q: ${ex.query}</div>
<div class="example-response">${ex.response}</div>
</div>
`;
}
}
// Show patterns to avoid
if (data.negative_patterns && data.negative_patterns.length > 0) {
html += '<h3 style="margin: var(--spacing-lg) 0 var(--spacing-md);">Wzorce do unikania</h3>';
html += '<ul style="color: var(--error); font-size: var(--font-size-sm);">';
for (const pattern of data.negative_patterns) {
html += `<li>${pattern}</li>`;
}
html += '</ul>';
}
document.getElementById('learningStatus').innerHTML = html;
} catch (error) {
console.error('Error loading learning status:', error);
document.getElementById('learningStatus').innerHTML =
'<p class="text-muted">Blad ladowania statusu</p>';
}
}
// Load on page load
loadLearningStatus();
{% endblock %}