diff --git a/.env.example b/.env.example index 1814d9f..82074eb 100644 --- a/.env.example +++ b/.env.example @@ -178,16 +178,16 @@ GIT_API_TOKEN=your-git-api-token # ============================================================================ # Enable detailed audit trail of AI decision-making -FORENSIC_AUDIT_ENABLED=true +PUBLIC_FORENSIC_AUDIT_ENABLED=true # Audit detail level: minimal, standard, verbose -FORENSIC_AUDIT_DETAIL_LEVEL=standard +PUBLIC_FORENSIC_AUDIT_DETAIL_LEVEL=standard # Audit retention time (hours) -FORENSIC_AUDIT_RETENTION_HOURS=24 +PUBLIC_FORENSIC_AUDIT_RETENTION_HOURS=24 # Maximum audit entries per request -FORENSIC_AUDIT_MAX_ENTRIES=50 +PUBLIC_FORENSIC_AUDIT_MAX_ENTRIES=50 # ============================================================================ # 10. SIMPLIFIED CONFIDENCE SCORING SYSTEM diff --git a/public/js/auditTrailRenderer.js b/public/js/auditTrailRenderer.js new file mode 100644 index 0000000..9aed45a --- /dev/null +++ b/public/js/auditTrailRenderer.js @@ -0,0 +1,389 @@ +// src/js/auditTrailRenderer.js + +import { auditService } from '../../src/utils/auditService.js'; + +export class AuditTrailRenderer { + constructor(containerId, options = {}) { + this.containerId = containerId; + this.options = { + title: options.title || 'KI-Entscheidungspfad', + collapsible: options.collapsible !== false, + defaultExpanded: options.defaultExpanded || false, + ...options + }; + this.componentId = `audit-trail-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`; + } + + /** + * Render audit trail from raw audit data + * FIXED: Proper Promise handling + */ + render(rawAuditTrail) { + const container = document.getElementById(this.containerId); + if (!container) { + console.error(`[AUDIT RENDERER] Container ${this.containerId} not found`); + return; + } + + if (!rawAuditTrail || !Array.isArray(rawAuditTrail) || rawAuditTrail.length === 0) { + this.renderEmpty(); + return; + } + + try { + console.log('[AUDIT RENDERER] Processing audit trail...', rawAuditTrail.length, 'entries'); + + // Process audit trail using the centralized service (synchronous) + const processedAudit = auditService.processAuditTrail(rawAuditTrail); + + console.log('[AUDIT RENDERER] Processed audit:', processedAudit); + + if (processedAudit && processedAudit.phases && processedAudit.phases.length > 0) { + this.renderProcessed(processedAudit); + // Attach event handlers after DOM is updated + setTimeout(() => this.attachEventHandlers(), 0); + } else { + console.warn('[AUDIT RENDERER] No processed audit data'); + this.renderEmpty(); + } + } catch (error) { + console.error('[AUDIT RENDERER] Failed to render audit trail:', error); + this.renderError(error); + } + } + /** + * Render processed audit trail + */ + renderProcessed(processedAudit) { + const container = document.getElementById(this.containerId); + if (!container) return; + + const detailsId = `${this.componentId}-details`; + + console.log('[AUDIT RENDERER] Rendering processed audit with', processedAudit.phases.length, 'phases'); + + container.innerHTML = ` +
+
+
+
+
+

${this.options.title}

+
+ +
+
+
+ ${auditService.formatDuration(processedAudit.totalTime)} +
+
+
+ ${processedAudit.avgConfidence}% Vertrauen +
+
+ ${processedAudit.stepCount} Schritte +
+
+
+ + ${this.options.collapsible ? ` +
+ + + +
+ ` : ''} +
+ +
+ ${this.renderSummary(processedAudit)} + ${this.renderProcessFlow(processedAudit)} + ${this.renderTechnicalDetails(processedAudit)} +
+
+ `; + + console.log('[AUDIT RENDERER] HTML rendered successfully'); + } + + /** + * Render audit summary section + */ + renderSummary(audit) { + return ` +
+
📊 Analyse-Qualität
+
+
+
${audit.highConfidenceSteps}
+
Hohe Sicherheit
+
+
+
+ ${audit.lowConfidenceSteps} +
+
Unsichere Schritte
+
+
+
${auditService.formatDuration(audit.totalTime)}
+
Verarbeitungszeit
+
+
+ + ${audit.summary.keyInsights && audit.summary.keyInsights.length > 0 ? ` +
+
✓ Erkenntnisse:
+ +
+ ` : ''} + + ${audit.summary.potentialIssues && audit.summary.potentialIssues.length > 0 ? ` +
+
⚠ Hinweise:
+ +
+ ` : ''} +
+ `; + } + + /** + * Render process flow section + */ + renderProcessFlow(audit) { + if (!audit.phases || audit.phases.length === 0) { + return '

Keine Phasen verfügbar

'; + } + + return ` +
+ ${audit.phases.map((phase, index) => ` +
+
+
+ ${phase.icon || '📋'} + ${phase.displayName || phase.name} +
+
+
+
+
+
+
+ ${phase.avgConfidence || 0}% +
+
+ +
+ ${(phase.entries || []).map(entry => ` +
+
+ ${auditService.getActionDisplayName(entry.action)} + +
+ + ${(entry.inputSummary && entry.inputSummary !== 'null') || (entry.outputSummary && entry.outputSummary !== 'null') ? ` +
+ ${entry.inputSummary && entry.inputSummary !== 'null' ? ` +
Input: ${this.escapeHtml(entry.inputSummary)}
+ ` : ''} + ${entry.outputSummary && entry.outputSummary !== 'null' ? ` +
Output: ${this.escapeHtml(entry.outputSummary)}
+ ` : ''} +
+ ` : ''} +
+ `).join('')} +
+
+ `).join('')} +
+ `; + } + + /** + * Render technical details section + */ + renderTechnicalDetails(audit) { + const technicalId = `${this.componentId}-technical`; + + return ` +
+ + +
+ `; + } + + /** + * Attach event handlers for interactions + */ + attachEventHandlers() { + console.log('[AUDIT RENDERER] Attaching event handlers...'); + + // Handle collapsible header + if (this.options.collapsible) { + const header = document.querySelector(`[data-target="${this.componentId}-details"]`); + const details = document.getElementById(`${this.componentId}-details`); + const toggleIcon = header?.querySelector('.toggle-icon svg'); + + if (header && details && toggleIcon) { + // Remove existing listeners + header.replaceWith(header.cloneNode(true)); + const newHeader = document.querySelector(`[data-target="${this.componentId}-details"]`); + const newToggleIcon = newHeader?.querySelector('.toggle-icon svg'); + + if (newHeader && newToggleIcon) { + newHeader.addEventListener('click', () => { + const isCollapsed = details.classList.contains('collapsed'); + + if (isCollapsed) { + details.classList.remove('collapsed'); + newToggleIcon.style.transform = 'rotate(180deg)'; + } else { + details.classList.add('collapsed'); + newToggleIcon.style.transform = 'rotate(0deg)'; + } + }); + console.log('[AUDIT RENDERER] Collapsible header handler attached'); + } + } + } + + // Handle technical details toggle + const technicalBtn = document.querySelector(`[data-target="${this.componentId}-technical"]`); + const technicalDetails = document.getElementById(`${this.componentId}-technical`); + + if (technicalBtn && technicalDetails) { + // Remove existing listener + technicalBtn.replaceWith(technicalBtn.cloneNode(true)); + const newTechnicalBtn = document.querySelector(`[data-target="${this.componentId}-technical"]`); + + if (newTechnicalBtn) { + newTechnicalBtn.addEventListener('click', () => { + const isCollapsed = technicalDetails.classList.contains('collapsed'); + + if (isCollapsed) { + technicalDetails.classList.remove('collapsed'); + newTechnicalBtn.textContent = '🔧 Technische Details ausblenden'; + } else { + technicalDetails.classList.add('collapsed'); + newTechnicalBtn.textContent = '🔧 Technische Details anzeigen'; + } + }); + console.log('[AUDIT RENDERER] Technical details handler attached'); + } + } + } + + /** + * Render empty state + */ + renderEmpty() { + const container = document.getElementById(this.containerId); + if (container) { + container.innerHTML = ` +
+
+
+
+

Kein Audit-Trail verfügbar

+
+
+
+ `; + } + } + + /** + * Render error state + */ + renderError(error) { + const container = document.getElementById(this.containerId); + if (container) { + container.innerHTML = ` +
+
+
+
+

Audit-Trail Fehler

+
+
+
+

+ Fehler beim Laden der Audit-Informationen: ${this.escapeHtml(error.message)} +

+
+
+ `; + } + } + + /** + * Utility method to escape HTML + */ + escapeHtml(text) { + if (typeof text !== 'string') return String(text); + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + /** + * Clear the audit trail display + */ + clear() { + const container = document.getElementById(this.containerId); + if (container) { + container.innerHTML = ''; + } + } + + /** + * Get container element + */ + getContainer() { + return document.getElementById(this.containerId); + } +} \ No newline at end of file diff --git a/src/components/AIQueryInterface.astro b/src/components/AIQueryInterface.astro index e8a7604..b8b3969 100644 --- a/src/components/AIQueryInterface.astro +++ b/src/components/AIQueryInterface.astro @@ -1,6 +1,9 @@ --- +// src/components/AIQueryInterface.astro + import { getToolsData } from '../utils/dataService.js'; import { isToolHosted } from '../utils/toolHelpers.js'; +import { AuditTrailRenderer } from '../js/auditTrailRenderer.js'; const data = await getToolsData(); const tools = data.tools; @@ -207,16 +210,22 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || []; - + - \ No newline at end of file diff --git a/src/components/ToolFilters.astro b/src/components/ToolFilters.astro index 841c7fc..9726b9b 100644 --- a/src/components/ToolFilters.astro +++ b/src/components/ToolFilters.astro @@ -55,7 +55,7 @@ const sortedTags = Object.entries(tagFrequency)