Progress
This commit is contained in:
parent
4b0d208ef5
commit
a0955c2e58
@ -15,7 +15,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
|
||||
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
|
||||
</svg>
|
||||
KI-gestützte Workflow-Empfehlungen
|
||||
Forensic AI
|
||||
</h2>
|
||||
<p id="ai-description" class="text-muted" style="max-width: 700px; margin: 0 auto; line-height: 1.6;">
|
||||
Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Workflow-Empfehlungen
|
||||
@ -169,16 +169,16 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
<!-- Micro-task Progress -->
|
||||
<div id="micro-task-progress" class="micro-task-progress hidden">
|
||||
<div class="micro-task-header">
|
||||
<span class="micro-task-label">🔬 Micro-Task Analyse</span>
|
||||
<span class="micro-task-label">🔬 micro-Agent-Analysis</span>
|
||||
<span id="micro-task-counter" class="micro-task-counter">1/6</span>
|
||||
</div>
|
||||
<div class="micro-task-steps">
|
||||
<div class="micro-step" data-step="scenario">📋 Szenario</div>
|
||||
<div class="micro-step" data-step="approach">🎯 Ansatz</div>
|
||||
<div class="micro-step" data-step="considerations">⚠️ Kritisches</div>
|
||||
<div class="micro-step" data-step="tools">🔧 Tools</div>
|
||||
<div class="micro-step" data-step="knowledge">📚 Wissen</div>
|
||||
<div class="micro-step" data-step="final">✅ Final</div>
|
||||
<div class="micro-step" data-step="scenario">📋 Problemanalyse</div>
|
||||
<div class="micro-step" data-step="approach">🎯 Ermittlungsansatz</div>
|
||||
<div class="micro-step" data-step="considerations">⚠️ Herausforderungen</div>
|
||||
<div class="micro-step" data-step="tools">🔧 Methoden</div>
|
||||
<div class="micro-step" data-step="knowledge">📚 Evaluation</div>
|
||||
<div class="micro-step" data-step="final">✅ Audit-Trail</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -292,13 +292,13 @@ class AIQueryInterface {
|
||||
return {
|
||||
workflow: {
|
||||
placeholder: "Beschreiben Sie Ihr forensisches Szenario... z.B. 'Verdacht auf Ransomware-Angriff auf Windows-Domänencontroller'",
|
||||
description: "Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Workflow-Empfehlungen.",
|
||||
description: "Beschreiben Sie Ihre Untersuchungssituation und erhalten Empfehlungen für alle Phasen der Untersuchung.",
|
||||
submitText: "Empfehlungen generieren",
|
||||
loadingText: "Analysiere Szenario und generiere Empfehlungen..."
|
||||
},
|
||||
tool: {
|
||||
placeholder: "Beschreiben Sie Ihr Problem... z.B. 'Analyse von Android-Backups mit WhatsApp-Nachrichten'",
|
||||
description: "Beschreiben Sie Ihr Problem und erhalten Sie 1-3 gezielt passende Empfehlungen.",
|
||||
description: "Beschreiben Sie Ihre Untersuchungssituation und erhalten Empfehlungen für eine spezifische Aufgabenstellung.",
|
||||
submitText: "Empfehlungen finden",
|
||||
loadingText: "Analysiere Anforderungen und suche passende Methode..."
|
||||
}
|
||||
@ -706,7 +706,7 @@ class AIQueryInterface {
|
||||
|
||||
const html = `
|
||||
<div class="workflow-container">
|
||||
${this.renderHeader('Empfohlener DFIR-Workflow', originalQuery)}
|
||||
${this.renderHeader('Untersuchungsansatz', originalQuery)}
|
||||
${this.renderContextualAnalysis(recommendation, 'workflow')}
|
||||
${this.renderBackgroundKnowledge(recommendation.background_knowledge)}
|
||||
${this.renderWorkflowPhases(toolsByPhase, phaseOrder, phaseNames)}
|
||||
@ -721,7 +721,7 @@ class AIQueryInterface {
|
||||
displayToolResults(recommendation, originalQuery) {
|
||||
const html = `
|
||||
<div class="tool-results-container">
|
||||
${this.renderHeader('Passende Empfehlungen', originalQuery)}
|
||||
${this.renderHeader('Handlungsempfehlung', originalQuery)}
|
||||
${this.renderContextualAnalysis(recommendation, 'tool')}
|
||||
${this.renderBackgroundKnowledge(recommendation.background_knowledge)}
|
||||
${this.renderToolRecommendations(recommendation.recommended_tools)}
|
||||
|
@ -2015,6 +2015,7 @@ input[type="checkbox"] {
|
||||
gap: 1rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.phase-header {
|
||||
|
@ -357,6 +357,17 @@ class ImprovedMicroTaskAIPipeline {
|
||||
let candidateConcepts: any[] = [];
|
||||
let selectionMethod = 'unknown';
|
||||
|
||||
// WAIT for embeddings initialization if embeddings are enabled
|
||||
if (process.env.AI_EMBEDDINGS_ENABLED === 'true') {
|
||||
try {
|
||||
console.log('[AI PIPELINE] Waiting for embeddings initialization...');
|
||||
await embeddingsService.waitForInitialization();
|
||||
console.log('[AI PIPELINE] Embeddings ready, proceeding with similarity search');
|
||||
} catch (error) {
|
||||
console.error('[AI PIPELINE] Embeddings initialization failed, falling back to full dataset:', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (embeddingsService.isEnabled()) {
|
||||
const embeddingsStart = Date.now();
|
||||
const similarItems = await embeddingsService.findSimilar(
|
||||
@ -425,7 +436,7 @@ class ImprovedMicroTaskAIPipeline {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(`[AI PIPELINE] Embeddings disabled, using full dataset`);
|
||||
console.log(`[AI PIPELINE] Embeddings disabled or not ready, using full dataset`);
|
||||
candidateTools = toolsData.tools;
|
||||
candidateConcepts = toolsData.concepts;
|
||||
selectionMethod = 'full_dataset';
|
||||
|
@ -31,6 +31,7 @@ interface SimilarityResult extends EmbeddingData {
|
||||
class EmbeddingsService {
|
||||
private embeddings: EmbeddingData[] = [];
|
||||
private isInitialized = false;
|
||||
private initializationPromise: Promise<void> | null = null; // ADD THIS LINE
|
||||
private readonly embeddingsPath = path.join(process.cwd(), 'data', 'embeddings.json');
|
||||
private readonly batchSize: number;
|
||||
private readonly batchDelay: number;
|
||||
@ -42,7 +43,25 @@ class EmbeddingsService {
|
||||
this.batchDelay = parseInt(process.env.AI_EMBEDDINGS_BATCH_DELAY_MS || '1000', 10);
|
||||
}
|
||||
|
||||
// REPLACE the existing initialize method with this:
|
||||
async initialize(): Promise<void> {
|
||||
// If initialization is already in progress, wait for it
|
||||
if (this.initializationPromise) {
|
||||
return this.initializationPromise;
|
||||
}
|
||||
|
||||
// If already initialized, return immediately
|
||||
if (this.isInitialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Start initialization and store the promise
|
||||
this.initializationPromise = this.performInitialization();
|
||||
return this.initializationPromise;
|
||||
}
|
||||
|
||||
// ADD THIS NEW METHOD:
|
||||
private async performInitialization(): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
console.log('[EMBEDDINGS] Embeddings disabled, skipping initialization');
|
||||
return;
|
||||
@ -74,9 +93,29 @@ class EmbeddingsService {
|
||||
} catch (error) {
|
||||
console.error('[EMBEDDINGS] Failed to initialize:', error);
|
||||
this.isInitialized = false;
|
||||
throw error;
|
||||
} finally {
|
||||
this.initializationPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
async waitForInitialization(): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.isInitialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.initializationPromise) {
|
||||
await this.initializationPromise;
|
||||
return;
|
||||
}
|
||||
|
||||
return this.initialize();
|
||||
}
|
||||
|
||||
private hashData(data: any): string {
|
||||
return Buffer.from(JSON.stringify(data)).toString('base64').slice(0, 32);
|
||||
}
|
||||
@ -127,7 +166,6 @@ class EmbeddingsService {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// API key is optional for Ollama but required for Mistral/OpenAI
|
||||
if (apiKey) {
|
||||
headers['Authorization'] = `Bearer ${apiKey}`;
|
||||
}
|
||||
@ -148,12 +186,10 @@ class EmbeddingsService {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Detect Ollama format
|
||||
if (Array.isArray(data.embeddings)) {
|
||||
return data.embeddings;
|
||||
}
|
||||
|
||||
// Detect OpenAI/Mistral format
|
||||
if (Array.isArray(data.data)) {
|
||||
return data.data.map((item: any) => item.embedding);
|
||||
}
|
||||
@ -170,7 +206,6 @@ class EmbeddingsService {
|
||||
const contents = allItems.map(item => this.createContentString(item));
|
||||
this.embeddings = [];
|
||||
|
||||
// Process in batches to respect rate limits
|
||||
for (let i = 0; i < contents.length; i += this.batchSize) {
|
||||
const batch = contents.slice(i, i + this.batchSize);
|
||||
const batchItems = allItems.slice(i, i + this.batchSize);
|
||||
@ -198,7 +233,6 @@ class EmbeddingsService {
|
||||
});
|
||||
});
|
||||
|
||||
// Rate limiting delay between batches
|
||||
if (i + this.batchSize < contents.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, this.batchDelay));
|
||||
}
|
||||
@ -213,7 +247,6 @@ class EmbeddingsService {
|
||||
}
|
||||
|
||||
public async embedText(text: string): Promise<number[]> {
|
||||
// Re‑use the private batch helper to avoid auth duplication
|
||||
const [embedding] = await this.generateEmbeddingsBatch([text.toLowerCase()]);
|
||||
return embedding;
|
||||
}
|
||||
@ -239,25 +272,21 @@ class EmbeddingsService {
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate embedding for query
|
||||
const queryEmbeddings = await this.generateEmbeddingsBatch([query.toLowerCase()]);
|
||||
const queryEmbedding = queryEmbeddings[0];
|
||||
|
||||
console.log(`[EMBEDDINGS] Computing similarities for ${this.embeddings.length} items`);
|
||||
|
||||
// Calculate similarities - properly typed
|
||||
const similarities: SimilarityResult[] = this.embeddings.map(item => ({
|
||||
...item,
|
||||
similarity: this.cosineSimilarity(queryEmbedding, item.embedding)
|
||||
}));
|
||||
|
||||
// Filter by threshold and sort by similarity (descending - highest first)
|
||||
const results = similarities
|
||||
.filter(item => item.similarity >= threshold)
|
||||
.sort((a, b) => b.similarity - a.similarity) // CRITICAL: Ensure descending order
|
||||
.sort((a, b) => b.similarity - a.similarity)
|
||||
.slice(0, maxResults);
|
||||
|
||||
// ENHANCED: Verify ordering is correct
|
||||
const orderingValid = results.every((item, index) => {
|
||||
if (index === 0) return true;
|
||||
return item.similarity <= results[index - 1].similarity;
|
||||
@ -270,15 +299,13 @@ class EmbeddingsService {
|
||||
});
|
||||
}
|
||||
|
||||
// ENHANCED: Log top results for debugging
|
||||
console.log(`[EMBEDDINGS] Found ${results.length} similar items (threshold: ${threshold})`);
|
||||
if (results.length > 0) {
|
||||
console.log('[EMBEDDINGS] Top 5 similarity matches:');
|
||||
results.slice(0, 5).forEach((item, idx) => {
|
||||
console.log('[EMBEDDINGS] Top 10 similarity matches:');
|
||||
results.slice(0, 10).forEach((item, idx) => {
|
||||
console.log(` ${idx + 1}. ${item.name} (${item.type}) = ${item.similarity.toFixed(4)}`);
|
||||
});
|
||||
|
||||
// Verify first result is indeed the highest
|
||||
const topSimilarity = results[0].similarity;
|
||||
const hasHigherSimilarity = results.some(item => item.similarity > topSimilarity);
|
||||
if (hasHigherSimilarity) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user