enhancement 1: audit trail
This commit is contained in:
@@ -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;">
|
||||
|
||||
Reference in New Issue
Block a user