enhancement 1: audit trail

This commit is contained in:
overcuriousity
2025-08-03 12:41:02 +02:00
parent 57c507915f
commit 6308c03709
6 changed files with 639 additions and 71 deletions

View File

@@ -711,6 +711,7 @@ class AIQueryInterface {
${this.renderBackgroundKnowledge(recommendation.background_knowledge)}
${this.renderWorkflowPhases(toolsByPhase, phaseOrder, phaseNames)}
${this.renderWorkflowSuggestion(recommendation.workflow_suggestion)}
${this.renderAuditTrail(recommendation.auditTrail)}
</div>
`;
@@ -725,12 +726,105 @@ class AIQueryInterface {
${this.renderBackgroundKnowledge(recommendation.background_knowledge)}
${this.renderToolRecommendations(recommendation.recommended_tools)}
${this.renderAdditionalConsiderations(recommendation.additional_considerations)}
${this.renderAuditTrail(recommendation.auditTrail)}
</div>
`;
this.elements.results.innerHTML = html;
}
// NEW: Audit Trail Rendering Functions
renderAuditTrail(auditTrail) {
if (!auditTrail || !Array.isArray(auditTrail) || auditTrail.length === 0) {
return '';
}
// Reuse existing card and info styles from global.css
return `
<div class="card-info-sm mt-4" style="border-left: 4px solid var(--color-text-secondary);">
<div class="flex items-center gap-2 mb-3">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
</svg>
<h4 class="text-sm font-semibold text-secondary">Forensic Audit Trail (${auditTrail.length} Entries)</h4>
<button class="btn-icon btn-sm" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.innerHTML = this.nextElementSibling.style.display === 'none' ? this.innerHTML.replace('▼', '▶') : this.innerHTML.replace('▶', '▼');">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"/>
</svg>
</button>
</div>
<div style="display: none;" class="audit-trail-details">
<div class="text-xs text-muted mb-2">
<strong>Transparency Note:</strong> This trail documents every decision step for forensic verification and reproducibility.
</div>
${auditTrail.map(entry => this.renderAuditEntry(entry)).join('')}
</div>
</div>
`;
}
renderAuditEntry(entry) {
const confidenceColor = entry.confidence >= 80 ? 'var(--color-accent)' :
entry.confidence >= 60 ? 'var(--color-warning)' : 'var(--color-error)';
const formattedTime = new Date(entry.timestamp).toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// Reuse existing grid and text utilities
return `
<div class="border-l-2 pl-3 py-2 mb-2" style="border-left-color: ${confidenceColor};">
<div class="flex justify-between items-center mb-1">
<span class="text-xs font-medium">${entry.phase} → ${entry.action}</span>
<div class="flex items-center gap-2">
<span class="badge badge-mini" style="background-color: ${confidenceColor}; color: white;">
${entry.confidence}% confidence
</span>
<span class="text-xs text-muted">${entry.processingTimeMs}ms</span>
<span class="text-xs text-muted">${formattedTime}</span>
</div>
</div>
<div class="text-xs text-muted grid-cols-2 gap-2" style="display: grid;">
<div><strong>Input:</strong> ${this.formatAuditData(entry.input)}</div>
<div><strong>Output:</strong> ${this.formatAuditData(entry.output)}</div>
</div>
${entry.metadata && Object.keys(entry.metadata).length > 0 ? `
<div class="text-xs text-muted mt-1 pt-1 border-t border-dashed">
<strong>Metadata:</strong> ${this.formatAuditData(entry.metadata)}
</div>
` : ''}
</div>
`;
}
formatAuditData(data) {
if (data === null || data === undefined) return 'null';
if (typeof data === 'string') {
return data.length > 100 ? data.slice(0, 100) + '...' : data;
}
if (typeof data === 'number') return data.toString();
if (typeof data === 'boolean') return data.toString();
if (Array.isArray(data)) {
if (data.length === 0) return '[]';
if (data.length <= 3) return JSON.stringify(data);
return `[${data.slice(0, 3).map(i => typeof i === 'string' ? i : JSON.stringify(i)).join(', ')}, ...+${data.length - 3}]`;
}
if (typeof data === 'object') {
const keys = Object.keys(data);
if (keys.length === 0) return '{}';
if (keys.length <= 3) {
return '{' + keys.map(k => `${k}: ${typeof data[k] === 'string' ? data[k].slice(0, 20) + (data[k].length > 20 ? '...' : '') : JSON.stringify(data[k])}`).join(', ') + '}';
}
return `{${keys.slice(0, 3).join(', ')}, ...+${keys.length - 3} keys}`;
}
return String(data);
}
renderHeader(title, query) {
return `
<div style="text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, var(--color-primary) 0%, #525252 100%); color: white; border-radius: 0.75rem;">