forensic-ai #4

Merged
mstoeck3 merged 20 commits from forensic-ai into main 2025-08-05 20:56:02 +00:00
6 changed files with 22 additions and 79 deletions
Showing only changes of commit b515a45e1e - Show all commits

View File

@ -702,12 +702,10 @@ class AIQueryInterface {
toolsByPhase[phase] = []; toolsByPhase[phase] = [];
}); });
// DEBUG: Log recommendation structure
console.log('[AI Results] Recommendation structure:', recommendation); console.log('[AI Results] Recommendation structure:', recommendation);
console.log('[AI Results] Recommended tools:', recommendation.recommended_tools); console.log('[AI Results] Recommended tools:', recommendation.recommended_tools);
recommendation.recommended_tools?.forEach(recTool => { recommendation.recommended_tools?.forEach(recTool => {
// DEBUG: Log each tool's confidence data
console.log('[AI Results] Tool confidence data:', recTool.name, recTool.confidence); console.log('[AI Results] Tool confidence data:', recTool.name, recTool.confidence);
if (toolsByPhase[recTool.phase]) { if (toolsByPhase[recTool.phase]) {
@ -716,7 +714,7 @@ class AIQueryInterface {
toolsByPhase[recTool.phase].push({ toolsByPhase[recTool.phase].push({
...fullTool, ...fullTool,
recommendation: recTool, recommendation: recTool,
confidence: recTool.confidence, // Ensure confidence is passed confidence: recTool.confidence,
justification: recTool.justification, justification: recTool.justification,
priority: recTool.priority, priority: recTool.priority,
recommendationStrength: recTool.recommendationStrength recommendationStrength: recTool.recommendationStrength
@ -836,13 +834,11 @@ class AIQueryInterface {
return ''; return '';
} }
// Calculate summary statistics
const totalTime = auditTrail.reduce((sum, entry) => sum + entry.processingTimeMs, 0); const totalTime = auditTrail.reduce((sum, entry) => sum + entry.processingTimeMs, 0);
const avgConfidence = auditTrail.reduce((sum, entry) => sum + entry.confidence, 0) / auditTrail.length; const avgConfidence = auditTrail.reduce((sum, entry) => sum + entry.confidence, 0) / auditTrail.length;
const lowConfidenceSteps = auditTrail.filter(entry => entry.confidence < 60).length; const lowConfidenceSteps = auditTrail.filter(entry => entry.confidence < 60).length;
const highConfidenceSteps = auditTrail.filter(entry => entry.confidence >= 80).length; const highConfidenceSteps = auditTrail.filter(entry => entry.confidence >= 80).length;
// Group entries by phase for better organization
const groupedEntries = auditTrail.reduce((groups, entry) => { const groupedEntries = auditTrail.reduce((groups, entry) => {
if (!groups[entry.phase]) groups[entry.phase] = []; if (!groups[entry.phase]) groups[entry.phase] = [];
groups[entry.phase].push(entry); groups[entry.phase].push(entry);
@ -1048,7 +1044,6 @@ class AIQueryInterface {
second: '2-digit' second: '2-digit'
}); });
// Reuse existing grid and text utilities
return ` return `
<div class="border-l-2 pl-3 py-2 mb-2" style="border-left-color: ${confidenceColor};"> <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"> <div class="flex justify-between items-center mb-1">

View File

@ -2,7 +2,6 @@
export const AI_PROMPTS = { export const AI_PROMPTS = {
// Main tool selection prompt
toolSelection: (mode: string, userQuery: string, selectionMethod: string, maxSelectedItems: number) => { toolSelection: (mode: string, userQuery: string, selectionMethod: string, maxSelectedItems: number) => {
const modeInstruction = mode === 'workflow' const modeInstruction = mode === 'workflow'
? 'Der Benutzer möchte einen UMFASSENDEN WORKFLOW mit mehreren Tools/Methoden über verschiedene Phasen. Wählen Sie 15-25 Tools aus, die den vollständigen Untersuchungslebenszyklus abdecken.' ? 'Der Benutzer möchte einen UMFASSENDEN WORKFLOW mit mehreren Tools/Methoden über verschiedene Phasen. Wählen Sie 15-25 Tools aus, die den vollständigen Untersuchungslebenszyklus abdecken.'
@ -51,7 +50,6 @@ Antworten Sie NUR mit diesem JSON-Format:
}`; }`;
}, },
// Scenario analysis prompt
scenarioAnalysis: (isWorkflow: boolean, userQuery: string) => { scenarioAnalysis: (isWorkflow: boolean, userQuery: string) => {
const analysisType = isWorkflow ? 'forensische Szenario' : 'technische Problem'; const analysisType = isWorkflow ? 'forensische Szenario' : 'technische Problem';
const considerations = isWorkflow ? const considerations = isWorkflow ?
@ -74,7 +72,6 @@ ${considerations}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählungen oder Markdown-Formatierung. Maximum 150 Wörter.`; WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählungen oder Markdown-Formatierung. Maximum 150 Wörter.`;
}, },
// Investigation approach prompt
investigationApproach: (isWorkflow: boolean, userQuery: string) => { investigationApproach: (isWorkflow: boolean, userQuery: string) => {
const approachType = isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz'; const approachType = isWorkflow ? 'Untersuchungsansatz' : 'Lösungsansatz';
const considerations = isWorkflow ? const considerations = isWorkflow ?
@ -96,7 +93,6 @@ ${considerations}
WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 150 Wörter.`; WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdown. Maximum 150 Wörter.`;
}, },
// Critical considerations prompt
criticalConsiderations: (isWorkflow: boolean, userQuery: string) => { criticalConsiderations: (isWorkflow: boolean, userQuery: string) => {
const considerationType = isWorkflow ? 'kritische forensische Überlegungen' : 'wichtige methodische Voraussetzungen'; const considerationType = isWorkflow ? 'kritische forensische Überlegungen' : 'wichtige methodische Voraussetzungen';
const aspects = isWorkflow ? const aspects = isWorkflow ?
@ -179,7 +175,6 @@ WICHTIG:
- "pros" soll die Stärken für diese spezifische Aufgabe hervorheben`; - "pros" soll die Stärken für diese spezifische Aufgabe hervorheben`;
}, },
// Background knowledge selection prompt
backgroundKnowledgeSelection: (userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]) => { backgroundKnowledgeSelection: (userQuery: string, mode: string, selectedToolNames: string[], availableConcepts: any[]) => {
return `Wählen Sie relevante forensische Konzepte für das Verständnis der empfohlenen Methodik. return `Wählen Sie relevante forensische Konzepte für das Verständnis der empfohlenen Methodik.
@ -200,7 +195,6 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
]`; ]`;
}, },
// Final recommendations prompt
finalRecommendations: (isWorkflow: boolean, userQuery: string, selectedToolNames: string[]) => { finalRecommendations: (isWorkflow: boolean, userQuery: string, selectedToolNames: string[]) => {
const prompt = isWorkflow ? const prompt = isWorkflow ?
`Erstellen Sie eine Workflow-Empfehlung basierend auf DFIR-Prinzipien. `Erstellen Sie eine Workflow-Empfehlung basierend auf DFIR-Prinzipien.
@ -225,7 +219,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
} }
} as const; } as const;
// Type-safe prompt function with proper overloads
export function getPrompt(key: 'toolSelection', mode: string, userQuery: string, selectionMethod: string, maxSelectedItems: number): string; export function getPrompt(key: 'toolSelection', mode: string, userQuery: string, selectionMethod: string, maxSelectedItems: number): string;
export function getPrompt(key: 'scenarioAnalysis', isWorkflow: boolean, userQuery: string): string; export function getPrompt(key: 'scenarioAnalysis', isWorkflow: boolean, userQuery: string): string;
export function getPrompt(key: 'investigationApproach', isWorkflow: boolean, userQuery: string): string; export function getPrompt(key: 'investigationApproach', isWorkflow: boolean, userQuery: string): string;
@ -238,7 +231,6 @@ export function getPrompt(promptKey: keyof typeof AI_PROMPTS, ...args: any[]): s
try { try {
const promptFunction = AI_PROMPTS[promptKey]; const promptFunction = AI_PROMPTS[promptKey];
if (typeof promptFunction === 'function') { if (typeof promptFunction === 'function') {
// Use type assertion since we've validated the function exists
return (promptFunction as (...args: any[]) => string)(...args); return (promptFunction as (...args: any[]) => string)(...args);
} else { } else {
console.error(`[PROMPTS] Invalid prompt key: ${promptKey}`); console.error(`[PROMPTS] Invalid prompt key: ${promptKey}`);

View File

@ -265,20 +265,15 @@ const phases = data.phases;
return; return;
} }
// AI Button click handler
if (aiQueryBtn) { if (aiQueryBtn) {
aiQueryBtn.addEventListener('click', () => { aiQueryBtn.addEventListener('click', () => {
// Visual feedback
aiQueryBtn.classList.add('activated'); aiQueryBtn.classList.add('activated');
setTimeout(() => aiQueryBtn.classList.remove('activated'), 400); setTimeout(() => aiQueryBtn.classList.remove('activated'), 400);
// Switch to AI view
switchToView('ai'); switchToView('ai');
// Trigger existing view change system
window.dispatchEvent(new CustomEvent('viewChanged', { detail: 'ai' })); window.dispatchEvent(new CustomEvent('viewChanged', { detail: 'ai' }));
// Scroll to AI interface
if (window.scrollToElementById) { if (window.scrollToElementById) {
window.scrollToElementById('ai-interface'); window.scrollToElementById('ai-interface');
} else { } else {
@ -294,14 +289,12 @@ const phases = data.phases;
const filtersSection = document.getElementById('filters-section'); const filtersSection = document.getElementById('filters-section');
const noResults = document.getElementById('no-results'); const noResults = document.getElementById('no-results');
// Hide all views first
if (toolsGrid) toolsGrid.style.display = 'none'; if (toolsGrid) toolsGrid.style.display = 'none';
if (matrixContainer) matrixContainer.style.display = 'none'; if (matrixContainer) matrixContainer.style.display = 'none';
if (aiInterface) aiInterface.style.display = 'none'; if (aiInterface) aiInterface.style.display = 'none';
if (filtersSection) filtersSection.style.display = 'none'; if (filtersSection) filtersSection.style.display = 'none';
if (noResults) noResults.style.display = 'none'; if (noResults) noResults.style.display = 'none';
// Show selected view
switch (view) { switch (view) {
case 'grid': case 'grid':
if (toolsGrid) toolsGrid.style.display = 'block'; if (toolsGrid) toolsGrid.style.display = 'block';

View File

@ -63,17 +63,15 @@ interface AnalysisContext {
auditTrail: AuditEntry[]; auditTrail: AuditEntry[];
// Store actual similarity data from embeddings
embeddingsSimilarities: Map<string, number>; embeddingsSimilarities: Map<string, number>;
} }
interface ConfidenceMetrics { interface ConfidenceMetrics {
overall: number; // 0-100: Combined confidence score overall: number;
semanticRelevance: number; // How well tool description matches query (from embeddings) semanticRelevance: number;
taskSuitability: number; // AI-determined fitness for this specific task taskSuitability: number;
methodologicalConsistency: number; // How well different analysis steps agree uncertaintyFactors: string[];
uncertaintyFactors: string[]; // Specific reasons why this might not work strengthIndicators: string[];
strengthIndicators: string[]; // Specific reasons why this is a good choice
} }
class ImprovedMicroTaskAIPipeline { class ImprovedMicroTaskAIPipeline {
@ -102,10 +100,10 @@ class ImprovedMicroTaskAIPipeline {
}; };
private confidenceConfig: { private confidenceConfig: {
semanticWeight: number; // Weight for embeddings similarity semanticWeight: number;
suitabilityWeight: number; // Weight for AI task fit evaluation suitabilityWeight: number;
consistencyWeight: number; // Weight for cross-validation agreement consistencyWeight: number;
reliabilityWeight: number; // Weight for tool quality indicators reliabilityWeight: number;
minimumThreshold: number; minimumThreshold: number;
mediumThreshold: number; mediumThreshold: number;
highThreshold: number; highThreshold: number;
@ -143,10 +141,9 @@ class ImprovedMicroTaskAIPipeline {
retentionHours: parseInt(process.env.FORENSIC_AUDIT_RETENTION_HOURS || '72', 10) retentionHours: parseInt(process.env.FORENSIC_AUDIT_RETENTION_HOURS || '72', 10)
}; };
// Updated confidence weights - more focused on AI evaluation
this.confidenceConfig = { this.confidenceConfig = {
semanticWeight: parseFloat(process.env.CONFIDENCE_SEMANTIC_WEIGHT || '0.3'), // Embeddings similarity semanticWeight: parseFloat(process.env.CONFIDENCE_SEMANTIC_WEIGHT || '0.3'),
suitabilityWeight: parseFloat(process.env.CONFIDENCE_SUITABILITY_WEIGHT || '0.7'), // AI task fit evaluation suitabilityWeight: parseFloat(process.env.CONFIDENCE_SUITABILITY_WEIGHT || '0.7'),
consistencyWeight: 0, consistencyWeight: 0,
reliabilityWeight: 0, reliabilityWeight: 0,
minimumThreshold: parseInt(process.env.CONFIDENCE_MINIMUM_THRESHOLD || '40', 10), minimumThreshold: parseInt(process.env.CONFIDENCE_MINIMUM_THRESHOLD || '40', 10),
@ -235,7 +232,7 @@ class ImprovedMicroTaskAIPipeline {
const selectionRatio = result.selectedTools.length / candidateCount; const selectionRatio = result.selectedTools.length / candidateCount;
const hasReasoning = result.reasoning && result.reasoning.length > 50; const hasReasoning = result.reasoning && result.reasoning.length > 50;
let confidence = 60; // Base confidence let confidence = 60;
if (selectionRatio > 0.05 && selectionRatio < 0.3) confidence += 20; if (selectionRatio > 0.05 && selectionRatio < 0.3) confidence += 20;
else if (selectionRatio <= 0.05) confidence -= 10; else if (selectionRatio <= 0.05) confidence -= 10;
@ -386,7 +383,6 @@ class ImprovedMicroTaskAIPipeline {
let candidateConcepts: any[] = []; let candidateConcepts: any[] = [];
let selectionMethod = 'unknown'; let selectionMethod = 'unknown';
// Initialize embeddings similarities storage
context.embeddingsSimilarities = new Map<string, number>(); context.embeddingsSimilarities = new Map<string, number>();
if (process.env.AI_EMBEDDINGS_ENABLED === 'true') { if (process.env.AI_EMBEDDINGS_ENABLED === 'true') {
@ -409,7 +405,6 @@ class ImprovedMicroTaskAIPipeline {
console.log(`[AI PIPELINE] Embeddings found ${similarItems.length} similar items`); console.log(`[AI PIPELINE] Embeddings found ${similarItems.length} similar items`);
// Store actual similarity scores for confidence calculation
similarItems.forEach(item => { similarItems.forEach(item => {
context.embeddingsSimilarities.set(item.name, item.similarity); context.embeddingsSimilarities.set(item.name, item.similarity);
}); });
@ -707,18 +702,14 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
limitations: string[] = [] limitations: string[] = []
): ConfidenceMetrics { ): ConfidenceMetrics {
// 1. Semantic Relevance: Real embeddings similarity score
const rawSemanticRelevance = context.embeddingsSimilarities.has(tool.name) ? const rawSemanticRelevance = context.embeddingsSimilarities.has(tool.name) ?
context.embeddingsSimilarities.get(tool.name)! * 100 : 50; context.embeddingsSimilarities.get(tool.name)! * 100 : 50;
// 2. Task Suitability: Enhanced with phase-awareness for workflow mode
let enhancedTaskSuitability = taskRelevance; let enhancedTaskSuitability = taskRelevance;
if (context.mode === 'workflow') { if (context.mode === 'workflow') {
// In workflow mode, boost score if tool is well-matched to its assigned phase
const toolSelection = context.selectedTools?.find(st => st.tool.name === tool.name); const toolSelection = context.selectedTools?.find(st => st.tool.name === tool.name);
if (toolSelection && tool.phases && tool.phases.includes(toolSelection.phase)) { if (toolSelection && tool.phases && tool.phases.includes(toolSelection.phase)) {
// Boost for phase alignment (but cap at 100)
const phaseBonus = Math.min(15, 100 - taskRelevance); const phaseBonus = Math.min(15, 100 - taskRelevance);
enhancedTaskSuitability = Math.min(100, taskRelevance + phaseBonus); enhancedTaskSuitability = Math.min(100, taskRelevance + phaseBonus);
@ -726,7 +717,6 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
} }
} }
// Simple weighted combination - no artificial scaling
const overall = ( const overall = (
rawSemanticRelevance * this.confidenceConfig.semanticWeight + rawSemanticRelevance * this.confidenceConfig.semanticWeight +
enhancedTaskSuitability * this.confidenceConfig.suitabilityWeight enhancedTaskSuitability * this.confidenceConfig.suitabilityWeight
@ -747,7 +737,6 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
overall: Math.round(overall), overall: Math.round(overall),
semanticRelevance: Math.round(rawSemanticRelevance), semanticRelevance: Math.round(rawSemanticRelevance),
taskSuitability: Math.round(enhancedTaskSuitability), taskSuitability: Math.round(enhancedTaskSuitability),
methodologicalConsistency: 0,
uncertaintyFactors, uncertaintyFactors,
strengthIndicators strengthIndicators
}; };
@ -756,18 +745,15 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
private identifySpecificUncertaintyFactors(tool: any, context: AnalysisContext, limitations: string[], confidence: number): string[] { private identifySpecificUncertaintyFactors(tool: any, context: AnalysisContext, limitations: string[], confidence: number): string[] {
const factors: string[] = []; const factors: string[] = [];
// Add AI-identified limitations first (most specific)
if (limitations && limitations.length > 0) { if (limitations && limitations.length > 0) {
factors.push(...limitations.slice(0, 2)); // Limit to top 2 to leave room for others factors.push(...limitations.slice(0, 2));
} }
// Low semantic similarity
const similarity = context.embeddingsSimilarities.get(tool.name) || 0.5; const similarity = context.embeddingsSimilarities.get(tool.name) || 0.5;
if (similarity < 0.7) { if (similarity < 0.7) {
factors.push('Geringe semantische Ähnlichkeit zur Anfrage - Tool-Beschreibung passt möglicherweise nicht optimal'); factors.push('Geringe semantische Ähnlichkeit zur Anfrage - Tool-Beschreibung passt möglicherweise nicht optimal');
} }
// Skill level vs scenario complexity mismatch
if (tool.skillLevel === 'expert' && /schnell|rapid|triage|urgent|sofort/i.test(context.userQuery)) { if (tool.skillLevel === 'expert' && /schnell|rapid|triage|urgent|sofort/i.test(context.userQuery)) {
factors.push('Experten-Tool für zeitkritisches Szenario - Setup und Einarbeitung könnten zu lange dauern'); factors.push('Experten-Tool für zeitkritisches Szenario - Setup und Einarbeitung könnten zu lange dauern');
} }
@ -776,35 +762,29 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
factors.push('Einsteiger-Tool für komplexe Analyse - könnte funktionale Limitierungen haben'); factors.push('Einsteiger-Tool für komplexe Analyse - könnte funktionale Limitierungen haben');
} }
// Access and deployment concerns
if (tool.type === 'software' && !isToolHosted(tool) && tool.accessType === 'download') { if (tool.type === 'software' && !isToolHosted(tool) && tool.accessType === 'download') {
factors.push('Installation und Setup erforderlich'); factors.push('Installation und Setup erforderlich');
} }
// License restrictions
if (tool.license === 'Proprietary') { if (tool.license === 'Proprietary') {
factors.push('Kommerzielle Software - Lizenzkosten und rechtliche Beschränkungen zu beachten'); factors.push('Kommerzielle Software - Lizenzkosten und rechtliche Beschränkungen zu beachten');
} }
// Low overall confidence warning
if (confidence < 60) { if (confidence < 60) {
factors.push('Moderate Gesamtbewertung - alternative Ansätze sollten ebenfalls betrachtet werden'); factors.push('Moderate Gesamtbewertung - alternative Ansätze sollten ebenfalls betrachtet werden');
} }
return factors.slice(0, 4); // Limit to 4 most relevant factors return factors.slice(0, 4);
} }
// NEW: Identify specific strength indicators
private identifySpecificStrengthIndicators(tool: any, context: AnalysisContext, confidence: number): string[] { private identifySpecificStrengthIndicators(tool: any, context: AnalysisContext, confidence: number): string[] {
const indicators: string[] = []; const indicators: string[] = [];
// High semantic similarity
const similarity = context.embeddingsSimilarities.get(tool.name) || 0.5; const similarity = context.embeddingsSimilarities.get(tool.name) || 0.5;
if (similarity >= 0.7) { if (similarity >= 0.7) {
indicators.push('Sehr gute semantische Übereinstimmung mit Ihrer Anfrage'); indicators.push('Sehr gute semantische Übereinstimmung mit Ihrer Anfrage');
} }
// Quality indicators
if (tool.knowledgebase === true) { if (tool.knowledgebase === true) {
indicators.push('Umfassende Dokumentation und Wissensbasis verfügbar'); indicators.push('Umfassende Dokumentation und Wissensbasis verfügbar');
} }
@ -813,17 +793,15 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
indicators.push('Sofort verfügbar über gehostete Lösung - kein Setup erforderlich'); indicators.push('Sofort verfügbar über gehostete Lösung - kein Setup erforderlich');
} }
// Skill level match
if (tool.skillLevel === 'intermediate' || tool.skillLevel === 'advanced') { if (tool.skillLevel === 'intermediate' || tool.skillLevel === 'advanced') {
indicators.push('Ausgewogenes Verhältnis zwischen Funktionalität und Benutzerfreundlichkeit'); indicators.push('Ausgewogenes Verhältnis zwischen Funktionalität und Benutzerfreundlichkeit');
} }
// Method alignment
if (tool.type === 'method' && /methodik|vorgehen|prozess|ansatz/i.test(context.userQuery)) { if (tool.type === 'method' && /methodik|vorgehen|prozess|ansatz/i.test(context.userQuery)) {
indicators.push('Methodischer Ansatz passt zu Ihrer prozeduralen Anfrage'); indicators.push('Methodischer Ansatz passt zu Ihrer prozeduralen Anfrage');
} }
return indicators.slice(0, 4); // Limit to 4 most important indicators return indicators.slice(0, 4);
} }
private async analyzeScenario(context: AnalysisContext): Promise<MicroTaskResult> { private async analyzeScenario(context: AnalysisContext): Promise<MicroTaskResult> {
@ -902,11 +880,9 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
validSelections.forEach((sel: any) => { validSelections.forEach((sel: any) => {
const tool = phaseTools.find((t: any) => t.name === sel.toolName); const tool = phaseTools.find((t: any) => t.name === sel.toolName);
if (tool) { if (tool) {
// Ensure taskRelevance is a number
const taskRelevance = typeof sel.taskRelevance === 'number' ? const taskRelevance = typeof sel.taskRelevance === 'number' ?
sel.taskRelevance : parseInt(String(sel.taskRelevance)) || 70; sel.taskRelevance : parseInt(String(sel.taskRelevance)) || 70;
// Derive priority automatically from score
const priority = this.derivePriorityFromScore(taskRelevance); const priority = this.derivePriorityFromScore(taskRelevance);
this.addToolToSelection(context, tool, phase.id, priority, sel.justification, taskRelevance, sel.limitations); this.addToolToSelection(context, tool, phase.id, priority, sel.justification, taskRelevance, sel.limitations);
@ -967,7 +943,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
hasExplanation: !!evaluation.detailed_explanation, hasExplanation: !!evaluation.detailed_explanation,
hasImplementationApproach: !!evaluation.implementation_approach, hasImplementationApproach: !!evaluation.implementation_approach,
prosCount: evaluation.pros?.length || 0, prosCount: evaluation.pros?.length || 0,
limitationsCount: evaluation.limitations?.length || 0, // ← Updated field name limitationsCount: evaluation.limitations?.length || 0,
hasLimitations: Array.isArray(evaluation.limitations) && evaluation.limitations.length > 0 hasLimitations: Array.isArray(evaluation.limitations) && evaluation.limitations.length > 0
}, },
70, 70,
@ -1101,7 +1077,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
const context: AnalysisContext = { const context: AnalysisContext = {
userQuery, userQuery,
mode, mode,
filteredData: {}, // Will be populated by getIntelligentCandidates filteredData: {},
contextHistory: [], contextHistory: [],
maxContextLength: this.maxContextTokens, maxContextLength: this.maxContextTokens,
currentContextLength: 0, currentContextLength: 0,
@ -1125,8 +1101,6 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
{ auditEnabled: this.auditConfig.enabled, confidenceScoringEnabled: true } { auditEnabled: this.auditConfig.enabled, confidenceScoringEnabled: true }
); );
// MICRO-TASK SEQUENCE WITH ENHANCED CONFIDENCE TRACKING
const analysisResult = await this.analyzeScenario(context); const analysisResult = await this.analyzeScenario(context);
if (analysisResult.success) completeTasks++; else failedTasks++; if (analysisResult.success) completeTasks++; else failedTasks++;
await this.delay(this.microTaskDelay); await this.delay(this.microTaskDelay);
@ -1234,7 +1208,6 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
components: { components: {
semantic: confidence.semanticRelevance, semantic: confidence.semanticRelevance,
suitability: confidence.taskSuitability, suitability: confidence.taskSuitability,
consistency: confidence.methodologicalConsistency
} }
}, },
confidence.overall, confidence.overall,
@ -1286,7 +1259,7 @@ ${JSON.stringify(conceptsToSend, null, 2)}`;
detailed_explanation: st.tool.evaluation?.detailed_explanation || '', detailed_explanation: st.tool.evaluation?.detailed_explanation || '',
implementation_approach: st.tool.evaluation?.implementation_approach || '', implementation_approach: st.tool.evaluation?.implementation_approach || '',
pros: st.tool.evaluation?.pros || [], pros: st.tool.evaluation?.pros || [],
cons: st.tool.evaluation?.limitations || [], // ← FIXED: Use limitations as cons for display cons: st.tool.evaluation?.limitations || [],
alternatives: st.tool.evaluation?.alternatives || '', alternatives: st.tool.evaluation?.alternatives || '',
confidence: confidence, confidence: confidence,
recommendationStrength: confidence.overall >= this.confidenceConfig.highThreshold ? 'strong' : recommendationStrength: confidence.overall >= this.confidenceConfig.highThreshold ? 'strong' :

View File

@ -31,7 +31,7 @@ interface SimilarityResult extends EmbeddingData {
class EmbeddingsService { class EmbeddingsService {
private embeddings: EmbeddingData[] = []; private embeddings: EmbeddingData[] = [];
private isInitialized = false; private isInitialized = false;
private initializationPromise: Promise<void> | null = null; // ADD THIS LINE private initializationPromise: Promise<void> | null = null;
private readonly embeddingsPath = path.join(process.cwd(), 'data', 'embeddings.json'); private readonly embeddingsPath = path.join(process.cwd(), 'data', 'embeddings.json');
private readonly batchSize: number; private readonly batchSize: number;
private readonly batchDelay: number; private readonly batchDelay: number;
@ -43,24 +43,19 @@ class EmbeddingsService {
this.batchDelay = parseInt(process.env.AI_EMBEDDINGS_BATCH_DELAY_MS || '1000', 10); this.batchDelay = parseInt(process.env.AI_EMBEDDINGS_BATCH_DELAY_MS || '1000', 10);
} }
// REPLACE the existing initialize method with this:
async initialize(): Promise<void> { async initialize(): Promise<void> {
// If initialization is already in progress, wait for it
if (this.initializationPromise) { if (this.initializationPromise) {
return this.initializationPromise; return this.initializationPromise;
} }
// If already initialized, return immediately
if (this.isInitialized) { if (this.isInitialized) {
return Promise.resolve(); return Promise.resolve();
} }
// Start initialization and store the promise
this.initializationPromise = this.performInitialization(); this.initializationPromise = this.performInitialization();
return this.initializationPromise; return this.initializationPromise;
} }
// ADD THIS NEW METHOD:
private async performInitialization(): Promise<void> { private async performInitialization(): Promise<void> {
if (!this.enabled) { if (!this.enabled) {
console.log('[EMBEDDINGS] Embeddings disabled, skipping initialization'); console.log('[EMBEDDINGS] Embeddings disabled, skipping initialization');
@ -70,13 +65,11 @@ class EmbeddingsService {
try { try {
console.log('[EMBEDDINGS] Initializing embeddings system...'); console.log('[EMBEDDINGS] Initializing embeddings system...');
// Create data directory if it doesn't exist
await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true }); await fs.mkdir(path.dirname(this.embeddingsPath), { recursive: true });
const toolsData = await getCompressedToolsDataForAI(); const toolsData = await getCompressedToolsDataForAI();
const currentDataHash = this.hashData(toolsData); const currentDataHash = this.hashData(toolsData);
// Try to load existing embeddings
const existingEmbeddings = await this.loadEmbeddings(); const existingEmbeddings = await this.loadEmbeddings();
if (existingEmbeddings && existingEmbeddings.version === currentDataHash) { if (existingEmbeddings && existingEmbeddings.version === currentDataHash) {
@ -336,12 +329,10 @@ class EmbeddingsService {
// Global instance
const embeddingsService = new EmbeddingsService(); const embeddingsService = new EmbeddingsService();
export { embeddingsService, type EmbeddingData, type SimilarityResult }; export { embeddingsService, type EmbeddingData, type SimilarityResult };
// Auto-initialize on import in server environment
if (typeof window === 'undefined' && process.env.NODE_ENV !== 'test') { if (typeof window === 'undefined' && process.env.NODE_ENV !== 'test') {
embeddingsService.initialize().catch(error => { embeddingsService.initialize().catch(error => {
console.error('[EMBEDDINGS] Auto-initialization failed:', error); console.error('[EMBEDDINGS] Auto-initialization failed:', error);

View File

@ -96,7 +96,6 @@ class RateLimitedQueue {
this.tasks.push(queuedTask); this.tasks.push(queuedTask);
// Kick the processor soon.
setTimeout(() => { setTimeout(() => {
this.processQueue(); this.processQueue();
}, 100); }, 100);
@ -170,7 +169,7 @@ class RateLimitedQueue {
.filter((t) => t.status === "queued") .filter((t) => t.status === "queued")
.sort((a, b) => a.addedAt - b.addedAt)[0]; .sort((a, b) => a.addedAt - b.addedAt)[0];
if (!nextTask) break; // No more work if (!nextTask) break;
nextTask.status = "processing"; nextTask.status = "processing";
nextTask.startedAt = Date.now(); nextTask.startedAt = Date.now();