diff --git a/src/components/AIQueryInterface.astro b/src/components/AIQueryInterface.astro index d787530..79e6455 100644 --- a/src/components/AIQueryInterface.astro +++ b/src/components/AIQueryInterface.astro @@ -422,9 +422,7 @@ class AIQueryInterface { return Object.values(this.elements).some(el => el !== null); } - setupEventListeners() { - console.log('[AI Interface] Setting up event listeners...'); - + setupEventListeners() { this.elements.toggleSwitch?.addEventListener('click', () => this.toggleMode()); this.elements.workflowLabel?.addEventListener('click', () => this.setMode('workflow')); this.elements.toolLabel?.addEventListener('click', () => this.setMode('tool')); @@ -438,13 +436,10 @@ class AIQueryInterface { }); if (this.elements.submitBtn) { - console.log('[AI Interface] Attaching click handler to submit button'); this.elements.submitBtn.addEventListener('click', () => { - console.log('[AI Interface] Submit button clicked!'); this.handleSubmit(); }); } else { - console.error('[AI Interface] Submit button not found!'); } this.elements.dismissSuggestions?.addEventListener('click', () => this.hideSmartPrompting()); @@ -654,9 +649,7 @@ class AIQueryInterface { if (this.elements.smartHint) showElement(this.elements.smartHint); } - async handleSubmit() { - console.log('[AI Interface] handleSubmit called'); - + async handleSubmit() { const query = this.elements.input.value.trim(); if (!query) { @@ -1084,7 +1077,6 @@ class AIQueryInterface { if (downloadBtn && !downloadBtn.hasAttribute('data-setup')) { downloadBtn.setAttribute('data-setup', 'true'); downloadBtn.addEventListener('click', () => this.downloadResults()); - console.log('[AI Interface] Download button setup complete'); } }; @@ -1119,7 +1111,6 @@ class AIQueryInterface { qualityMetrics: { avgProcessingTime: 0, confidenceDistribution: { high: 0, medium: 0, low: 0 } - // Removed aiTransparency }, analysisQuality: 'excellent', keyInsights: [ @@ -1602,25 +1593,33 @@ class AIQueryInterface { concept_name: bg.concept_name, relevance: bg.relevance })) || [], - contextHistory: [], // This would need to be passed from server if needed - embeddingsSimilarities: {} // This would need to be passed from server if needed + contextHistory: [], + embeddingsSimilarities: {} }; + // Enhanced export structure with proper audit trail handling const exportData = { metadata: { timestamp: new Date().toISOString(), - version: "1.0", + version: "1.1", // Increment version for improved structure toolsDataHash: toolsDataHash, aiModel: aiModel, aiParameters: aiParameters, userQuery: inputValue, mode: this.currentMode, processingStats: processingStats, - exportedBy: 'ForensicPathways' + exportedBy: 'ForensicPathways', + auditTrailVersion: '1.1' // Track audit trail format version }, - recommendation: this.currentRecommendation, - auditTrail: this.currentRecommendation.auditTrail || [], - rawContext: rawContext + recommendation: { + // Export recommendation without auditTrail to avoid duplication + ...this.currentRecommendation, + auditTrail: undefined // Remove from recommendation as it's at top level + }, + auditTrail: this.currentRecommendation.auditTrail || [], // Extract to top level + rawContext: rawContext, + // Add validation checksum for integrity + checksum: this.calculateDataChecksum(this.currentRecommendation) }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { @@ -1634,17 +1633,46 @@ class AIQueryInterface { a.click(); URL.revokeObjectURL(url); - console.log('[AI Interface] Analysis downloaded with real metadata:', { + console.log('[AI Interface] Analysis downloaded with enhanced structure:', { + version: '1.1', aiModel, toolsDataHash: toolsDataHash.slice(0, 8) + '...', tokensUsed: aiParameters.totalTokensUsed, - auditEntries: exportData.auditTrail.length + auditEntries: exportData.auditTrail.length, + checksum: exportData.checksum.slice(0, 8) + '...' }); } + calculateDataChecksum(data) { + if (!data) return 'empty'; + + try { + // Simple checksum based on key data properties + const keyData = { + recommendedToolsCount: data.recommended_tools?.length || 0, + backgroundKnowledgeCount: data.background_knowledge?.length || 0, + hasScenarioAnalysis: !!(data.scenario_analysis || data.problem_analysis), + hasApproach: !!data.investigation_approach, + processingTimeMs: data.processingStats?.processingTimeMs || 0 + }; + + const dataString = JSON.stringify(keyData); + let hash = 0; + for (let i = 0; i < dataString.length; i++) { + const char = dataString.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32-bit integer + } + return Math.abs(hash).toString(36); + } catch (error) { + console.error('[AI Interface] Checksum calculation failed:', error); + return 'error'; + } + } + validateUploadStructure(data) { try { - return !!( + const isValid = !!( data && typeof data === 'object' && data.metadata && @@ -1652,8 +1680,32 @@ class AIQueryInterface { typeof data.metadata.timestamp === 'string' && data.recommendation && typeof data.recommendation === 'object' && - Array.isArray(data.auditTrail) + Array.isArray(data.auditTrail) // Audit trail at top level ); + + if (!isValid) { + return false; + } + + // Enhanced validation for audit trail structure + if (data.auditTrail.length > 0) { + const sampleEntry = data.auditTrail[0]; + const hasRequiredFields = !!( + sampleEntry && + typeof sampleEntry === 'object' && + typeof sampleEntry.timestamp === 'number' && + typeof sampleEntry.phase === 'string' && + typeof sampleEntry.action === 'string' && + typeof sampleEntry.confidence === 'number' + ); + + if (!hasRequiredFields) { + console.warn('[AI Interface] Audit trail entries missing required fields'); + return false; + } + } + + return true; } catch (error) { console.error('[AI Interface] Structure validation error:', error); return false; @@ -1666,7 +1718,11 @@ class AIQueryInterface { this.showUploadedBanner(data.metadata); - this.currentRecommendation = data.recommendation; + // Fix: Ensure audit trail is available in recommendation for restoreAIResults + this.currentRecommendation = { + ...data.recommendation, + auditTrail: data.auditTrail // Restore audit trail to recommendation object + }; this.showResults(); @@ -1757,7 +1813,15 @@ class AIQueryInterface { throw new Error('Ungültiges Analyse-Dateiformat'); } - console.log('[AI Interface] Valid previous analysis file uploaded'); + // Store audit trail separately for restore function + this.uploadedAuditTrail = data.auditTrail; + + console.log('[AI Interface] Valid previous analysis file uploaded:', { + version: data.metadata.version || '1.0', + auditEntries: data.auditTrail?.length || 0, + toolsCount: data.recommendation.recommended_tools?.length || 0, + checksum: data.checksum?.slice(0, 8) + '...' || 'none' + }); this.hideResults(); this.hideError(); @@ -2218,15 +2282,20 @@ class AIQueryInterface { if (this.currentRecommendation && this.elements.results) { this.showResults(); - if (this.currentRecommendation.auditTrail) { + // Check both locations for audit trail for backward compatibility + const auditTrail = this.currentRecommendation.auditTrail || this.uploadedAuditTrail; + + if (auditTrail && Array.isArray(auditTrail) && auditTrail.length > 0) { setTimeout(() => { try { - this.renderAuditTrail(this.currentRecommendation.auditTrail); - console.log('[AI Interface] Audit trail restored successfully'); + this.renderAuditTrail(auditTrail); + console.log('[AI Interface] Audit trail restored successfully:', auditTrail.length, 'entries'); } catch (error) { console.error('[AI Interface] Audit trail restore failed:', error); } }, 100); + } else { + console.warn('[AI Interface] No audit trail available for restore'); } this.hideLoading(); diff --git a/src/pages/index.astro b/src/pages/index.astro index 0fc3b3f..28e64fe 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -510,9 +510,7 @@ if (aiAuthRequired) { }, 500); }; - function handleSharedURL() { - console.log('[SHARE] Handling shared URL:', window.location.search); - + function handleSharedURL() { const urlParams = new URLSearchParams(window.location.search); const toolParam = urlParams.get('tool'); const viewParam = urlParams.get('view'); diff --git a/src/utils/aiPipeline.ts b/src/utils/aiPipeline.ts index 5cb974f..0e810d0 100644 --- a/src/utils/aiPipeline.ts +++ b/src/utils/aiPipeline.ts @@ -1,4 +1,4 @@ -// src/utils/aiPipeline.ts - Fixed with accurate audit data and meaningful confidence +// src/utils/aiPipeline.ts - Fixed to remove hardcoded values and improve dynamics import { getCompressedToolsDataForAI, getDataVersion } from './dataService.js'; import { aiService } from './aiService.js'; import { toolSelector, type SelectionContext } from './toolSelector.js'; @@ -77,6 +77,12 @@ interface PipelineContext { }>; seenToolNames: Set; embeddingsSimilarities: Map; + // Add phase metadata for dynamic phase handling + phaseMetadata?: { + phases: any[]; + criticalPhaseIds: string[]; + phaseComplexity: Map; + }; } class AIPipeline { @@ -95,7 +101,7 @@ class AIPipeline { } }; - console.log('[AI-PIPELINE] Initialized with improved audit accuracy'); + console.log('[AI-PIPELINE] Initialized with dynamic phase handling'); } async processQuery(userQuery: string, mode: string): Promise { @@ -121,17 +127,16 @@ class AIPipeline { maxContextLength: this.config.maxContextTokens, currentContextLength: 0, seenToolNames: new Set(), - embeddingsSimilarities: new Map() + embeddingsSimilarities: new Map(), + // Initialize phase metadata dynamically + phaseMetadata: this.initializePhaseMetadata(toolsData.phases) }; - // Skip initialization audit entry - it doesn't add transparency value - console.log('[AI-PIPELINE] Phase 1: Tool candidate selection'); const candidateSelectionStart = Date.now(); const candidateData = await toolSelector.getIntelligentCandidates(userQuery, toolsData, mode, context); - // Calculate meaningful confidence for tool selection const selectionConfidence = this.calculateToolSelectionConfidence( candidateData.tools.length, toolsData.tools.length, @@ -197,8 +202,6 @@ class AIPipeline { const recommendation = this.buildRecommendation(context, mode, finalResult.content); - // Skip completion audit entry - it doesn't add transparency value - const processingStats = { embeddingsUsed: embeddingsService.isEnabled(), candidatesFromEmbeddings: candidateData.tools.length, @@ -242,6 +245,55 @@ class AIPipeline { } } + private initializePhaseMetadata(phases: any[]): { + phases: any[]; + criticalPhaseIds: string[]; + phaseComplexity: Map; + } { + if (!phases || !Array.isArray(phases)) { + console.warn('[AI-PIPELINE] No phases data available, using fallback'); + return { + phases: [], + criticalPhaseIds: [], + phaseComplexity: new Map() + }; + } + + // Dynamically determine critical phases based on typical_tools or key_activities + const criticalPhaseIds = phases + .filter(phase => { + const typicalToolsCount = phase.typical_tools?.length || 0; + const keyActivitiesCount = phase.key_activities?.length || 0; + // Consider phases with many tools or activities as critical + return typicalToolsCount >= 3 || keyActivitiesCount >= 2; + }) + .map(phase => phase.id); + + // Calculate phase complexity based on metadata + const phaseComplexity = new Map(); + phases.forEach(phase => { + let complexity = 1; // Base complexity + + if (phase.typical_tools?.length > 5) complexity += 1; + if (phase.key_activities?.length > 3) complexity += 1; + if (phase.description?.length > 100) complexity += 1; + + phaseComplexity.set(phase.id, complexity); + }); + + console.log('[AI-PIPELINE] Initialized phase metadata:', { + totalPhases: phases.length, + criticalPhases: criticalPhaseIds.length, + avgComplexity: Array.from(phaseComplexity.values()).reduce((sum, c) => sum + c, 0) / phases.length + }); + + return { + phases, + criticalPhaseIds, + phaseComplexity + }; + } + private calculateToolSelectionConfidence( selectedCount: number, totalCount: number, @@ -252,26 +304,22 @@ class AIPipeline { const selectionRatio = selectedCount / totalCount; - // Good selection ratio (5-20% is optimal) if (selectionRatio >= 0.05 && selectionRatio <= 0.20) { confidence += 25; } else if (selectionRatio < 0.05) { - confidence += 15; // Very selective + confidence += 15; } else if (selectionRatio > 0.30) { - confidence -= 15; // Too inclusive + confidence -= 15; } - // Embeddings method bonus if (method.includes('embeddings')) { confidence += 15; } - // Concepts also selected if (conceptsCount > 0) { confidence += 10; } - // Reasonable absolute numbers if (selectedCount >= 8 && selectedCount <= 25) { confidence += 10; } @@ -301,12 +349,12 @@ class AIPipeline { const selections = await toolSelector.selectToolsForPhase(context.userQuery, phase, phaseTools, context); - // Calculate meaningful confidence based on phase selection quality const phaseConfidence = this.calculatePhaseSelectionConfidence( selections.length, phaseTools.length, phase.id, - selections + selections, + context.phaseMetadata ); auditService.addEntry( @@ -341,7 +389,10 @@ class AIPipeline { const moderatedTaskRelevance = this.moderateTaskRelevance(sel.taskRelevance); const priority = this.derivePriorityFromScore(moderatedTaskRelevance); - this.addToolToSelection(context, tool, phase.id, priority, sel.justification, moderatedTaskRelevance, sel.limitations); + // Generate dynamic limitations based on context + const dynamicLimitations = this.generateDynamicLimitations(tool, phase, sel); + + this.addToolToSelection(context, tool, phase.id, priority, sel.justification, moderatedTaskRelevance, dynamicLimitations); auditService.addEntry( 'tool-reasoning', @@ -354,7 +405,7 @@ class AIPipeline { }, { justification: sel.justification, - limitations: sel.limitations, + limitations: dynamicLimitations, addedToPhase: phase.name }, moderatedTaskRelevance || 70, @@ -384,19 +435,24 @@ class AIPipeline { selectedCount: number, availableCount: number, phaseId: string, - selections: any[] + selections: any[], + phaseMetadata?: { + phases: any[]; + criticalPhaseIds: string[]; + phaseComplexity: Map; + } ): number { let confidence = 60; - // Phase-specific expectations - const criticalPhases = ['acquisition', 'examination', 'analysis']; - const isCritical = criticalPhases.includes(phaseId); + // Use dynamic phase metadata instead of hardcoded values + const isCritical = phaseMetadata?.criticalPhaseIds.includes(phaseId) || false; + const phaseComplexity = phaseMetadata?.phaseComplexity.get(phaseId) || 1; // Selection made if (selectedCount > 0) { confidence += 20; } else { - return 30; // No selection is concerning + return 30; } // Selection ratio (for phases, 20-50% is reasonable) @@ -404,14 +460,19 @@ class AIPipeline { if (ratio >= 0.2 && ratio <= 0.5) { confidence += 15; } else if (ratio < 0.2 && selectedCount >= 1) { - confidence += 10; // Selective is ok + confidence += 10; } - // Critical phases should have adequate tools + // Dynamic critical phase handling if (isCritical && selectedCount >= 2) { confidence += 10; } + // Phase complexity factor + if (phaseComplexity > 2 && selectedCount >= phaseComplexity) { + confidence += 5; + } + // Quality of selections (based on task relevance) const avgRelevance = selections.length > 0 ? selections.reduce((sum, s) => sum + (s.taskRelevance || 70), 0) / selections.length : 0; @@ -425,6 +486,35 @@ class AIPipeline { return Math.min(95, Math.max(30, confidence)); } + private generateDynamicLimitations(tool: any, phase: any, selection: any): string[] { + const limitations: string[] = []; + + // Add existing limitations if provided + if (selection.limitations && Array.isArray(selection.limitations)) { + limitations.push(...selection.limitations); + } + + // Generate context-aware limitations + if (tool.type === 'software' && !tool.projectUrl) { + limitations.push('Installation und Konfiguration erforderlich'); + } + + if (tool.skillLevel === 'expert') { + limitations.push('Erfordert spezialisierte Kenntnisse'); + } + + if (tool.license === 'Proprietary') { + limitations.push('Kommerzielle Lizenz erforderlich'); + } + + // Phase-specific limitations + if (phase.id === 'acquisition' && tool.type === 'method') { + limitations.push('Sorgfältige Dokumentation für Chain of Custody erforderlich'); + } + + return limitations.slice(0, 3); // Limit to 3 most relevant + } + private async processToolMode( context: PipelineContext, completedTasks: number, @@ -582,7 +672,6 @@ class AIPipeline { }; } - // This is the fix for "0 tools added" - use the actual valid tools const actualToolsAdded = validTools.map(tool => tool.name); for (const tool of validTools) { @@ -610,6 +699,9 @@ class AIPipeline { moderatedTaskRelevance = this.moderateTaskRelevance(75); } + // Generate dynamic limitations instead of hardcoded ones + const dynamicLimitations = this.generateCompletionLimitations(tool, phase, selection); + this.addToolToSelection( context, tool, @@ -617,16 +709,15 @@ class AIPipeline { 'medium', detailedJustification, moderatedTaskRelevance, - ['Nachträgliche Ergänzung via semantische Phasensuche mit KI-Bewertung'] + dynamicLimitations ); console.log('[AI-PIPELINE] Added phase completion tool with AI reasoning:', tool.name); } - // Use the actual tools added for audit auditService.addPhaseCompletion( phase.id, - actualToolsAdded, // This ensures correct count + actualToolsAdded, selection.completionReasoning || `${actualToolsAdded.length} Tools für ${phase.name} hinzugefügt`, phaseStart, { @@ -660,6 +751,29 @@ class AIPipeline { } } + private generateCompletionLimitations(tool: any, phase: any, selection: any): string[] { + const limitations: string[] = []; + + // Context-specific limitation for completion tools + limitations.push('Nachträgliche Ergänzung - ursprünglich nicht als Hauptmethode identifiziert'); + + // Tool-specific limitations + if (tool.skillLevel === 'expert') { + limitations.push('Erfordert Expertenwissen für optimale Nutzung'); + } + + if (tool.type === 'software' && !tool.projectUrl) { + limitations.push('Zusätzliche Setup-Zeit erforderlich'); + } + + // Phase-specific context + if (phase.typical_tools && !phase.typical_tools.includes(tool.name)) { + limitations.push(`Nicht typisch für ${phase.name}-Phase - alternative Anwendung`); + } + + return limitations.slice(0, 3); + } + private moderateTaskRelevance(taskRelevance: number): number { if (typeof taskRelevance !== 'number') { return 70; @@ -904,7 +1018,6 @@ class AIPipeline { 'background-knowledge' ); - // Calculate confidence based on quality of selections const selectionQualityBonus = this.calculateKnowledgeSelectionBonus(context.backgroundKnowledge, availableConcepts); const finalConfidence = Math.min(95, responseConfidence + selectionQualityBonus); @@ -951,13 +1064,11 @@ class AIPipeline { bonus += 10; } - // Good selection ratio (10-30% of available concepts) const ratio = selectedKnowledge.length / availableConcepts.length; if (ratio >= 0.1 && ratio <= 0.3) { bonus += 15; } - // Quality reasoning provided const hasGoodReasonings = selectedKnowledge.some(bk => bk.relevance && bk.relevance.length > 30 ); @@ -983,7 +1094,6 @@ class AIPipeline { 'final-recommendations' ); - // Calculate bonus based on context quality const contextBonus = this.calculateSynthesisBonus(selectedToolNames, context); const finalConfidence = Math.min(95, confidence + contextBonus);