Livrare Lot 3 (Frontend): aplicație web, aplicație mobilă Android, extensie browser

- Surse complete web (React/Vite) + mobil (React Native/Expo) + extensie (MV3)
- Documentație de livrare: ghid utilizare, matrice trasabilitate cerințe, raport testare furnizor
- Artefacte binare: imagine Docker didi-frontend:lot3-1.0, APK, extensie v3.2.6 + SHA256SUMS
- Configurare adresă platformă externalizată (build args / .env / config.js)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Top Clossers 2026-07-17 12:33:59 +03:00
commit cec967f953
321 changed files with 80506 additions and 0 deletions

131
extension/history.js Normal file
View file

@ -0,0 +1,131 @@
// DIDI History Page Script (Full V3 API)
console.log('[DIDI History] Script loaded');
let historyData = [];
function getRiskColorByScore(score) {
if (score >= 80) return '#B91C1C';
if (score >= 60) return '#EF4444';
if (score >= 40) return '#F97316';
if (score >= 20) return '#EAB308';
return '#22C55E';
}
document.addEventListener('DOMContentLoaded', async () => {
chrome.runtime.sendMessage({ action: 'getAuthStatus' }, (authResponse) => {
const authInfo = document.getElementById('authInfo');
if (authResponse?.authenticated) {
authInfo.innerHTML = `<span class="auth-connected">Connected</span>`;
} else {
authInfo.innerHTML = `<span class="auth-disconnected">Not connected</span>`;
}
});
chrome.storage.local.get(['didiHistory'], (data) => {
historyData = data.didiHistory || [];
console.log('[DIDI History] Loaded', historyData.length, 'items');
renderHistory();
});
setupEventListeners();
});
function renderHistory() {
const grid = document.getElementById('historyGrid');
const subtitle = document.getElementById('subtitle');
if (!historyData || historyData.length === 0) {
subtitle.textContent = 'No analyses yet';
grid.innerHTML = `<div class="empty-state"><h3>No Analysis History</h3><p>Take a screenshot or analyze text to see results here.</p></div>`;
return;
}
subtitle.textContent = `${historyData.length} analysis result(s)`;
grid.innerHTML = historyData.map((item, index) => {
const analysis = item.analysis || {};
// Backend schema is FLAT: components live directly on `analysis`, not nested under `components`.
const verdict = analysis.verdict || {};
const techData = analysis.techniques || {};
const techniques = techData.techniques_detected || [];
const claimsData = analysis.claims || {};
const aiData = analysis.ai_tampered || {};
const riskScore = verdict.risk_score ?? analysis.risk_score ?? 0;
const riskCategory = verdict.risk_category || analysis.risk_category || 'Unknown';
const riskColor = getRiskColorByScore(riskScore);
const aiProb = aiData.ai_probability ?? 0;
const analysisType = analysis.analysis_type || analysis.input_type || 'text';
const createdAt = item.timestamp || '';
return `
<div class="history-item" data-index="${index}">
<div class="risk-badge" style="background: ${riskColor}20; border-color: ${riskColor};">
<span class="risk-score" style="color: ${riskColor};">${riskScore}</span>
<span class="risk-category" style="color: ${riskColor};">${riskCategory}</span>
</div>
<div class="item-preview">
<span style="color: #1fb6bd; font-size: 11px; text-transform: uppercase; font-weight: 600;">${analysisType}</span>
</div>
<div class="item-stats">
<div class="stat"><span class="stat-label">Techniques</span><span class="stat-value">${techniques.length}</span></div>
<div class="stat"><span class="stat-label">Claims</span><span class="stat-value">${claimsData.total_claims || 0}</span></div>
<div class="stat"><span class="stat-label">AI Prob.</span><span class="stat-value">${aiProb}%</span></div>
</div>
<div class="item-actions">
<button class="view-details-btn" data-index="${index}">View Details</button>
<button class="delete-btn" data-index="${index}">Delete</button>
</div>
${createdAt ? `<div class="timestamp">${new Date(createdAt).toLocaleString()}</div>` : ''}
</div>`;
}).join('');
}
function setupEventListeners() {
const modal = document.getElementById('analysisModal');
const modalClose = document.getElementById('modalClose');
document.getElementById('historyGrid').addEventListener('click', (e) => {
if (e.target.classList.contains('view-details-btn')) {
e.preventDefault(); e.stopPropagation();
openModal(parseInt(e.target.getAttribute('data-index'), 10));
return;
}
if (e.target.classList.contains('delete-btn')) {
e.preventDefault(); e.stopPropagation();
deleteItem(parseInt(e.target.getAttribute('data-index'), 10));
return;
}
const card = e.target.closest('.history-item');
if (card && !e.target.closest('button')) {
openModal(parseInt(card.getAttribute('data-index'), 10));
}
});
modalClose.addEventListener('click', () => closeModal());
modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
}
function deleteItem(index) {
if (!confirm('Delete this analysis result?')) return;
historyData.splice(index, 1);
chrome.storage.local.set({ didiHistory: historyData }, () => renderHistory());
}
function openModal(index) {
const item = historyData[index];
if (!item) return;
const modal = document.getElementById('analysisModal');
document.getElementById('modalBody').innerHTML = generateAnalysisHTML(item);
modal.classList.add('active');
}
function closeModal() {
document.getElementById('analysisModal').classList.remove('active');
}
function generateAnalysisHTML(item) {
// Shared renderer mirrors web app PipelineAnalysis layout.
return window.DidiRender.renderAnalysisHTML(item.analysis || {});
}