- Dodano skrypt cron do automatycznej ekstrakcji wiedzy (scripts/cron_extract_knowledge.py) - Dodano panel deduplikacji faktów (/admin/zopk/knowledge/fact-duplicates) - Dodano API i funkcje auto-weryfikacji encji i faktów - Dodano panel Timeline ZOPK (/admin/zopk/timeline) z CRUD - Rozszerzono dashboard bazy wiedzy o statystyki weryfikacji i przyciski auto-weryfikacji - Dodano migrację 016_zopk_milestones.sql dla tabeli kamieni milowych - Naprawiono duplikat modelu ZOPKMilestone w database.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
677 lines
22 KiB
HTML
677 lines
22 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Baza Wiedzy ZOPK - Panel Admina{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: var(--spacing-xl);
|
||
flex-wrap: wrap;
|
||
gap: var(--spacing-md);
|
||
}
|
||
|
||
.page-header h1 {
|
||
font-size: var(--font-size-2xl);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: var(--spacing-lg);
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--surface);
|
||
border-radius: var(--radius-lg);
|
||
padding: var(--spacing-lg);
|
||
box-shadow: var(--shadow);
|
||
text-align: center;
|
||
transition: var(--transition);
|
||
text-decoration: none;
|
||
color: inherit;
|
||
}
|
||
|
||
.stat-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-lg);
|
||
}
|
||
|
||
.stat-card.clickable {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 2.5rem;
|
||
margin-bottom: var(--spacing-sm);
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: var(--font-size-2xl);
|
||
font-weight: 700;
|
||
color: var(--primary);
|
||
margin-bottom: var(--spacing-xs);
|
||
}
|
||
|
||
.stat-label {
|
||
color: var(--text-secondary);
|
||
font-size: var(--font-size-sm);
|
||
}
|
||
|
||
.stat-sublabel {
|
||
color: var(--text-tertiary);
|
||
font-size: var(--font-size-xs);
|
||
margin-top: var(--spacing-xs);
|
||
}
|
||
|
||
.section {
|
||
background: var(--surface);
|
||
border-radius: var(--radius-lg);
|
||
padding: var(--spacing-lg);
|
||
box-shadow: var(--shadow);
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: var(--font-size-lg);
|
||
font-weight: 600;
|
||
margin-bottom: var(--spacing-lg);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.quick-links {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: var(--spacing-md);
|
||
}
|
||
|
||
.quick-link {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-md);
|
||
padding: var(--spacing-md);
|
||
background: var(--background);
|
||
border-radius: var(--radius);
|
||
text-decoration: none;
|
||
color: var(--text-primary);
|
||
transition: var(--transition);
|
||
}
|
||
|
||
.quick-link:hover {
|
||
background: var(--primary);
|
||
color: white;
|
||
}
|
||
|
||
.quick-link-icon {
|
||
font-size: 1.5rem;
|
||
width: 40px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--surface);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.quick-link:hover .quick-link-icon {
|
||
background: rgba(255,255,255,0.2);
|
||
}
|
||
|
||
.quick-link-text {
|
||
flex: 1;
|
||
}
|
||
|
||
.quick-link-title {
|
||
font-weight: 600;
|
||
font-size: var(--font-size-base);
|
||
}
|
||
|
||
.quick-link-desc {
|
||
font-size: var(--font-size-xs);
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.top-entities-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.entity-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-sm);
|
||
padding: var(--spacing-sm);
|
||
background: var(--background);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.entity-type-badge {
|
||
padding: 2px 8px;
|
||
border-radius: var(--radius-sm);
|
||
font-size: var(--font-size-xs);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.entity-type-company { background: #dbeafe; color: #1e40af; }
|
||
.entity-type-person { background: #fce7f3; color: #be185d; }
|
||
.entity-type-place { background: #d1fae5; color: #065f46; }
|
||
.entity-type-organization { background: #fef3c7; color: #92400e; }
|
||
.entity-type-project { background: #e0e7ff; color: #3730a3; }
|
||
.entity-type-technology { background: #f3e8ff; color: #7c3aed; }
|
||
|
||
.entity-mentions {
|
||
margin-left: auto;
|
||
font-size: var(--font-size-sm);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: var(--spacing-xl);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.loading::after {
|
||
content: '';
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 2px solid var(--border);
|
||
border-top-color: var(--primary);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-left: var(--spacing-sm);
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
.action-btn {
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
border-radius: var(--radius);
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: var(--font-size-sm);
|
||
font-weight: 500;
|
||
transition: var(--transition);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--spacing-xs);
|
||
}
|
||
|
||
.action-btn-primary {
|
||
background: var(--primary);
|
||
color: white;
|
||
}
|
||
|
||
.action-btn-primary:hover {
|
||
background: var(--primary-dark);
|
||
}
|
||
|
||
.action-btn-secondary {
|
||
background: var(--background);
|
||
color: var(--text-primary);
|
||
border: 1px solid var(--border);
|
||
}
|
||
|
||
.action-btn-secondary:hover {
|
||
background: var(--surface);
|
||
}
|
||
|
||
.pipeline-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-sm);
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
background: var(--background);
|
||
border-radius: var(--radius);
|
||
font-size: var(--font-size-sm);
|
||
}
|
||
|
||
.pipeline-status.running {
|
||
background: #fef3c7;
|
||
color: #92400e;
|
||
}
|
||
|
||
.pipeline-status.success {
|
||
background: #d1fae5;
|
||
color: #065f46;
|
||
}
|
||
|
||
.breadcrumb {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-xs);
|
||
font-size: var(--font-size-sm);
|
||
color: var(--text-secondary);
|
||
margin-bottom: var(--spacing-md);
|
||
}
|
||
|
||
.breadcrumb a {
|
||
color: var(--text-secondary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.breadcrumb a:hover {
|
||
color: var(--primary);
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container">
|
||
<div class="breadcrumb">
|
||
<a href="{{ url_for('admin_zopk') }}">Panel Admina</a>
|
||
<span>›</span>
|
||
<a href="{{ url_for('admin_zopk') }}">ZOP Kaszubia</a>
|
||
<span>›</span>
|
||
<span>Baza Wiedzy</span>
|
||
</div>
|
||
|
||
<div class="page-header">
|
||
<h1>🧠 Baza Wiedzy ZOPK</h1>
|
||
<div class="header-actions">
|
||
<div id="pipelineStatus" class="pipeline-status">
|
||
<span>⏳</span>
|
||
<span>Ładowanie...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats Grid -->
|
||
<div class="stats-grid" id="statsGrid">
|
||
<div class="loading">Ładowanie statystyk...</div>
|
||
</div>
|
||
|
||
<!-- Quick Links -->
|
||
<div class="section">
|
||
<h2 class="section-title">📋 Przegląd danych</h2>
|
||
<div class="quick-links">
|
||
<a href="{{ url_for('admin_zopk_knowledge_chunks') }}" class="quick-link">
|
||
<div class="quick-link-icon">📄</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Chunks (Fragmenty)</div>
|
||
<div class="quick-link-desc">Fragmenty tekstu z embeddingami</div>
|
||
</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts') }}" class="quick-link">
|
||
<div class="quick-link-icon">📌</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Fakty</div>
|
||
<div class="quick-link-desc">Wyekstraktowane fakty strukturalne</div>
|
||
</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_knowledge_entities') }}" class="quick-link">
|
||
<div class="quick-link-icon">🏢</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Encje</div>
|
||
<div class="quick-link-desc">Firmy, osoby, miejsca, projekty</div>
|
||
</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_knowledge_duplicates') }}" class="quick-link" style="border-color: #f59e0b;">
|
||
<div class="quick-link-icon">🔀</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Duplikaty</div>
|
||
<div class="quick-link-desc">Łączenie podobnych encji</div>
|
||
</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_knowledge_graph') }}" class="quick-link" style="border-color: #8b5cf6;">
|
||
<div class="quick-link-icon">🕸️</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Graf relacji</div>
|
||
<div class="quick-link-desc">Wizualizacja powiązań</div>
|
||
</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_news') }}" class="quick-link">
|
||
<div class="quick-link-icon">📰</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Źródłowe artykuły</div>
|
||
<div class="quick-link-desc">Newsy ZOPK (źródło wiedzy)</div>
|
||
</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_fact_duplicates') }}" class="quick-link" style="border-color: #f59e0b;">
|
||
<div class="quick-link-icon">🔀</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Duplikaty faktów</div>
|
||
<div class="quick-link-desc">Łączenie podobnych faktów</div>
|
||
</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_timeline') }}" class="quick-link" style="border-color: #10b981;">
|
||
<div class="quick-link-icon">🗺️</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Timeline ZOPK</div>
|
||
<div class="quick-link-desc">Roadmapa projektu</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Top Entities -->
|
||
<div class="section">
|
||
<h2 class="section-title">🏆 Top 10 encji według wzmianek</h2>
|
||
<div id="topEntities" class="top-entities-grid">
|
||
<div class="loading">Ładowanie encji...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Actions -->
|
||
<div class="section">
|
||
<h2 class="section-title">⚡ Akcje</h2>
|
||
<div class="quick-links">
|
||
<div class="quick-link" onclick="runExtraction()" style="cursor: pointer;">
|
||
<div class="quick-link-icon">🔄</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Uruchom ekstrakcję</div>
|
||
<div class="quick-link-desc">Przetwórz nowe artykuły</div>
|
||
</div>
|
||
</div>
|
||
<div class="quick-link" onclick="generateEmbeddings()" style="cursor: pointer;">
|
||
<div class="quick-link-icon">🧲</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Generuj embeddingi</div>
|
||
<div class="quick-link-desc">Wektory dla chunków bez embeddingów</div>
|
||
</div>
|
||
</div>
|
||
<div class="quick-link" onclick="autoVerifyEntities()" style="cursor: pointer; border-left: 3px solid #10b981;">
|
||
<div class="quick-link-icon">✅</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Auto-weryfikuj encje</div>
|
||
<div class="quick-link-desc">Zweryfikuj encje z ≥5 wzmiankami</div>
|
||
</div>
|
||
</div>
|
||
<div class="quick-link" onclick="autoVerifyFacts()" style="cursor: pointer; border-left: 3px solid #3b82f6;">
|
||
<div class="quick-link-icon">📌</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Auto-weryfikuj fakty</div>
|
||
<div class="quick-link-desc">Zweryfikuj fakty z ważnością ≥70%</div>
|
||
</div>
|
||
</div>
|
||
<a href="{{ url_for('admin_zopk') }}" class="quick-link">
|
||
<div class="quick-link-icon">📊</div>
|
||
<div class="quick-link-text">
|
||
<div class="quick-link-title">Dashboard ZOPK</div>
|
||
<div class="quick-link-desc">Powrót do głównego panelu</div>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Verification Stats -->
|
||
<div class="section">
|
||
<h2 class="section-title">✅ Status weryfikacji</h2>
|
||
<div id="verificationStats" class="stats-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
|
||
<div class="loading">Ładowanie statystyk weryfikacji...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
// Load stats on page load
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadStats();
|
||
});
|
||
|
||
async function loadStats() {
|
||
try {
|
||
const response = await fetch('/admin/zopk/knowledge/stats');
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
renderStats(data);
|
||
renderTopEntities(data.top_entities || []);
|
||
updatePipelineStatus(data);
|
||
} else {
|
||
document.getElementById('statsGrid').innerHTML = '<div class="loading">Błąd ładowania: ' + data.error + '</div>';
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading stats:', error);
|
||
document.getElementById('statsGrid').innerHTML = '<div class="loading">Błąd połączenia</div>';
|
||
}
|
||
}
|
||
|
||
function renderStats(data) {
|
||
const articles = data.articles || {};
|
||
const kb = data.knowledge_base || {};
|
||
|
||
const statsHtml = `
|
||
<a href="{{ url_for('admin_zopk_knowledge_chunks') }}" class="stat-card clickable">
|
||
<div class="stat-icon">📄</div>
|
||
<div class="stat-value">${kb.total_chunks || 0}</div>
|
||
<div class="stat-label">Chunks</div>
|
||
<div class="stat-sublabel">${kb.chunks_with_embeddings || 0} z embeddingami</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts') }}" class="stat-card clickable">
|
||
<div class="stat-icon">📌</div>
|
||
<div class="stat-value">${kb.total_facts || 0}</div>
|
||
<div class="stat-label">Fakty</div>
|
||
<div class="stat-sublabel">Wyekstraktowane informacje</div>
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_knowledge_entities') }}" class="stat-card clickable">
|
||
<div class="stat-icon">🏢</div>
|
||
<div class="stat-value">${kb.total_entities || 0}</div>
|
||
<div class="stat-label">Encje</div>
|
||
<div class="stat-sublabel">Firmy, osoby, miejsca</div>
|
||
</a>
|
||
<div class="stat-card">
|
||
<div class="stat-icon">🔗</div>
|
||
<div class="stat-value">${kb.total_relations || 0}</div>
|
||
<div class="stat-label">Relacje</div>
|
||
<div class="stat-sublabel">Powiązania między encjami</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon">📰</div>
|
||
<div class="stat-value">${articles.extracted || 0}/${articles.scraped || 0}</div>
|
||
<div class="stat-label">Artykuły przetworzone</div>
|
||
<div class="stat-sublabel">${articles.pending_extract || 0} oczekuje</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon">🧲</div>
|
||
<div class="stat-value">${kb.chunks_with_embeddings || 0}/${kb.total_chunks || 0}</div>
|
||
<div class="stat-label">Embeddingi</div>
|
||
<div class="stat-sublabel">${kb.chunks_without_embeddings || 0} do wygenerowania</div>
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById('statsGrid').innerHTML = statsHtml;
|
||
}
|
||
|
||
function renderTopEntities(entities) {
|
||
if (!entities.length) {
|
||
document.getElementById('topEntities').innerHTML = '<div class="loading">Brak encji w bazie</div>';
|
||
return;
|
||
}
|
||
|
||
const html = entities.map(e => `
|
||
<div class="entity-item">
|
||
<span class="entity-type-badge entity-type-${e.type}">${e.type}</span>
|
||
<span>${e.name}</span>
|
||
<span class="entity-mentions">${e.mentions}×</span>
|
||
</div>
|
||
`).join('');
|
||
|
||
document.getElementById('topEntities').innerHTML = html;
|
||
}
|
||
|
||
function updatePipelineStatus(data) {
|
||
const articles = data.articles || {};
|
||
const kb = data.knowledge_base || {};
|
||
|
||
let status = 'success';
|
||
let text = 'Pipeline OK';
|
||
|
||
if (articles.pending_extract > 0) {
|
||
status = 'running';
|
||
text = `${articles.pending_extract} artykułów czeka na ekstrakcję`;
|
||
} else if (kb.chunks_without_embeddings > 0) {
|
||
status = 'running';
|
||
text = `${kb.chunks_without_embeddings} chunków bez embeddingów`;
|
||
}
|
||
|
||
document.getElementById('pipelineStatus').className = 'pipeline-status ' + status;
|
||
document.getElementById('pipelineStatus').innerHTML = `
|
||
<span>${status === 'success' ? '✅' : '⏳'}</span>
|
||
<span>${text}</span>
|
||
`;
|
||
}
|
||
|
||
async function runExtraction() {
|
||
if (!confirm('Uruchomić ekstrakcję wiedzy z artykułów?')) return;
|
||
|
||
try {
|
||
const response = await fetch('/admin/zopk/knowledge/extract', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
},
|
||
body: JSON.stringify({ limit: 50 })
|
||
});
|
||
|
||
const data = await response.json();
|
||
alert(data.message || data.error);
|
||
loadStats();
|
||
} catch (error) {
|
||
alert('Błąd: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async function generateEmbeddings() {
|
||
if (!confirm('Wygenerować embeddingi dla chunków?')) return;
|
||
|
||
try {
|
||
const response = await fetch('/admin/zopk/knowledge/embeddings', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
},
|
||
body: JSON.stringify({ limit: 100 })
|
||
});
|
||
|
||
const data = await response.json();
|
||
alert(data.message || data.error);
|
||
loadStats();
|
||
} catch (error) {
|
||
alert('Błąd: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async function autoVerifyEntities() {
|
||
if (!confirm('Auto-weryfikować encje z ≥5 wzmiankami?')) return;
|
||
|
||
try {
|
||
const response = await fetch('/api/zopk/knowledge/auto-verify/entities', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
},
|
||
body: JSON.stringify({ min_mentions: 5, limit: 100 })
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
alert(`✅ Zweryfikowano ${data.verified_count} encji`);
|
||
loadStats();
|
||
loadVerificationStats();
|
||
} else {
|
||
alert('Błąd: ' + data.error);
|
||
}
|
||
} catch (error) {
|
||
alert('Błąd: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async function autoVerifyFacts() {
|
||
if (!confirm('Auto-weryfikować fakty z ważnością ≥70%?')) return;
|
||
|
||
try {
|
||
const response = await fetch('/api/zopk/knowledge/auto-verify/facts', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
},
|
||
body: JSON.stringify({ min_importance: 0.7, limit: 200 })
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
alert(`✅ Zweryfikowano ${data.verified_count} faktów`);
|
||
loadStats();
|
||
loadVerificationStats();
|
||
} else {
|
||
alert('Błąd: ' + data.error);
|
||
}
|
||
} catch (error) {
|
||
alert('Błąd: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async function loadVerificationStats() {
|
||
try {
|
||
const response = await fetch('/api/zopk/knowledge/dashboard-stats');
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
renderVerificationStats(data);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading verification stats:', error);
|
||
}
|
||
}
|
||
|
||
function renderVerificationStats(data) {
|
||
const stats = data.verification || {};
|
||
const html = `
|
||
<div class="stat-card" style="border-left: 3px solid #10b981;">
|
||
<div class="stat-icon">🏢</div>
|
||
<div class="stat-value">${stats.entities_verified || 0}/${stats.entities_total || 0}</div>
|
||
<div class="stat-label">Encje zweryfikowane</div>
|
||
<div class="stat-sublabel">${stats.entities_pending || 0} oczekuje</div>
|
||
</div>
|
||
<div class="stat-card" style="border-left: 3px solid #3b82f6;">
|
||
<div class="stat-icon">📌</div>
|
||
<div class="stat-value">${stats.facts_verified || 0}/${stats.facts_total || 0}</div>
|
||
<div class="stat-label">Fakty zweryfikowane</div>
|
||
<div class="stat-sublabel">${stats.facts_pending || 0} oczekuje</div>
|
||
</div>
|
||
<div class="stat-card" style="border-left: 3px solid #8b5cf6;">
|
||
<div class="stat-icon">📄</div>
|
||
<div class="stat-value">${stats.chunks_verified || 0}/${stats.chunks_total || 0}</div>
|
||
<div class="stat-label">Chunks zweryfikowane</div>
|
||
<div class="stat-sublabel">${stats.chunks_pending || 0} oczekuje</div>
|
||
</div>
|
||
<div class="stat-card" style="border-left: 3px solid #f59e0b;">
|
||
<div class="stat-icon">🔗</div>
|
||
<div class="stat-value">${stats.relations_verified || 0}/${stats.relations_total || 0}</div>
|
||
<div class="stat-label">Relacje zweryfikowane</div>
|
||
<div class="stat-sublabel">${stats.relations_pending || 0} oczekuje</div>
|
||
</div>
|
||
`;
|
||
document.getElementById('verificationStats').innerHTML = html;
|
||
}
|
||
|
||
// Load verification stats on page load
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadVerificationStats();
|
||
});
|
||
{% endblock %}
|