updates to style
This commit is contained in:
		
							parent
							
								
									89583664d6
								
							
						
					
					
						commit
						eec6af739b
					
				@ -2,6 +2,9 @@
 | 
			
		||||
 | 
			
		||||
Ein kuratiertes Verzeichnis für digitale Forensik- und Incident-Response-Tools, entwickelt für die Seminargruppe CC24-w1.
 | 
			
		||||
 | 
			
		||||
*DISCLAIMER:*
 | 
			
		||||
Hier wurde Exzessives Vibe-Coding verwendet. Die Auswahl der Software ist aber kuratiert.
 | 
			
		||||
 | 
			
		||||
## 🎯 Projektübersicht
 | 
			
		||||
 | 
			
		||||
CC24-Hub ist eine statische Website, die eine strukturierte Übersicht über bewährte DFIR-Tools bietet. Das Projekt orientiert sich am NIST-Framework (SP 800-86) und kategorisiert Tools nach forensischen Domänen und Untersuchungsphasen.
 | 
			
		||||
 | 
			
		||||
@ -1,598 +1,381 @@
 | 
			
		||||
---
 | 
			
		||||
// src/components/AIQueryInterface.astro
 | 
			
		||||
import { promises as fs } from 'fs';
 | 
			
		||||
import { load } from 'js-yaml';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
// Load tools data for tool details
 | 
			
		||||
// Load tools data for validation
 | 
			
		||||
const yamlPath = path.join(process.cwd(), 'src/data/tools.yaml');
 | 
			
		||||
const yamlContent = await fs.readFile(yamlPath, 'utf8');
 | 
			
		||||
const data = load(yamlContent) as any;
 | 
			
		||||
const tools = data.tools;
 | 
			
		||||
const phases = data.phases.filter((phase: any) => phase.id !== 'collaboration-general');
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<div id="ai-interface" class="ai-interface" style="display: none;">
 | 
			
		||||
  <!-- AI Query Input Section -->
 | 
			
		||||
<!-- AI Query Interface -->
 | 
			
		||||
<section id="ai-interface" class="ai-interface" style="display: none;">
 | 
			
		||||
  <div class="ai-query-section">
 | 
			
		||||
    <div style="text-align: center; margin-bottom: 2rem;">
 | 
			
		||||
      <h2 style="margin-bottom: 1rem; color: var(--color-primary);">
 | 
			
		||||
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: text-top;">
 | 
			
		||||
        <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.75rem; vertical-align: middle;">
 | 
			
		||||
          <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 Tool-Empfehlungen
 | 
			
		||||
      </h2>
 | 
			
		||||
      <p class="text-muted" style="max-width: 600px; margin: 0 auto; line-height: 1.6;">
 | 
			
		||||
        Beschreiben Sie Ihr Ermittlungsszenario auf Deutsch oder Englisch und erhalten Sie 
 | 
			
		||||
        personalisierte Tool-Empfehlungen basierend auf dem NIST-Framework.
 | 
			
		||||
      <p class="text-muted" style="max-width: 700px; margin: 0 auto; line-height: 1.6;">
 | 
			
		||||
        Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Tool-Empfehlungen 
 | 
			
		||||
        basierend auf bewährten DFIR-Workflows und der verfügbaren Software-Datenbank.
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="ai-input-container" style="max-width: 800px; margin: 0 auto 2rem;">
 | 
			
		||||
      <div style="position: relative;">
 | 
			
		||||
        <textarea 
 | 
			
		||||
          id="ai-query-input"
 | 
			
		||||
          placeholder="Beschreiben Sie Ihr Ermittlungsszenario... 
 | 
			
		||||
 | 
			
		||||
Beispiele:
 | 
			
		||||
• Ich untersuche einen Ransomware-Angriff auf Windows-Systeme und brauche Tools für Memory-Analyse
 | 
			
		||||
• We need to investigate suspicious network traffic from an IoT device
 | 
			
		||||
• Ein Smartphone wurde kompromittiert - welche Tools für Mobile Forensik?"
 | 
			
		||||
          style="width: 100%; min-height: 120px; padding: 1rem; border: 2px solid var(--color-border); border-radius: 0.5rem; resize: vertical; font-family: inherit; font-size: 0.875rem; line-height: 1.5;"
 | 
			
		||||
          maxlength="2000"
 | 
			
		||||
        ></textarea>
 | 
			
		||||
        <div style="position: absolute; bottom: 0.75rem; right: 0.75rem; font-size: 0.75rem; color: var(--color-text-secondary);">
 | 
			
		||||
          <span id="char-count">0</span>/2000
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    <div class="ai-input-container" style="max-width: 800px; margin: 0 auto;">
 | 
			
		||||
      <textarea 
 | 
			
		||||
        id="ai-query-input" 
 | 
			
		||||
        placeholder="Beschreiben Sie Ihr forensisches Szenario... z.B. 'Verdacht auf Ransomware-Angriff auf Windows-Domänencontroller mit verschlüsselten Dateien und verdächtigen Netzwerkverbindungen'"
 | 
			
		||||
        style="min-height: 120px; resize: vertical; font-size: 0.9375rem; line-height: 1.5;"
 | 
			
		||||
        maxlength="2000"
 | 
			
		||||
      ></textarea>
 | 
			
		||||
      
 | 
			
		||||
      <div style="display: flex; gap: 1rem; margin-top: 1rem; justify-content: center;">
 | 
			
		||||
        <button id="ai-submit-btn" class="btn btn-primary" style="padding: 0.75rem 2rem;">
 | 
			
		||||
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
            <path d="M22 2L11 13"/>
 | 
			
		||||
            <path d="M22 2l-7 20-4-9-9-4 20-7z"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          Empfehlungen abrufen
 | 
			
		||||
        </button>
 | 
			
		||||
        <button id="ai-clear-btn" class="btn btn-secondary">
 | 
			
		||||
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
            <path d="M3 6h18"/>
 | 
			
		||||
            <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/>
 | 
			
		||||
            <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          Zurücksetzen
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Loading State -->
 | 
			
		||||
    <div id="ai-loading" class="ai-loading" style="display: none; text-align: center; padding: 2rem;">
 | 
			
		||||
      <div class="loading-spinner" style="margin-bottom: 1rem;">
 | 
			
		||||
        <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2" style="animation: spin 1s linear infinite;">
 | 
			
		||||
          <path d="M12 2v4"/>
 | 
			
		||||
          <path d="M12 18v4"/>
 | 
			
		||||
          <path d="M4.93 4.93l2.83 2.83"/>
 | 
			
		||||
          <path d="M16.24 16.24l2.83 2.83"/>
 | 
			
		||||
          <path d="M2 12h4"/>
 | 
			
		||||
          <path d="M18 12h4"/>
 | 
			
		||||
          <path d="M4.93 19.07l2.83-2.83"/>
 | 
			
		||||
          <path d="M16.24 7.76l2.83-2.83"/>
 | 
			
		||||
        </svg>
 | 
			
		||||
      </div>
 | 
			
		||||
      <h3 style="margin-bottom: 0.5rem; color: var(--color-primary);">KI analysiert Ihr Szenario...</h3>
 | 
			
		||||
      <p class="text-muted">Dies kann einige Sekunden dauern.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Error State -->
 | 
			
		||||
    <div id="ai-error" class="ai-error" style="display: none; text-align: center; padding: 2rem;">
 | 
			
		||||
      <div style="margin-bottom: 1rem;">
 | 
			
		||||
        <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-error)" stroke-width="2">
 | 
			
		||||
          <circle cx="12" cy="12" r="10"/>
 | 
			
		||||
          <line x1="15" y1="9" x2="9" y2="15"/>
 | 
			
		||||
          <line x1="9" y1="9" x2="15" y2="15"/>
 | 
			
		||||
        </svg>
 | 
			
		||||
      </div>
 | 
			
		||||
      <h3 style="margin-bottom: 0.5rem; color: var(--color-error);">Fehler bei der Analyse</h3>
 | 
			
		||||
      <p class="text-muted" id="ai-error-message">Ein unerwarteter Fehler ist aufgetreten.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <!-- AI Results Section -->
 | 
			
		||||
  <div id="ai-results" class="ai-results" style="display: none;">
 | 
			
		||||
    <!-- Scenario Analysis -->
 | 
			
		||||
    <div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-primary);">
 | 
			
		||||
      <h3 style="margin-bottom: 1rem; color: var(--color-primary);">
 | 
			
		||||
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
          <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>
 | 
			
		||||
        Szenario-Analyse
 | 
			
		||||
      </h3>
 | 
			
		||||
      <div id="scenario-analysis" style="line-height: 1.7;"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Workflow Visualization -->
 | 
			
		||||
    <div style="margin-bottom: 2rem;">
 | 
			
		||||
      <h3 style="margin-bottom: 1.5rem; text-align: center; color: var(--color-text);">
 | 
			
		||||
        Empfohlener DFIR-Workflow
 | 
			
		||||
      </h3>
 | 
			
		||||
      
 | 
			
		||||
      <!-- Phase Flow -->
 | 
			
		||||
      <div class="workflow-container">
 | 
			
		||||
        {phases.map((phase: any, index: number) => (
 | 
			
		||||
          <div class="workflow-phase" data-phase={phase.id}>
 | 
			
		||||
            <div class="phase-header">
 | 
			
		||||
              <div class="phase-number">{index + 1}</div>
 | 
			
		||||
              <div class="phase-info">
 | 
			
		||||
                <h4 class="phase-title">{phase.name}</h4>
 | 
			
		||||
                <div class="phase-tools" id={`phase-tools-${phase.id}`}>
 | 
			
		||||
                  <!-- Tools will be populated by JavaScript -->
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            {index < phases.length - 1 && (
 | 
			
		||||
              <div class="workflow-arrow">
 | 
			
		||||
                <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2">
 | 
			
		||||
                  <path d="M5 12h14"/>
 | 
			
		||||
                  <path d="M12 5l7 7-7 7"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
          </div>
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Additional Information -->
 | 
			
		||||
    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <h4 style="margin-bottom: 1rem; color: var(--color-accent);">
 | 
			
		||||
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
            <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          Workflow-Empfehlung
 | 
			
		||||
        </h4>
 | 
			
		||||
        <div id="workflow-suggestion" style="line-height: 1.6;"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
      
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <h4 style="margin-bottom: 1rem; color: var(--color-warning);">
 | 
			
		||||
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
      <!-- Privacy Notice -->
 | 
			
		||||
      <div style="margin-top: 0.5rem; margin-bottom: 1rem;">
 | 
			
		||||
        <p style="font-size: 0.75rem; color: var(--color-text-secondary); text-align: center; line-height: 1.0;">
 | 
			
		||||
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
 | 
			
		||||
            <circle cx="12" cy="12" r="10"/>
 | 
			
		||||
            <path d="M12 16v-4"/>
 | 
			
		||||
            <path d="M12 8h.01"/>
 | 
			
		||||
            <line x1="12" y1="8" x2="12" y2="12"/>
 | 
			
		||||
            <line x1="12" y1="16" x2="12.01" y2="16"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          Wichtige Hinweise
 | 
			
		||||
        </h4>
 | 
			
		||||
        <div id="additional-notes" style="line-height: 1.6;"></div>
 | 
			
		||||
          Die Anfrage wird an mistral.ai übertragen und unterliegt deren 
 | 
			
		||||
          <a href="https://mistral.ai/privacy-policy/" target="_blank" rel="noopener noreferrer" style="color: var(--color-primary); text-decoration: underline;">Datenschutzrichtlinien</a>.
 | 
			
		||||
           Eine typische Anfrage kostet mich $0.0008.
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
      
 | 
			
		||||
      <div style="display: flex; justify-content: center; gap: 1rem;">
 | 
			
		||||
        <button id="ai-submit-btn" class="btn btn-accent" style="padding: 0.75rem 2rem;">
 | 
			
		||||
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
            <path d="M14.828 14.828a4 4 0 0 1-5.656 0"/>
 | 
			
		||||
            <path d="M9 9a3 3 0 1 1 6 0c0 .749-.269 1.433-.73 1.96L11 14v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1l-3.27-3.04A3 3 0 0 1 5 9a3 3 0 0 1 6 0"/>
 | 
			
		||||
            <path d="M12 17h.01"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          Empfehlungen generieren
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
    <!-- New Query Button -->
 | 
			
		||||
    <div style="text-align: center; margin-top: 2rem;">
 | 
			
		||||
      <button id="new-query-btn" class="btn btn-secondary">
 | 
			
		||||
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
          <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
 | 
			
		||||
          <path d="M21 3v5h-5"/>
 | 
			
		||||
          <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
 | 
			
		||||
          <path d="M3 21v-5h5"/>
 | 
			
		||||
        </svg>
 | 
			
		||||
        Neue Anfrage starten
 | 
			
		||||
      </button>
 | 
			
		||||
  <!-- Loading State -->
 | 
			
		||||
  <div id="ai-loading" class="ai-loading" style="display: none; text-align: center; padding: 2rem;">
 | 
			
		||||
    <div style="display: inline-block; margin-bottom: 1rem;">
 | 
			
		||||
      <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2" style="animation: pulse 2s ease-in-out infinite;">
 | 
			
		||||
        <path d="M14.828 14.828a4 4 0 0 1-5.656 0"/>
 | 
			
		||||
        <path d="M9 9a3 3 0 1 1 6 0c0 .749-.269 1.433-.73 1.96L11 14v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1l-3.27-3.04A3 3 0 0 1 5 9a3 3 0 0 1 6 0"/>
 | 
			
		||||
        <path d="M12 17h.01"/>
 | 
			
		||||
      </svg>
 | 
			
		||||
    </div>
 | 
			
		||||
    <p style="color: var(--color-text-secondary);">Analysiere Szenario und generiere Empfehlungen...</p>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <!-- Error State -->
 | 
			
		||||
  <div id="ai-error" class="ai-error" style="display: none; text-align: center; padding: 2rem;">
 | 
			
		||||
    <div style="background-color: var(--color-error); color: white; padding: 1rem; border-radius: 0.5rem; max-width: 600px; margin: 0 auto;">
 | 
			
		||||
      <h3 style="margin-bottom: 0.5rem;">Fehler bei der KI-Anfrage</h3>
 | 
			
		||||
      <p id="ai-error-message" style="margin: 0;">Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
/* Loading spinner animation */
 | 
			
		||||
@keyframes spin {
 | 
			
		||||
  from { transform: rotate(0deg); }
 | 
			
		||||
  to { transform: rotate(360deg); }
 | 
			
		||||
}
 | 
			
		||||
  <!-- AI Results -->
 | 
			
		||||
  <div id="ai-results" class="ai-results" style="display: none;">
 | 
			
		||||
    <!-- Results will be populated here by JavaScript -->
 | 
			
		||||
  </div>
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
/* Workflow styling */
 | 
			
		||||
.workflow-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  gap: 1rem;
 | 
			
		||||
  max-width: 900px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.workflow-phase {
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.phase-header {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: flex-start;
 | 
			
		||||
  gap: 1rem;
 | 
			
		||||
  padding: 1.5rem;
 | 
			
		||||
  background-color: var(--color-bg);
 | 
			
		||||
  border: 2px solid var(--color-border);
 | 
			
		||||
  border-radius: 0.75rem;
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.phase-header:hover {
 | 
			
		||||
  border-color: var(--color-primary);
 | 
			
		||||
  box-shadow: var(--shadow-md);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.phase-number {
 | 
			
		||||
  background-color: var(--color-primary);
 | 
			
		||||
  color: white;
 | 
			
		||||
  width: 2.5rem;
 | 
			
		||||
  height: 2.5rem;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  font-size: 1.125rem;
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.phase-info {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  min-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.phase-title {
 | 
			
		||||
  margin: 0 0 1rem 0;
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
  font-size: 1.25rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.phase-tools {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  gap: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.workflow-arrow {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  margin: 0.5rem 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Tool recommendation cards */
 | 
			
		||||
.tool-recommendation {
 | 
			
		||||
  background-color: var(--color-bg-secondary);
 | 
			
		||||
  border: 1px solid var(--color-border);
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
  padding: 1rem;
 | 
			
		||||
  transition: all 0.2s ease;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-recommendation:hover {
 | 
			
		||||
  border-color: var(--color-primary);
 | 
			
		||||
  box-shadow: var(--shadow-sm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-recommendation.hosted {
 | 
			
		||||
  background-color: var(--color-hosted-bg);
 | 
			
		||||
  border-color: var(--color-hosted);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-recommendation.oss {
 | 
			
		||||
  background-color: var(--color-oss-bg);
 | 
			
		||||
  border-color: var(--color-oss);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-header {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: start;
 | 
			
		||||
  margin-bottom: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-name {
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  color: var(--color-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-priority {
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
  padding: 0.25rem 0.5rem;
 | 
			
		||||
  border-radius: 1rem;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-priority.high {
 | 
			
		||||
  background-color: var(--color-error);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-priority.medium {
 | 
			
		||||
  background-color: var(--color-warning);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-priority.low {
 | 
			
		||||
  background-color: var(--color-accent);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-justification {
 | 
			
		||||
  font-size: 0.875rem;
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
  margin-bottom: 0.75rem;
 | 
			
		||||
  font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-rec-metadata {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  gap: 0.5rem;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 0.75rem;
 | 
			
		||||
  color: var(--color-text-secondary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Responsive design */
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
  .workflow-container {
 | 
			
		||||
    gap: 0.75rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .phase-header {
 | 
			
		||||
    padding: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .phase-number {
 | 
			
		||||
    width: 2rem;
 | 
			
		||||
    height: 2rem;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .phase-title {
 | 
			
		||||
    font-size: 1.125rem;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .tool-rec-header {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 0.5rem;
 | 
			
		||||
    align-items: start;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script define:vars={{ toolsData: tools }}>
 | 
			
		||||
// Global tool data for quick lookup
 | 
			
		||||
window.aiToolsData = toolsData;
 | 
			
		||||
// Store AI session results
 | 
			
		||||
window.aiSessionResults = null;
 | 
			
		||||
 | 
			
		||||
// Authentication check function
 | 
			
		||||
async function checkAuthentication() {
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await fetch('/api/auth/status');
 | 
			
		||||
    const data = await response.json();
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: data.authenticated,
 | 
			
		||||
      authRequired: data.authRequired
 | 
			
		||||
    };
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Auth check failed:', error);
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: false,
 | 
			
		||||
      authRequired: true
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Initialize AI interface
 | 
			
		||||
<script define:vars={{ tools }}>
 | 
			
		||||
document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
  const queryInput = document.getElementById('ai-query-input');
 | 
			
		||||
  const submitBtn = document.getElementById('ai-submit-btn');
 | 
			
		||||
  const clearBtn = document.getElementById('ai-clear-btn');
 | 
			
		||||
  const charCount = document.getElementById('char-count');
 | 
			
		||||
  const loadingEl = document.getElementById('ai-loading');
 | 
			
		||||
  const errorEl = document.getElementById('ai-error');
 | 
			
		||||
  const resultsEl = document.getElementById('ai-results');
 | 
			
		||||
  const newQueryBtn = document.getElementById('new-query-btn');
 | 
			
		||||
  const aiInterface = document.getElementById('ai-interface');
 | 
			
		||||
  const aiInput = document.getElementById('ai-query-input');
 | 
			
		||||
  const aiSubmitBtn = document.getElementById('ai-submit-btn');
 | 
			
		||||
  const aiLoading = document.getElementById('ai-loading');
 | 
			
		||||
  const aiError = document.getElementById('ai-error');
 | 
			
		||||
  const aiErrorMessage = document.getElementById('ai-error-message');
 | 
			
		||||
  const aiResults = document.getElementById('ai-results');
 | 
			
		||||
 | 
			
		||||
  // Character counter
 | 
			
		||||
  if (queryInput && charCount) {
 | 
			
		||||
    queryInput.addEventListener('input', () => {
 | 
			
		||||
      charCount.textContent = queryInput.value.length;
 | 
			
		||||
    });
 | 
			
		||||
  let currentRecommendation = null;
 | 
			
		||||
 | 
			
		||||
  if (!aiInput || !aiSubmitBtn || !aiLoading || !aiError || !aiResults) {
 | 
			
		||||
    console.error('AI interface elements not found');
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Submit query
 | 
			
		||||
  if (submitBtn) {
 | 
			
		||||
    submitBtn.addEventListener('click', async () => {
 | 
			
		||||
      const query = queryInput?.value?.trim();
 | 
			
		||||
      if (!query) {
 | 
			
		||||
        alert('Bitte geben Sie eine Beschreibung Ihres Ermittlungsszenarios ein.');
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Check authentication
 | 
			
		||||
      const authStatus = await checkAuthentication();
 | 
			
		||||
      if (authStatus.authRequired && !authStatus.authenticated) {
 | 
			
		||||
        // Redirect to login only if authentication is required
 | 
			
		||||
        window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(window.location.pathname)}`;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Show loading state
 | 
			
		||||
      showLoadingState();
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        const response = await fetch('/api/ai/query', {
 | 
			
		||||
          method: 'POST',
 | 
			
		||||
          headers: {
 | 
			
		||||
            'Content-Type': 'application/json',
 | 
			
		||||
          },
 | 
			
		||||
          body: JSON.stringify({ query }),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const data = await response.json();
 | 
			
		||||
 | 
			
		||||
        if (data.success) {
 | 
			
		||||
          showResults(data.recommendation);
 | 
			
		||||
        } else {
 | 
			
		||||
          showError(data.error || 'Unbekannter Fehler');
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error('AI query failed:', error);
 | 
			
		||||
        showError('Verbindungsfehler. Bitte versuchen Sie es erneut.');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Clear button
 | 
			
		||||
  if (clearBtn) {
 | 
			
		||||
    clearBtn.addEventListener('click', () => {
 | 
			
		||||
      queryInput.value = '';
 | 
			
		||||
      charCount.textContent = '0';
 | 
			
		||||
      hideAllStates();
 | 
			
		||||
      // Clear session results
 | 
			
		||||
      window.aiSessionResults = null;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // New query button
 | 
			
		||||
  if (newQueryBtn) {
 | 
			
		||||
    newQueryBtn.addEventListener('click', () => {
 | 
			
		||||
      hideAllStates();
 | 
			
		||||
      // Clear session results
 | 
			
		||||
      window.aiSessionResults = null;
 | 
			
		||||
      queryInput?.focus();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Function to restore previous results when switching back to AI view
 | 
			
		||||
  function restoreSessionResults() {
 | 
			
		||||
    if (window.aiSessionResults) {
 | 
			
		||||
      showResults(window.aiSessionResults);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Make restore function globally accessible
 | 
			
		||||
  window.restoreAIResults = restoreSessionResults;
 | 
			
		||||
 | 
			
		||||
  // State management functions
 | 
			
		||||
  function showLoadingState() {
 | 
			
		||||
    hideAllStates();
 | 
			
		||||
    loadingEl.style.display = 'block';
 | 
			
		||||
    submitBtn.disabled = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function showError(message) {
 | 
			
		||||
    hideAllStates();
 | 
			
		||||
    errorEl.style.display = 'block';
 | 
			
		||||
    document.getElementById('ai-error-message').textContent = message;
 | 
			
		||||
    submitBtn.disabled = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function showResults(recommendation) {
 | 
			
		||||
    hideAllStates();
 | 
			
		||||
    populateResults(recommendation);
 | 
			
		||||
    // Save results to session
 | 
			
		||||
    window.aiSessionResults = recommendation;
 | 
			
		||||
    resultsEl.style.display = 'block';
 | 
			
		||||
    submitBtn.disabled = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function hideAllStates() {
 | 
			
		||||
    loadingEl.style.display = 'none';
 | 
			
		||||
    errorEl.style.display = 'none';
 | 
			
		||||
    resultsEl.style.display = 'none';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Populate results
 | 
			
		||||
  function populateResults(recommendation) {
 | 
			
		||||
    // Scenario analysis
 | 
			
		||||
    document.getElementById('scenario-analysis').textContent = recommendation.scenario_analysis || 'Keine Analyse verfügbar.';
 | 
			
		||||
 | 
			
		||||
    // Workflow suggestion
 | 
			
		||||
    document.getElementById('workflow-suggestion').textContent = recommendation.workflow_suggestion || 'Keine Workflow-Empfehlung verfügbar.';
 | 
			
		||||
 | 
			
		||||
    // Additional notes
 | 
			
		||||
    document.getElementById('additional-notes').textContent = recommendation.additional_notes || 'Keine zusätzlichen Hinweise verfügbar.';
 | 
			
		||||
 | 
			
		||||
    // Populate phase tools
 | 
			
		||||
    populatePhaseTools(recommendation.recommended_tools || []);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Populate tools by phase
 | 
			
		||||
  function populatePhaseTools(recommendedTools) {
 | 
			
		||||
    const phases = ['data-collection', 'examination', 'analysis', 'reporting'];
 | 
			
		||||
  // Character counter for input
 | 
			
		||||
  const updateCharacterCount = () => {
 | 
			
		||||
    const length = aiInput.value.length;
 | 
			
		||||
    const maxLength = 2000;
 | 
			
		||||
    
 | 
			
		||||
    phases.forEach(phaseId => {
 | 
			
		||||
      const container = document.getElementById(`phase-tools-${phaseId}`);
 | 
			
		||||
      if (!container) return;
 | 
			
		||||
    // Find or create character counter
 | 
			
		||||
    let counter = document.getElementById('ai-char-counter');
 | 
			
		||||
    if (!counter) {
 | 
			
		||||
      counter = document.createElement('div');
 | 
			
		||||
      counter.id = 'ai-char-counter';
 | 
			
		||||
      counter.style.cssText = 'font-size: 0.75rem; color: var(--color-text-secondary); text-align: right; margin-top: 0.25rem;';
 | 
			
		||||
      aiInput.parentNode.insertBefore(counter, aiInput.nextSibling);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    counter.textContent = `${length}/${maxLength}`;
 | 
			
		||||
    if (length > maxLength * 0.9) {
 | 
			
		||||
      counter.style.color = 'var(--color-warning)';
 | 
			
		||||
    } else {
 | 
			
		||||
      counter.style.color = 'var(--color-text-secondary)';
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
      const phaseTools = recommendedTools.filter(tool => tool.phase === phaseId);
 | 
			
		||||
      
 | 
			
		||||
      if (phaseTools.length === 0) {
 | 
			
		||||
        container.innerHTML = '<p class="text-muted" style="font-size: 0.875rem; margin: 0; font-style: italic; grid-column: 1 / -1; text-align: center; padding: 2rem;">Keine spezifischen Tools für diese Phase empfohlen.</p>';
 | 
			
		||||
        return;
 | 
			
		||||
  aiInput.addEventListener('input', updateCharacterCount);
 | 
			
		||||
  updateCharacterCount(); // Initial count
 | 
			
		||||
 | 
			
		||||
  // Submit handler
 | 
			
		||||
  const handleSubmit = async () => {
 | 
			
		||||
    const query = aiInput.value.trim();
 | 
			
		||||
    
 | 
			
		||||
    if (!query) {
 | 
			
		||||
      alert('Bitte geben Sie eine Beschreibung Ihres Szenarios ein.');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (query.length < 10) {
 | 
			
		||||
      alert('Bitte geben Sie eine detailliertere Beschreibung ein (mindestens 10 Zeichen).');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Hide previous results and errors
 | 
			
		||||
    aiResults.style.display = 'none';
 | 
			
		||||
    aiError.style.display = 'none';
 | 
			
		||||
    aiLoading.style.display = 'block';
 | 
			
		||||
    
 | 
			
		||||
    // Disable submit button
 | 
			
		||||
    aiSubmitBtn.disabled = true;
 | 
			
		||||
    aiSubmitBtn.textContent = 'Generiere Empfehlungen...';
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await fetch('/api/ai/query', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({ query })
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const data = await response.json();
 | 
			
		||||
 | 
			
		||||
      if (!response.ok) {
 | 
			
		||||
        throw new Error(data.error || `HTTP ${response.status}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Sort by priority: high -> medium -> low
 | 
			
		||||
      const priorityOrder = { 'high': 1, 'medium': 2, 'low': 3 };
 | 
			
		||||
      phaseTools.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
 | 
			
		||||
      if (!data.success) {
 | 
			
		||||
        throw new Error(data.error || 'Unknown error');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      container.innerHTML = phaseTools.map(recTool => {
 | 
			
		||||
        const toolData = window.aiToolsData.find(t => t.name === recTool.name);
 | 
			
		||||
        if (!toolData) return '';
 | 
			
		||||
      // Store recommendation for restoration
 | 
			
		||||
      currentRecommendation = data.recommendation;
 | 
			
		||||
      
 | 
			
		||||
      // Display results
 | 
			
		||||
      displayResults(data.recommendation, query);
 | 
			
		||||
      aiLoading.style.display = 'none';
 | 
			
		||||
      aiResults.style.display = 'block';
 | 
			
		||||
 | 
			
		||||
        const hasValidProjectUrl = toolData.projectUrl && toolData.projectUrl.trim() !== '';
 | 
			
		||||
        const isOSS = toolData.license !== 'Proprietary';
 | 
			
		||||
        const hasKnowledgebase = toolData.knowledgebase === true;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error('AI query failed:', error);
 | 
			
		||||
      aiLoading.style.display = 'none';
 | 
			
		||||
      aiError.style.display = 'block';
 | 
			
		||||
      
 | 
			
		||||
      // Show user-friendly error messages
 | 
			
		||||
      if (error.message.includes('429')) {
 | 
			
		||||
        aiErrorMessage.textContent = 'Zu viele Anfragen. Bitte warten Sie einen Moment und versuchen Sie es erneut.';
 | 
			
		||||
      } else if (error.message.includes('401')) {
 | 
			
		||||
        aiErrorMessage.textContent = 'Authentifizierung erforderlich. Bitte melden Sie sich an.';
 | 
			
		||||
      } else if (error.message.includes('503')) {
 | 
			
		||||
        aiErrorMessage.textContent = 'KI-Service vorübergehend nicht verfügbar. Bitte versuchen Sie es später erneut.';
 | 
			
		||||
      } else {
 | 
			
		||||
        aiErrorMessage.textContent = `Fehler: ${error.message}`;
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      // Re-enable submit button
 | 
			
		||||
      aiSubmitBtn.disabled = false;
 | 
			
		||||
      aiSubmitBtn.innerHTML = `
 | 
			
		||||
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
 | 
			
		||||
          <path d="M14.828 14.828a4 4 0 0 1-5.656 0"/>
 | 
			
		||||
          <path d="M9 9a3 3 0 1 1 6 0c0 .749-.269 1.433-.73 1.96L11 14v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1l-3.27-3.04A3 3 0 0 1 5 9a3 3 0 0 1 6 0"/>
 | 
			
		||||
          <path d="M12 17h.01"/>
 | 
			
		||||
        </svg>
 | 
			
		||||
        Empfehlungen generieren
 | 
			
		||||
      `;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
        const cardClass = hasValidProjectUrl ? 'hosted' : (isOSS ? 'oss' : '');
 | 
			
		||||
  // Event listeners
 | 
			
		||||
  aiSubmitBtn.addEventListener('click', handleSubmit);
 | 
			
		||||
  
 | 
			
		||||
  aiInput.addEventListener('keydown', (e) => {
 | 
			
		||||
    if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      handleSubmit();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
        return `
 | 
			
		||||
          <div class="tool-recommendation ${cardClass}" onclick="window.showToolDetails('${toolData.name}')">
 | 
			
		||||
            <div class="tool-rec-header">
 | 
			
		||||
              <h5 class="tool-rec-name">${toolData.name}</h5>
 | 
			
		||||
              <span class="tool-rec-priority ${recTool.priority}">${recTool.priority.toUpperCase()}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div class="tool-rec-justification">
 | 
			
		||||
              "${recTool.justification}"
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div style="margin-top: auto;">
 | 
			
		||||
              <div class="tool-rec-metadata">
 | 
			
		||||
                <div style="display: flex; align-items: center; gap: 0.25rem; margin-bottom: 0.5rem;">
 | 
			
		||||
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
			
		||||
                    <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
 | 
			
		||||
                    <line x1="9" y1="9" x2="15" y2="9"></line>
 | 
			
		||||
                    <line x1="9" y1="15" x2="15" y2="15"></line>
 | 
			
		||||
                  </svg>
 | 
			
		||||
                  <span>${(toolData.platforms || []).join(', ')}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div style="display: flex; align-items: center; gap: 0.25rem; margin-bottom: 0.5rem;">
 | 
			
		||||
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
			
		||||
                    <circle cx="12" cy="12" r="10"></circle>
 | 
			
		||||
                    <path d="M12 6v6l4 2"></path>
 | 
			
		||||
                  </svg>
 | 
			
		||||
                  <span>${toolData.skillLevel}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
 | 
			
		||||
                  ${hasValidProjectUrl ? '<span class="badge badge-primary">Self-Hosted</span>' : ''}
 | 
			
		||||
                  ${isOSS ? '<span class="badge badge-success">Open Source</span>' : ''}
 | 
			
		||||
                  ${hasKnowledgebase ? '<span class="badge badge-error">Infos 📖</span>' : ''}
 | 
			
		||||
  // Function to restore AI results when switching back to AI view
 | 
			
		||||
  window.restoreAIResults = () => {
 | 
			
		||||
    if (currentRecommendation) {
 | 
			
		||||
      aiResults.style.display = 'block';
 | 
			
		||||
      aiLoading.style.display = 'none';
 | 
			
		||||
      aiError.style.display = 'none';
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  function displayResults(recommendation, originalQuery) {
 | 
			
		||||
    // Group tools by phase
 | 
			
		||||
    const toolsByPhase = {};
 | 
			
		||||
    const phaseOrder = ['data-collection', 'examination', 'analysis', 'reporting'];
 | 
			
		||||
    const phaseNames = {
 | 
			
		||||
      'data-collection': 'Datensammlung',
 | 
			
		||||
      'examination': 'Auswertung', 
 | 
			
		||||
      'analysis': 'Analyse',
 | 
			
		||||
      'reporting': 'Bericht & Präsentation'
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Initialize phases
 | 
			
		||||
    phaseOrder.forEach(phase => {
 | 
			
		||||
      toolsByPhase[phase] = [];
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Group recommended tools by phase
 | 
			
		||||
    recommendation.recommended_tools?.forEach(recTool => {
 | 
			
		||||
      if (toolsByPhase[recTool.phase]) {
 | 
			
		||||
        // Find full tool data
 | 
			
		||||
        const fullTool = tools.find(t => t.name === recTool.name);
 | 
			
		||||
        if (fullTool) {
 | 
			
		||||
          toolsByPhase[recTool.phase].push({
 | 
			
		||||
            ...fullTool,
 | 
			
		||||
            recommendation: recTool
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const resultsHTML = `
 | 
			
		||||
      <div class="workflow-container">
 | 
			
		||||
        <div style="text-align: center; margin-bottom: 2rem; padding: 1.5rem; background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-primary) 100%); color: white; border-radius: 0.75rem;">
 | 
			
		||||
          <h3 style="margin: 0 0 0.75rem 0; font-size: 1.5rem;">Empfohlener DFIR-Workflow</h3>
 | 
			
		||||
          <p style="margin: 0; opacity: 0.9; line-height: 1.5;">
 | 
			
		||||
            Basierend auf Ihrer Anfrage: "<em>${originalQuery.slice(0, 100)}${originalQuery.length > 100 ? '...' : ''}</em>"
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        ${recommendation.scenario_analysis ? `
 | 
			
		||||
          <div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-primary);">
 | 
			
		||||
            <h4 style="margin: 0 0 1rem 0; color: var(--color-primary); display: flex; align-items: center; gap: 0.5rem;">
 | 
			
		||||
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
			
		||||
                <path d="M12 20h9"/>
 | 
			
		||||
                <path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
 | 
			
		||||
              </svg>
 | 
			
		||||
              Szenario-Analyse
 | 
			
		||||
            </h4>
 | 
			
		||||
            <p style="margin: 0; line-height: 1.6; color: var(--color-text);">${recommendation.scenario_analysis}</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        ` : ''}
 | 
			
		||||
 | 
			
		||||
        ${phaseOrder.map((phase, index) => {
 | 
			
		||||
          const phaseTools = toolsByPhase[phase];
 | 
			
		||||
          if (phaseTools.length === 0) return '';
 | 
			
		||||
 | 
			
		||||
          return `
 | 
			
		||||
            <div class="workflow-phase">
 | 
			
		||||
              <div class="phase-header">
 | 
			
		||||
                <div class="phase-number">${index + 1}</div>
 | 
			
		||||
                <div class="phase-info">
 | 
			
		||||
                  <h3 class="phase-title">${phaseNames[phase]}</h3>
 | 
			
		||||
                  <div class="phase-tools">
 | 
			
		||||
                    ${phaseTools.map(tool => {
 | 
			
		||||
                      const hasValidProjectUrl = tool.projectUrl !== undefined && 
 | 
			
		||||
                                                tool.projectUrl !== null && 
 | 
			
		||||
                                                tool.projectUrl !== "" && 
 | 
			
		||||
                                                tool.projectUrl.trim() !== "";
 | 
			
		||||
                      
 | 
			
		||||
                      const priorityColors = {
 | 
			
		||||
                        high: 'var(--color-error)',
 | 
			
		||||
                        medium: 'var(--color-warning)', 
 | 
			
		||||
                        low: 'var(--color-accent)'
 | 
			
		||||
                      };
 | 
			
		||||
 | 
			
		||||
                      return `
 | 
			
		||||
                        <div class="tool-recommendation ${hasValidProjectUrl ? 'hosted' : (tool.license !== 'Proprietary' ? 'oss' : '')}" 
 | 
			
		||||
                             onclick="window.showToolDetails('${tool.name}')">
 | 
			
		||||
                          <div class="tool-rec-header">
 | 
			
		||||
                            <h4 class="tool-rec-name">${tool.name}</h4>
 | 
			
		||||
                            <span class="tool-rec-priority ${tool.recommendation.priority}" 
 | 
			
		||||
                                  style="background-color: ${priorityColors[tool.recommendation.priority]};">
 | 
			
		||||
                              ${tool.recommendation.priority}
 | 
			
		||||
                            </span>
 | 
			
		||||
                          </div>
 | 
			
		||||
                          
 | 
			
		||||
                          <div class="tool-rec-justification">
 | 
			
		||||
                            "${tool.recommendation.justification}"
 | 
			
		||||
                          </div>
 | 
			
		||||
                          
 | 
			
		||||
                          <div class="tool-rec-metadata">
 | 
			
		||||
                            <div style="display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.5rem;">
 | 
			
		||||
                              ${hasValidProjectUrl ? '<span class="badge badge-primary">Self-Hosted</span>' : ''}
 | 
			
		||||
                              ${tool.license !== 'Proprietary' ? '<span class="badge badge-success">Open Source</span>' : ''}
 | 
			
		||||
                              <span class="badge" style="background-color: var(--color-bg-tertiary); color: var(--color-text);">${tool.skillLevel}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div style="font-size: 0.8125rem; color: var(--color-text-secondary);">
 | 
			
		||||
                              ${tool.platforms.join(', ')} • ${tool.license}
 | 
			
		||||
                            </div>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      `;
 | 
			
		||||
                    }).join('')}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              ${index < phaseOrder.length - 1 ? `
 | 
			
		||||
                <div class="workflow-arrow">
 | 
			
		||||
                  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2">
 | 
			
		||||
                    <line x1="12" y1="5" x2="12" y2="19"/>
 | 
			
		||||
                    <polyline points="19,12 12,19 5,12"/>
 | 
			
		||||
                  </svg>
 | 
			
		||||
                </div>
 | 
			
		||||
              ` : ''}
 | 
			
		||||
            </div>
 | 
			
		||||
          `;
 | 
			
		||||
        }).join('')}
 | 
			
		||||
 | 
			
		||||
        ${recommendation.workflow_suggestion ? `
 | 
			
		||||
          <div class="card" style="margin-top: 2rem; border-left: 4px solid var(--color-accent);">
 | 
			
		||||
            <h4 style="margin: 0 0 1rem 0; color: var(--color-accent); display: flex; align-items: center; gap: 0.5rem;">
 | 
			
		||||
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
			
		||||
                <polyline points="9,11 12,14 22,4"/>
 | 
			
		||||
                <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
 | 
			
		||||
              </svg>
 | 
			
		||||
              Workflow-Empfehlung
 | 
			
		||||
            </h4>
 | 
			
		||||
            <p style="margin: 0; line-height: 1.6; color: var(--color-text);">${recommendation.workflow_suggestion}</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        `;
 | 
			
		||||
      }).join('');
 | 
			
		||||
    });
 | 
			
		||||
        ` : ''}
 | 
			
		||||
 | 
			
		||||
        ${recommendation.additional_notes ? `
 | 
			
		||||
          <div class="card" style="margin-top: 1rem; background-color: var(--color-warning); color: white;">
 | 
			
		||||
            <h4 style="margin: 0 0 1rem 0; display: flex; align-items: center; gap: 0.5rem;">
 | 
			
		||||
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 | 
			
		||||
                <circle cx="12" cy="12" r="10"/>
 | 
			
		||||
                <line x1="12" y1="8" x2="12" y2="12"/>
 | 
			
		||||
                <line x1="12" y1="16" x2="12.01" y2="16"/>
 | 
			
		||||
              </svg>
 | 
			
		||||
              Wichtige Hinweise
 | 
			
		||||
            </h4>
 | 
			
		||||
            <p style="margin: 0; line-height: 1.6;">${recommendation.additional_notes}</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        ` : ''}
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    aiResults.innerHTML = resultsHTML;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
@ -156,7 +156,7 @@ const tools = data.tools;
 | 
			
		||||
      switchToView('ai');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Function to switch between different views
 | 
			
		||||
// Function to switch between different views
 | 
			
		||||
    function switchToView(view) {
 | 
			
		||||
      // Hide all views first (using non-null assertions since we've already checked)
 | 
			
		||||
      toolsGrid!.style.display = 'none';
 | 
			
		||||
@ -176,6 +176,43 @@ const tools = data.tools;
 | 
			
		||||
          aiInterface!.style.display = 'block';
 | 
			
		||||
          // Keep filters visible in AI mode for view switching
 | 
			
		||||
          filtersSection!.style.display = 'block';
 | 
			
		||||
          
 | 
			
		||||
          // Hide filter controls in AI mode - AGGRESSIVE APPROACH
 | 
			
		||||
          const domainPhaseContainer = document.querySelector('.domain-phase-container') as HTMLElement;
 | 
			
		||||
          const searchInput = document.getElementById('search-tools') as HTMLElement;
 | 
			
		||||
          const tagCloud = document.querySelector('.tag-cloud') as HTMLElement;
 | 
			
		||||
          // Hide all checkbox wrappers
 | 
			
		||||
          const checkboxWrappers = document.querySelectorAll('.checkbox-wrapper');
 | 
			
		||||
          // Hide the "Nach Tags filtern" header and button
 | 
			
		||||
          const tagHeader = document.querySelector('.tag-header') as HTMLElement;
 | 
			
		||||
          // Hide any elements containing "Proprietäre Software" or "Nach Tags filtern"
 | 
			
		||||
          const filterLabels = document.querySelectorAll('label, .tag-header, h4, h3');
 | 
			
		||||
          // Hide ALL input elements in the filters section (more aggressive)
 | 
			
		||||
          const allInputs = filtersSection!.querySelectorAll('input, select, textarea');
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
          if (domainPhaseContainer) domainPhaseContainer.style.display = 'none';
 | 
			
		||||
          if (searchInput) searchInput.style.display = 'none';
 | 
			
		||||
          if (tagCloud) tagCloud.style.display = 'none';
 | 
			
		||||
          if (tagHeader) tagHeader.style.display = 'none';
 | 
			
		||||
          
 | 
			
		||||
          // Hide ALL inputs in the filters section
 | 
			
		||||
          allInputs.forEach(input => {
 | 
			
		||||
            (input as HTMLElement).style.display = 'none';
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          checkboxWrappers.forEach(wrapper => {
 | 
			
		||||
            (wrapper as HTMLElement).style.display = 'none';
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          // Hide specific filter section elements by text content
 | 
			
		||||
          filterLabels.forEach(element => {
 | 
			
		||||
            const text = element.textContent?.toLowerCase() || '';
 | 
			
		||||
            if (text.includes('proprietäre') || text.includes('tags filtern') || text.includes('nach tags') || text.includes('suchen') || text.includes('search')) {
 | 
			
		||||
              (element as HTMLElement).style.display = 'none';
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          // Restore previous AI results if they exist
 | 
			
		||||
          if ((window as any).restoreAIResults) {
 | 
			
		||||
            (window as any).restoreAIResults();
 | 
			
		||||
@ -189,10 +226,72 @@ const tools = data.tools;
 | 
			
		||||
        case 'matrix':
 | 
			
		||||
          matrixContainer!.style.display = 'block';
 | 
			
		||||
          filtersSection!.style.display = 'block';
 | 
			
		||||
          
 | 
			
		||||
          // Show filter controls in matrix mode
 | 
			
		||||
          const domainPhaseContainerMatrix = document.querySelector('.domain-phase-container') as HTMLElement;
 | 
			
		||||
          const searchInputMatrix = document.getElementById('search-tools') as HTMLElement;
 | 
			
		||||
          const tagCloudMatrix = document.querySelector('.tag-cloud') as HTMLElement;
 | 
			
		||||
          const checkboxWrappersMatrix = document.querySelectorAll('.checkbox-wrapper');
 | 
			
		||||
          const tagHeaderMatrix = document.querySelector('.tag-header') as HTMLElement;
 | 
			
		||||
          const filterLabelsMatrix = document.querySelectorAll('label, .tag-header, h4, h3');
 | 
			
		||||
          const allInputsMatrix = filtersSection!.querySelectorAll('input, select, textarea');
 | 
			
		||||
          
 | 
			
		||||
          if (domainPhaseContainerMatrix) domainPhaseContainerMatrix.style.display = 'grid';
 | 
			
		||||
          if (searchInputMatrix) searchInputMatrix.style.display = 'block';
 | 
			
		||||
          if (tagCloudMatrix) tagCloudMatrix.style.display = 'flex';
 | 
			
		||||
          if (tagHeaderMatrix) tagHeaderMatrix.style.display = 'flex';
 | 
			
		||||
          
 | 
			
		||||
          // Restore ALL inputs in the filters section
 | 
			
		||||
          allInputsMatrix.forEach(input => {
 | 
			
		||||
            (input as HTMLElement).style.display = 'block';
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          checkboxWrappersMatrix.forEach(wrapper => {
 | 
			
		||||
            (wrapper as HTMLElement).style.display = 'flex';
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          // Restore filter section elements
 | 
			
		||||
          filterLabelsMatrix.forEach(element => {
 | 
			
		||||
            const text = element.textContent?.toLowerCase() || '';
 | 
			
		||||
            if (text.includes('proprietäre') || text.includes('tags filtern') || text.includes('nach tags') || text.includes('suchen') || text.includes('search')) {
 | 
			
		||||
              (element as HTMLElement).style.display = 'block';
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          break;
 | 
			
		||||
        default: // grid
 | 
			
		||||
          toolsGrid!.style.display = 'block';
 | 
			
		||||
          filtersSection!.style.display = 'block';
 | 
			
		||||
          
 | 
			
		||||
          // Show filter controls in grid mode
 | 
			
		||||
          const domainPhaseContainerGrid = document.querySelector('.domain-phase-container') as HTMLElement;
 | 
			
		||||
          const searchInputGrid = document.getElementById('search-tools') as HTMLElement;
 | 
			
		||||
          const tagCloudGrid = document.querySelector('.tag-cloud') as HTMLElement;
 | 
			
		||||
          const checkboxWrappersGrid = document.querySelectorAll('.checkbox-wrapper');
 | 
			
		||||
          const tagHeaderGrid = document.querySelector('.tag-header') as HTMLElement;
 | 
			
		||||
          const filterLabelsGrid = document.querySelectorAll('label, .tag-header, h4, h3');
 | 
			
		||||
          const allInputsGrid = filtersSection!.querySelectorAll('input, select, textarea');
 | 
			
		||||
          
 | 
			
		||||
          if (domainPhaseContainerGrid) domainPhaseContainerGrid.style.display = 'grid';
 | 
			
		||||
          if (searchInputGrid) searchInputGrid.style.display = 'block';
 | 
			
		||||
          if (tagCloudGrid) tagCloudGrid.style.display = 'flex';
 | 
			
		||||
          if (tagHeaderGrid) tagHeaderGrid.style.display = 'flex';
 | 
			
		||||
          
 | 
			
		||||
          // Restore ALL inputs in the filters section
 | 
			
		||||
          allInputsGrid.forEach(input => {
 | 
			
		||||
            (input as HTMLElement).style.display = 'block';
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          checkboxWrappersGrid.forEach(wrapper => {
 | 
			
		||||
            (wrapper as HTMLElement).style.display = 'flex';
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          // Restore filter section elements
 | 
			
		||||
          filterLabelsGrid.forEach(element => {
 | 
			
		||||
            const text = element.textContent?.toLowerCase() || '';
 | 
			
		||||
            if (text.includes('proprietäre') || text.includes('tags filtern') || text.includes('nach tags') || text.includes('suchen') || text.includes('search')) {
 | 
			
		||||
              (element as HTMLElement).style.display = 'block';
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user