diff --git a/src/components/AIQueryInterface.astro b/src/components/AIQueryInterface.astro
index a56fbbc..e3bae69 100644
--- a/src/components/AIQueryInterface.astro
+++ b/src/components/AIQueryInterface.astro
@@ -1,13 +1,12 @@
---
import { getToolsData } from '../utils/dataService.js';
-
// Load tools data for validation
const data = await getToolsData();
const tools = data.tools;
const phases = data.phases;
-const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add this line
+const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
---
@@ -21,13 +20,35 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add th
KI-gestützte Tool-Empfehlungen
-
+
Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Tool-Empfehlungen
basierend auf bewährten DFIR-Workflows und der verfügbaren Software-Datenbank.
@@ -70,7 +91,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add th
- Analysiere Szenario und generiere Empfehlungen...
+ Analysiere Szenario und generiere Empfehlungen...
@@ -98,24 +119,108 @@ document.addEventListener('DOMContentLoaded', () => {
const aiInterface = document.getElementById('ai-interface');
const aiInput = document.getElementById('ai-query-input');
const aiSubmitBtn = document.getElementById('ai-submit-btn');
+ const submitBtnText = document.getElementById('submit-btn-text');
const aiLoading = document.getElementById('ai-loading');
+ const loadingText = document.getElementById('loading-text');
const aiError = document.getElementById('ai-error');
const aiErrorMessage = document.getElementById('ai-error-message');
const aiResults = document.getElementById('ai-results');
+ const aiDescription = document.getElementById('ai-description');
+
+ // Toggle elements
+ const toggleSwitch = document.querySelector('.toggle-switch');
+ const toggleSlider = document.querySelector('.toggle-slider');
+ const workflowLabel = document.getElementById('workflow-label');
+ const toolLabel = document.getElementById('tool-label');
let currentRecommendation = null;
+ let currentMode = 'workflow'; // 'workflow' or 'tool'
if (!aiInput || !aiSubmitBtn || !aiLoading || !aiError || !aiResults) {
console.error('AI interface elements not found');
return;
}
+ // Mode content configuration
+ const modeConfig = {
+ workflow: {
+ placeholder: "Beschreiben Sie Ihr forensisches Szenario... z.B. 'Verdacht auf Ransomware-Angriff auf Windows-Domänencontroller mit verschlüsselten Dateien und verdächtigen Netzwerkverbindungen'",
+ description: "Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Tool-Empfehlungen basierend auf bewährten DFIR-Workflows und der verfügbaren Software-Datenbank.",
+ submitText: "Empfehlungen generieren",
+ loadingText: "Analysiere Szenario und generiere Empfehlungen..."
+ },
+ tool: {
+ placeholder: "Beschreiben Sie Ihr spezifisches Problem oder Ihre Anforderung... z.B. 'Ich benötige ein Tool zur Analyse von Android-Backups mit WhatsApp-Nachrichten und GPS-Daten'",
+ description: "Beschreiben Sie Ihr spezifisches Problem oder Ihre Anforderung und erhalten Sie 1-3 gezielt passende Tool-Empfehlungen mit detaillierten Erklärungen zur optimalen Anwendung.",
+ submitText: "Tool-Empfehlungen finden",
+ loadingText: "Analysiere Anforderungen und suche passende Tools..."
+ }
+ };
+
+ // Update UI based on current mode
+ function updateModeUI() {
+ const config = modeConfig[currentMode];
+
+ // Update placeholder
+ aiInput.placeholder = config.placeholder;
+
+ // Update description
+ aiDescription.textContent = config.description;
+
+ // Update submit button text
+ submitBtnText.textContent = config.submitText;
+
+ // Update loading text
+ loadingText.textContent = config.loadingText;
+
+ // Update toggle visual state
+ if (currentMode === 'workflow') {
+ toggleSlider.style.transform = 'translateX(0)';
+ toggleSwitch.style.backgroundColor = 'var(--color-primary)';
+ workflowLabel.style.color = 'var(--color-primary)';
+ workflowLabel.classList.add('active');
+ toolLabel.style.color = 'var(--color-text-secondary)';
+ toolLabel.classList.remove('active');
+ } else {
+ toggleSlider.style.transform = 'translateX(26px)';
+ toggleSwitch.style.backgroundColor = 'var(--color-accent)';
+ toolLabel.style.color = 'var(--color-accent)';
+ toolLabel.classList.add('active');
+ workflowLabel.style.color = 'var(--color-text-secondary)';
+ workflowLabel.classList.remove('active');
+ }
+
+ // Clear previous results when switching modes
+ aiResults.style.display = 'none';
+ aiError.style.display = 'none';
+ currentRecommendation = null;
+ }
+
+ // Toggle mode handlers
+ function switchToMode(mode) {
+ if (currentMode !== mode) {
+ currentMode = mode;
+ updateModeUI();
+ }
+ }
+
+ toggleSwitch.addEventListener('click', () => {
+ switchToMode(currentMode === 'workflow' ? 'tool' : 'workflow');
+ });
+
+ workflowLabel.addEventListener('click', () => {
+ switchToMode('workflow');
+ });
+
+ toolLabel.addEventListener('click', () => {
+ switchToMode('tool');
+ });
+
// Character counter for input
const updateCharacterCount = () => {
const length = aiInput.value.length;
const maxLength = 2000;
- // Find or create character counter
let counter = document.getElementById('ai-char-counter');
if (!counter) {
counter = document.createElement('div');
@@ -133,14 +238,14 @@ document.addEventListener('DOMContentLoaded', () => {
};
aiInput.addEventListener('input', updateCharacterCount);
- updateCharacterCount(); // Initial count
+ updateCharacterCount();
// Submit handler
const handleSubmit = async () => {
const query = aiInput.value.trim();
if (!query) {
- alert('Bitte geben Sie eine Beschreibung Ihres Szenarios ein.');
+ alert('Bitte geben Sie eine Beschreibung ein.');
return;
}
@@ -156,7 +261,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Disable submit button
aiSubmitBtn.disabled = true;
- aiSubmitBtn.textContent = 'Generiere Empfehlungen...';
+ submitBtnText.textContent = currentMode === 'workflow' ? 'Generiere Empfehlungen...' : 'Suche passende Tools...';
try {
const response = await fetch('/api/ai/query', {
@@ -164,7 +269,10 @@ document.addEventListener('DOMContentLoaded', () => {
headers: {
'Content-Type': 'application/json',
},
- body: JSON.stringify({ query })
+ body: JSON.stringify({
+ query,
+ mode: currentMode
+ })
});
const data = await response.json();
@@ -180,8 +288,13 @@ document.addEventListener('DOMContentLoaded', () => {
// Store recommendation for restoration
currentRecommendation = data.recommendation;
- // Display results
- displayResults(data.recommendation, query);
+ // Display results based on mode
+ if (currentMode === 'workflow') {
+ displayWorkflowResults(data.recommendation, query);
+ } else {
+ displayToolResults(data.recommendation, query);
+ }
+
aiLoading.style.display = 'none';
aiResults.style.display = 'block';
@@ -203,14 +316,8 @@ document.addEventListener('DOMContentLoaded', () => {
} finally {
// Re-enable submit button
aiSubmitBtn.disabled = false;
- aiSubmitBtn.innerHTML = `
-
-
-
-
-
- Empfehlungen generieren
- `;
+ const config = modeConfig[currentMode];
+ submitBtnText.textContent = config.submitText;
}
};
@@ -235,11 +342,9 @@ document.addEventListener('DOMContentLoaded', () => {
// Helper function to format workflow suggestions as proper lists
function formatWorkflowSuggestion(text) {
- // Check if text contains numbered list items (1., 2., 3., etc.)
const numberedListPattern = /(\d+\.\s)/g;
if (numberedListPattern.test(text)) {
- // Split by numbered items and clean up
const items = text.split(/\d+\.\s/).filter(item => item.trim().length > 0);
if (items.length > 1) {
@@ -251,7 +356,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
- // Check for bullet points (-, *, •)
const bulletPattern = /^[\s]*[-\*•]\s/gm;
if (bulletPattern.test(text)) {
const items = text.split(/^[\s]*[-\*•]\s/gm).filter(item => item.trim().length > 0);
@@ -265,7 +369,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
- // Check for line breaks that might indicate separate points
if (text.includes('\n')) {
const lines = text.split('\n').filter(line => line.trim().length > 0);
if (lines.length > 1) {
@@ -277,11 +380,11 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
- // Fallback to regular paragraph if no list format detected
return `${text}
`;
}
- function displayResults(recommendation, originalQuery) {
+ // Display results for workflow mode (existing functionality)
+ function displayWorkflowResults(recommendation, originalQuery) {
// Group tools by phase
const toolsByPhase = {};
@@ -299,7 +402,6 @@ document.addEventListener('DOMContentLoaded', () => {
// 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({
@@ -428,11 +530,229 @@ document.addEventListener('DOMContentLoaded', () => {
` : ''}
`;
- aiResults.innerHTML = ''; // Clear previous results first
+
+ aiResults.innerHTML = '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = resultsHTML;
- // Sanitize any dynamic content before inserting
aiResults.appendChild(tempDiv);
}
+
+ // Display results for tool-specific mode (new functionality)
+ function displayToolResults(recommendation, originalQuery) {
+ function getSuitabilityText(score) {
+ const suitabilityTexts = {
+ high: 'GUT GEEIGNET',
+ medium: 'GEEIGNET',
+ low: 'VIELLEICHT GEEIGNET'
+ };
+ return suitabilityTexts[score] || 'GEEIGNET';
+ }
+
+ // Helper function to get phase names for a tool
+ function getToolPhases(tool) {
+ if (!tool.phases || tool.phases.length === 0) return '';
+
+ const phaseNames = phases.reduce((acc, phase) => {
+ acc[phase.id] = phase.name;
+ return acc;
+ }, {});
+
+ const domainAgnosticNames = domainAgnosticSoftware.reduce((acc, section) => {
+ acc[section.id] = section.name;
+ return acc;
+ }, {});
+
+ const allPhaseNames = { ...phaseNames, ...domainAgnosticNames };
+
+ return tool.phases.map(phaseId => allPhaseNames[phaseId]).filter(Boolean).join(', ');
+ }
+ const resultsHTML = `
+
+ `;
+
+ aiResults.innerHTML = '';
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = resultsHTML;
+ aiResults.appendChild(tempDiv);
+ }
+
+ // Initialize UI
+ updateModeUI();
});
\ No newline at end of file
diff --git a/src/pages/api/ai/query.ts b/src/pages/api/ai/query.ts
index 9cf1833..4696c9e 100644
--- a/src/pages/api/ai/query.ts
+++ b/src/pages/api/ai/query.ts
@@ -1,10 +1,8 @@
// src/pages/api/ai/query.ts
-// src/pages/api/ai/query.ts
import type { APIRoute } from 'astro';
import { getSessionFromRequest, verifySession } from '../../../utils/auth.js';
import { getCompressedToolsDataForAI } from '../../../utils/dataService.js';
-
export const prerender = false;
function getEnv(key: string): string {
@@ -84,8 +82,8 @@ async function loadToolsDatabase() {
}
}
-// Create system prompt
-function createSystemPrompt(toolsData: any): string {
+// Create system prompt for workflow mode
+function createWorkflowSystemPrompt(toolsData: any): string {
const toolsList = toolsData.tools.map((tool: any) => ({
name: tool.name,
description: tool.description,
@@ -179,6 +177,58 @@ ANTWORT-FORMAT (strict JSON):
Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des JSON.`;
}
+// Create system prompt for tool-specific mode
+function createToolSystemPrompt(toolsData: any): string {
+ const toolsList = toolsData.tools.map((tool: any) => ({
+ name: tool.name,
+ description: tool.description,
+ domains: tool.domains,
+ phases: tool.phases,
+ platforms: tool.platforms,
+ skillLevel: tool.skillLevel,
+ license: tool.license,
+ tags: tool.tags,
+ url: tool.url,
+ projectUrl: tool.projectUrl
+ }));
+
+ return `Du bist ein DFIR (Digital Forensics and Incident Response) Experte, der bei der Auswahl spezifischer Tools für konkrete Probleme hilft.
+
+VERFÜGBARE TOOLS DATABASE:
+${JSON.stringify(toolsList, null, 2)}
+
+WICHTIGE REGELN:
+1. Analysiere das spezifische Problem/die Anforderung sorgfältig
+2. Empfehle 1-3 Tools, sortiert nach Eignung (beste Empfehlung zuerst)
+3. Gib detaillierte Erklärungen, WARUM und WIE jedes Tool das Problem löst
+4. Berücksichtige praktische Aspekte: Skill Level, Plattformen, Verfügbarkeit
+5. Deutsche Antworten für deutsche Anfragen, English for English queries
+6. Gib konkrete Anwendungshinweise, nicht nur allgemeine Beschreibungen
+7. Erwähne sowohl Stärken als auch Schwächen/Limitationen
+8. Schlage alternative Ansätze vor, wenn sinnvoll
+9. Gib grundsätzliche Hinweise, WIE das Tool konkret eingesetzt wird
+
+ANTWORT-FORMAT (strict JSON):
+{
+ "problem_analysis": "Detaillierte Analyse des Problems/der Anforderung",
+ "recommended_tools": [
+ {
+ "name": "EXAKTER Name aus der Database",
+ "rank": 1,
+ "suitability_score": "high|medium|low",
+ "detailed_explanation": "Detaillierte Erklärung, warum dieses Tool das Problem löst",
+ "implementation_approach": "Konkrete Schritte/Ansatz zur Anwendung für dieses spezifische Problem",
+ "pros": ["Spezifische Vorteile für diesen Anwendungsfall", "Weitere Vorteile"],
+ "cons": ["Potentielle Nachteile oder Limitationen", "Weitere Einschränkungen"],
+ "alternatives": "Alternative Ansätze oder ergänzende Tools, falls relevant"
+ }
+ ],
+ "additional_considerations": "Wichtige Überlegungen, Voraussetzungen oder Warnungen"
+}
+
+Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des JSON.`;
+}
+
export const POST: APIRoute = async ({ request }) => {
try {
// Check if authentication is required
@@ -216,7 +266,7 @@ export const POST: APIRoute = async ({ request }) => {
// Parse request body
const body = await request.json();
- const { query } = body;
+ const { query, mode = 'workflow' } = body;
if (!query || typeof query !== 'string') {
return new Response(JSON.stringify({ error: 'Query required' }), {
@@ -225,6 +275,13 @@ export const POST: APIRoute = async ({ request }) => {
});
}
+ if (!['workflow', 'tool'].includes(mode)) {
+ return new Response(JSON.stringify({ error: 'Invalid mode. Must be "workflow" or "tool"' }), {
+ status: 400,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ }
+
// Sanitize input
const sanitizedQuery = sanitizeInput(query);
if (sanitizedQuery.includes('[FILTERED]')) {
@@ -237,8 +294,10 @@ export const POST: APIRoute = async ({ request }) => {
// Load tools database
const toolsData = await loadToolsDatabase();
- // Create AI request
- const systemPrompt = createSystemPrompt(toolsData);
+ // Create appropriate system prompt based on mode
+ const systemPrompt = mode === 'workflow'
+ ? createWorkflowSystemPrompt(toolsData)
+ : createToolSystemPrompt(toolsData);
const aiResponse = await fetch(process.env.AI_API_ENDPOINT + '/v1/chat/completions', {
method: 'POST',
@@ -247,7 +306,7 @@ export const POST: APIRoute = async ({ request }) => {
'Authorization': `Bearer ${process.env.AI_API_KEY}`
},
body: JSON.stringify({
- model: AI_MODEL, // or whatever model is available
+ model: AI_MODEL,
messages: [
{
role: 'system',
@@ -263,7 +322,6 @@ export const POST: APIRoute = async ({ request }) => {
})
});
-
if (!aiResponse.ok) {
console.error('AI API error:', await aiResponse.text());
return new Response(JSON.stringify({ error: 'AI service unavailable' }), {
@@ -295,24 +353,48 @@ export const POST: APIRoute = async ({ request }) => {
});
}
- // Validate tool names against database
+ // Validate tool names against database based on mode
const validToolNames = new Set(toolsData.tools.map((t: any) => t.name));
- const validatedRecommendation = {
- ...recommendation,
- recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
- if (!validToolNames.has(tool.name)) {
- console.warn(`AI recommended unknown tool: ${tool.name}`);
- return false;
- }
- return true;
- }) || []
- };
+ let validatedRecommendation;
+
+ if (mode === 'workflow') {
+ // Existing validation for workflow mode
+ validatedRecommendation = {
+ ...recommendation,
+ recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
+ if (!validToolNames.has(tool.name)) {
+ console.warn(`AI recommended unknown tool: ${tool.name}`);
+ return false;
+ }
+ return true;
+ }) || []
+ };
+ } else {
+ // New validation for tool mode
+ validatedRecommendation = {
+ ...recommendation,
+ recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
+ if (!validToolNames.has(tool.name)) {
+ console.warn(`AI recommended unknown tool: ${tool.name}`);
+ return false;
+ }
+ return true;
+ }).map((tool: any, index: number) => ({
+ ...tool,
+ rank: tool.rank || (index + 1), // Ensure rank is set
+ suitability_score: tool.suitability_score || 'medium', // Default suitability
+ pros: Array.isArray(tool.pros) ? tool.pros : [],
+ cons: Array.isArray(tool.cons) ? tool.cons : []
+ })) || []
+ };
+ }
// Log successful query
- console.log(`[AI Query] User: ${userId}, Query length: ${sanitizedQuery.length}, Tools: ${validatedRecommendation.recommended_tools.length}`);
+ console.log(`[AI Query] Mode: ${mode}, User: ${userId}, Query length: ${sanitizedQuery.length}, Tools: ${validatedRecommendation.recommended_tools.length}`);
return new Response(JSON.stringify({
success: true,
+ mode,
recommendation: validatedRecommendation,
query: sanitizedQuery
}), {
diff --git a/src/styles/global.css b/src/styles/global.css
index 838a30d..513a6a3 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -825,6 +825,62 @@ Collaboration Section Collapse */
animation: fadeIn 0.3s ease-in;
}
+.ai-mode-toggle {
+ user-select: none;
+}
+
+.toggle-label {
+ display: flex;
+ align-items: center;
+ font-size: 0.875rem;
+ transition: var(--transition-fast);
+}
+
+.toggle-label:hover {
+ color: var(--color-primary) !important;
+}
+
+.toggle-label.active {
+ font-weight: 600;
+}
+
+.toggle-switch {
+ position: relative;
+ cursor: pointer;
+ transition: var(--transition-fast);
+}
+
+.toggle-switch:hover {
+ transform: scale(1.05);
+}
+
+.toggle-slider {
+ box-shadow: var(--shadow-sm);
+ transition: var(--transition-fast);
+}
+
+/* Dark mode adjustments for toggle */
+[data-theme="dark"] .toggle-slider {
+ box-shadow: 0 2px 4px 0 rgb(255 255 255 / 10%);
+}
+
+[data-theme="dark"] .tool-rank-badge {
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 50%);
+}
+
+/* Focus states for accessibility */
+.toggle-switch:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+ border-radius: 12px;
+}
+
+.toggle-label:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+ border-radius: 0.25rem;
+}
+
.workflow-container {
display: flex;
flex-direction: column;
@@ -961,6 +1017,117 @@ Collaboration Section Collapse */
color: var(--color-text-secondary);
}
+/* Tool Results Styles */
+.tool-results-container {
+ max-width: 1000px;
+ margin: 0 auto;
+}
+
+.tool-recommendations-grid {
+ display: grid;
+ gap: 1.5rem;
+}
+
+.tool-detailed-recommendation {
+ position: relative;
+ transition: var(--transition-fast);
+ border: 2px solid transparent;
+ animation: fadeInUp 0.5s ease-out;
+}
+
+.tool-detailed-recommendation:nth-child(2) {
+ animation-delay: 0.1s;
+}
+
+.tool-detailed-recommendation:nth-child(3) {
+ animation-delay: 0.2s;
+}
+
+.tool-detailed-recommendation:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-lg);
+ border-color: var(--color-primary);
+}
+
+.tool-detailed-recommendation.card-hosted {
+ background-color: var(--color-hosted-bg);
+ border-color: var(--color-hosted);
+}
+
+.tool-detailed-recommendation.card-hosted:hover {
+ border-color: var(--color-hosted);
+ box-shadow: 0 0 0 1px var(--color-hosted), var(--shadow-lg);
+}
+
+.tool-detailed-recommendation.card-oss {
+ background-color: var(--color-oss-bg);
+ border-color: var(--color-oss);
+}
+
+.tool-detailed-recommendation.card-oss:hover {
+ border-color: var(--color-oss);
+}
+
+.tool-rank-badge {
+ animation: fadeInUp 0.6s ease-out;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ font-weight: 700;
+}
+
+.tool-detailed-explanation h4 {
+ display: flex;
+ align-items: center;
+ font-size: 1rem;
+ margin-bottom: 0.75rem;
+}
+
+.pros-cons-section {
+ animation: fadeIn 0.4s ease-in;
+}
+
+.pros, .cons {
+ font-size: 0.875rem;
+}
+
+.pros ul, .cons ul {
+ list-style-type: none;
+ padding-left: 0;
+}
+
+.pros li, .cons li {
+ position: relative;
+ padding-left: 1.5rem;
+ margin-bottom: 0.375rem;
+ line-height: 1.4;
+}
+
+.pros li::before {
+ content: '✓';
+ position: absolute;
+ left: 0;
+ color: var(--color-accent);
+ font-weight: bold;
+}
+
+.cons li::before {
+ content: '⚠';
+ position: absolute;
+ left: 0;
+ color: var(--color-warning);
+ font-weight: bold;
+}
+
+.tool-metadata {
+ background-color: var(--color-bg-secondary);
+ padding: 1rem;
+ border-radius: 0.5rem;
+ border-left: 3px solid var(--color-primary);
+}
+
+.alternatives {
+ border-left: 3px solid var(--color-text-secondary);
+}
+
/* Knowledgebase */
.kb-entry {
margin-bottom: 1rem;
@@ -1138,6 +1305,29 @@ footer {
font-size: 0.8125rem;
padding: 0.25rem 0.5rem;
}
+
+ .ai-mode-toggle {
+ flex-direction: column;
+ gap: 0.75rem;
+ text-align: center;
+ }
+
+ .toggle-label {
+ font-size: 0.8125rem;
+ }
+
+ .toggle-switch {
+ align-self: center;
+ }
+
+ .pros-cons-section {
+ grid-template-columns: 1fr;
+ }
+
+ .tool-metadata {
+ grid-template-columns: 1fr;
+ gap: 0.5rem;
+ }
}
@media (width <= 480px) {