This commit is contained in:
overcuriousity
2025-07-26 21:02:10 +02:00
parent d6a9620758
commit 72d5f267f0
3 changed files with 717 additions and 456 deletions

View File

@@ -46,13 +46,29 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
</span>
</div>
<textarea
<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>
<!-- Smart Prompting Container -->
<div id="smart-prompting-container" style="display: none; margin-top: 1rem;">
<div class="card" style="border-left: 4px solid var(--color-accent); padding: 1rem;">
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem;">
<span id="prompting-status">💡 KI analysiert Ihre Eingabe...</span>
<div id="prompting-spinner" style="display: none; animation: pulse 2s ease-in-out infinite;">⏳</div>
</div>
<div id="suggested-questions" style="display: none;">
<p style="font-size: 0.875rem; color: var(--color-text-secondary); margin-bottom: 0.5rem;">
Klicken Sie auf eine Frage, um sie zu Ihrer Beschreibung hinzuzufügen:
</p>
<div id="questions-container" style="display: flex; flex-direction: column; gap: 0.375rem;"></div>
</div>
</div>
</div>
<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.4;">
<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;">
@@ -131,6 +147,12 @@ function sanitizeHTML(html) {
return div.innerHTML;
}
function formatDuration(ms) {
if (ms < 1000) return '< 1s';
if (ms < 60000) return `${Math.ceil(ms / 1000)}s`;
return `${Math.ceil(ms / 60000)}m`;
}
document.addEventListener('DOMContentLoaded', () => {
const aiInterface = document.getElementById('ai-interface');
const aiInput = document.getElementById('ai-query-input');
@@ -245,6 +267,491 @@ document.addEventListener('DOMContentLoaded', () => {
aiInput.addEventListener('input', updateCharacterCount);
updateCharacterCount();
function formatWorkflowSuggestion(text) {
const numberedListPattern = /(\d+\.\s)/g;
if (numberedListPattern.test(text)) {
const items = text.split(/\d+\.\s/).filter(item => item.trim().length > 0);
if (items.length > 1) {
const listItems = items.map(item =>
`<li style="margin-bottom: 0.5rem; line-height: 1.6;">${item.trim()}</li>`
).join('');
return `<ol style="margin: 0; padding-left: 1.5rem; color: var(--color-text);">${listItems}</ol>`;
}
}
const bulletPattern = /^[\s]*[-\*•]\s/gm;
if (bulletPattern.test(text)) {
const items = text.split(/^[\s]*[-\*•]\s/gm).filter(item => item.trim().length > 0);
if (items.length > 1) {
const listItems = items.map(item =>
`<li style="margin-bottom: 0.5rem; line-height: 1.6;">${item.trim()}</li>`
).join('');
return `<ul style="margin: 0; padding-left: 1.5rem; color: var(--color-text);">${listItems}</ul>`;
}
}
if (text.includes('\n')) {
const lines = text.split('\n').filter(line => line.trim().length > 0);
if (lines.length > 1) {
const listItems = lines.map(line =>
`<li style="margin-bottom: 0.5rem; line-height: 1.6;">${line.trim()}</li>`
).join('');
return `<ul style="margin: 0; padding-left: 1.5rem; color: var(--color-text);">${listItems}</ul>`;
}
}
return `<p style="margin: 0; line-height: 1.6; color: var(--color-text);">${text}</p>`;
}
function renderBackgroundKnowledge(backgroundKnowledge) {
if (!backgroundKnowledge || backgroundKnowledge.length === 0) {
return '';
}
const conceptLinks = backgroundKnowledge.map(concept => `
<div class="concept-recommendation" style="background-color: var(--color-concept-bg); border: 1px solid var(--color-concept); border-radius: 0.5rem; padding: 1rem; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem;">
<button class="concept-link"
onclick="event.stopPropagation(); window.showToolDetails('${concept.concept_name}', 'secondary')"
style="background: none; border: none; color: var(--color-concept); font-weight: 600; cursor: pointer; text-decoration: underline; font-size: 0.875rem; padding: 0;"
onmouseover="this.style.color='var(--color-concept-hover)';"
onmouseout="this.style.color='var(--color-concept)';">
📚 ${concept.concept_name}
</button>
<span class="badge" style="background-color: var(--color-concept); color: white; font-size: 0.625rem;">Hintergrundwissen</span>
</div>
<p style="margin: 0; font-size: 0.8125rem; line-height: 1.5; color: var(--color-text-secondary);">
${concept.relevance}
</p>
</div>
`).join('');
return `
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-concept);">
<h4 style="margin: 0 0 1rem 0; color: var(--color-concept); 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="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/>
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>
</svg>
Empfohlenes Hintergrundwissen
</h4>
${conceptLinks}
</div>
`;
}
// Enhanced function to render contextual analysis sections
function renderContextualAnalysis(recommendation, mode) {
let html = '';
// Scenario/Problem Analysis Section
const analysisField = mode === 'workflow' ? recommendation.scenario_analysis : recommendation.problem_analysis;
if (analysisField) {
html += `
<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>
${mode === 'workflow' ? 'Szenario-Analyse' : 'Problem-Analyse'}
</h4>
${formatWorkflowSuggestion(analysisField)}
</div>
`;
}
// Investigation Approach Section
if (recommendation.investigation_approach) {
html += `
<div class="card" style="margin-bottom: 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>
${mode === 'workflow' ? 'Untersuchungsansatz' : 'Lösungsansatz'}
</h4>
${formatWorkflowSuggestion(recommendation.investigation_approach)}
</div>
`;
}
// Critical Considerations Section
if (recommendation.critical_considerations) {
html += `
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-warning);">
<h4 style="margin: 0 0 1rem 0; color: var(--color-warning); 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>
${mode === 'workflow' ? 'Kritische Überlegungen' : 'Wichtige Voraussetzungen'}
</h4>
${formatWorkflowSuggestion(recommendation.critical_considerations)}
</div>
`;
}
return html;
}
function displayWorkflowResults(recommendation, originalQuery) {
const toolsByPhase = {};
const phaseOrder = phases.map(phase => phase.id);
const phaseNames = phases.reduce((acc, phase) => {
acc[phase.id] = phase.name;
return acc;
}, {});
phaseOrder.forEach(phase => {
toolsByPhase[phase] = [];
});
recommendation.recommended_tools?.forEach(recTool => {
if (toolsByPhase[recTool.phase]) {
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>
${renderContextualAnalysis(recommendation, 'workflow')}
${renderBackgroundKnowledge(recommendation.background_knowledge)}
${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 ${tool.type === 'method' ? 'method' : hasValidProjectUrl ? 'hosted' : (tool.license !== 'Proprietary' ? 'oss' : '')}"
onclick="window.showToolDetails('${tool.name}')">
<div class="tool-rec-header">
<h4 class="tool-rec-name">
${tool.icon ? `<span style="margin-right: 0.5rem;">${tool.icon}</span>` : ''}
${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;">
${tool.type !== 'method' && hasValidProjectUrl ? '<span class="badge badge-primary">CC24-Server</span>' : ''}
${tool.knowledgebase === true ? '<span class="badge badge-error">📖</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.type === 'method' ? 'Methode' : 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>
${formatWorkflowSuggestion(recommendation.workflow_suggestion)}
</div>
` : ''}
${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>
<div style="color: white;">
${formatWorkflowSuggestion(recommendation.additional_notes).replace(/color: var\(--color-text\)/g, 'color: white')}
</div>
</div>
` : ''}
</div>
`;
aiResults.innerHTML = '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = resultsHTML;
aiResults.appendChild(tempDiv);
}
function displayToolResults(recommendation, originalQuery) {
function getSuitabilityText(score) {
const suitabilityTexts = {
high: 'GUT GEEIGNET',
medium: 'GEEIGNET',
low: 'VIELLEICHT GEEIGNET'
};
return suitabilityTexts[score] || 'GEEIGNET';
}
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 = `
<div class="tool-results-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;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg>
Passende Empfehlungen
</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>
${renderContextualAnalysis(recommendation, 'tool')}
${renderBackgroundKnowledge(recommendation.background_knowledge)}
<div class="tool-recommendations-grid" style="display: grid; gap: 1.5rem;">
${recommendation.recommended_tools?.map((toolRec, index) => {
const fullTool = tools.find(t => t.name === toolRec.name);
if (!fullTool) return '';
const hasValidProjectUrl = fullTool.projectUrl !== undefined &&
fullTool.projectUrl !== null &&
fullTool.projectUrl !== "" &&
fullTool.projectUrl.trim() !== "";
const isMethod = fullTool.type === 'method';
const suitabilityColors = {
high: 'var(--color-accent)',
medium: 'var(--color-warning)',
low: 'var(--color-text-secondary)'
};
const rankColors = {
1: 'var(--color-accent)',
2: 'var(--color-primary)',
3: 'var(--color-warning)'
};
return `
<div class="tool-detailed-recommendation card ${isMethod ? 'card-method' : hasValidProjectUrl ? 'card-hosted' : (fullTool.license !== 'Proprietary' ? 'card-oss' : '')}"
style="cursor: pointer; position: relative;"
onclick="window.showToolDetails('${fullTool.name}')">
<div class="tool-rank-badge" style="position: absolute; top: -8px; right: -8px; width: 32px; height: 32px; background-color: ${rankColors[toolRec.rank]}; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.125rem; box-shadow: var(--shadow-md);">
${toolRec.rank}
</div>
<div class="tool-rec-header" style="margin-bottom: 1rem;">
<h3 style="margin: 0 0 0.5rem 0; color: var(--color-text); font-size: 1.25rem;">${fullTool.name}</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;">
<span class="badge" style="background-color: ${suitabilityColors[toolRec.suitability_score]}; color: white; font-size: 0.8125rem;">
${getSuitabilityText(toolRec.suitability_score)}
</span>
${isMethod ? '<span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>' : ''}
${!isMethod && hasValidProjectUrl ? '<span class="badge badge-primary">CC24-Server</span>' : ''}
${!isMethod && fullTool.license !== 'Proprietary' ? '<span class="badge badge-success">Open Source</span>' : ''}
${fullTool.knowledgebase === true ? '<span class="badge badge-error">📖</span>' : ''}
</div>
</div>
<div>
${(() => {
const toolPhases = getToolPhases(fullTool);
return toolPhases ? `
<div style="margin-top: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; color: var(--color-text-secondary);">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink: 0;">
<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>
<span><strong>Anwendbare Phasen:</strong> ${toolPhases}</span>
</div>
</div>
` : '';
})()}
</div>
<div class="tool-detailed-explanation" style="margin-bottom: 1.5rem;">
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-accent); font-size: 1rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; 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>
Warum diese Methode für Ihr Szenario?
</h4>
${formatWorkflowSuggestion(toolRec.detailed_explanation)}
${toolRec.implementation_approach ? `
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-primary); font-size: 1rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;">
<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>
Anwendungsansatz
</h4>
${formatWorkflowSuggestion(toolRec.implementation_approach)}
` : ''}
</div>
${(toolRec.pros && toolRec.pros.length > 0) || (toolRec.cons && toolRec.cons.length > 0) ? `
<div class="pros-cons-section" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1.5rem;">
${toolRec.pros && toolRec.pros.length > 0 ? `
<div class="pros" style="background-color: var(--color-oss-bg); padding: 1rem; border-radius: 0.5rem; border-left: 3px solid var(--color-accent);">
<h5 style="margin: 0 0 0.5rem 0; color: var(--color-accent); font-size: 0.875rem; text-transform: uppercase;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
<polyline points="20,6 9,17 4,12"/>
</svg>
Vorteile
</h5>
<ul style="margin: 0; padding-left: 1rem; font-size: 0.875rem; line-height: 1.5;">
${toolRec.pros.map(pro => `<li style="margin-bottom: 0.25rem;">${pro}</li>`).join('')}
</ul>
</div>
` : ''}
${toolRec.cons && toolRec.cons.length > 0 ? `
<div class="cons" style="background-color: var(--color-hosted-bg); padding: 1rem; border-radius: 0.5rem; border-left: 3px solid var(--color-warning);">
<h5 style="margin: 0 0 0.5rem 0; color: var(--color-warning); font-size: 0.875rem; text-transform: uppercase;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
Nachteile
</h5>
<ul style="margin: 0; padding-left: 1rem; font-size: 0.875rem; line-height: 1.5;">
${toolRec.cons.map(con => `<li style="margin-bottom: 0.25rem;">${con}</li>`).join('')}
</ul>
</div>
` : ''}
</div>
` : ''}
<div class="tool-metadata" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.75rem; font-size: 0.8125rem; color: var(--color-text-secondary); margin-bottom: 1rem;">
${!isMethod ? '<div><strong>Plattformen:</strong> ' + fullTool.platforms.join(', ') + '</div>' : ''}
<div><strong>Skill Level:</strong> ${fullTool.skillLevel}</div>
${!isMethod ? '<div><strong>Lizenz:</strong> ' + fullTool.license + '</div>' : ''}
<div><strong>Typ:</strong> ${isMethod ? 'Methode' : fullTool.accessType}</div>
</div>
${toolRec.alternatives ? `
<div class="alternatives" style="background-color: var(--color-bg-secondary); padding: 1rem; border-radius: 0.5rem; margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; color: var(--color-text-secondary); font-size: 0.875rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27,6.96 12,12.01 20.73,6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>
Alternative Ansätze
</h5>
${formatWorkflowSuggestion(toolRec.alternatives)}
</div>
` : ''}
</div>
`;
}).join('')}
</div>
${recommendation.additional_considerations ? `
<div class="card" style="margin-top: 2rem; background-color: var(--color-bg-secondary); border-left: 4px solid var(--color-text-secondary);">
<h4 style="margin: 0 0 1rem 0; color: var(--color-text-secondary); 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>
Zusätzliche Überlegungen
</h4>
${formatWorkflowSuggestion(recommendation.additional_considerations)}
</div>
` : ''}
</div>
`;
aiResults.innerHTML = '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = resultsHTML;
aiResults.appendChild(tempDiv);
}
const handleSubmit = async () => {
const query = aiInput.value.trim();
@@ -402,456 +909,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
function formatWorkflowSuggestion(text) {
const numberedListPattern = /(\d+\.\s)/g;
if (numberedListPattern.test(text)) {
const items = text.split(/\d+\.\s/).filter(item => item.trim().length > 0);
if (items.length > 1) {
const listItems = items.map(item =>
`<li style="margin-bottom: 0.5rem; line-height: 1.6;">${item.trim()}</li>`
).join('');
return `<ol style="margin: 0; padding-left: 1.5rem; color: var(--color-text);">${listItems}</ol>`;
}
}
const bulletPattern = /^[\s]*[-\*•]\s/gm;
if (bulletPattern.test(text)) {
const items = text.split(/^[\s]*[-\*•]\s/gm).filter(item => item.trim().length > 0);
if (items.length > 1) {
const listItems = items.map(item =>
`<li style="margin-bottom: 0.5rem; line-height: 1.6;">${item.trim()}</li>`
).join('');
return `<ul style="margin: 0; padding-left: 1.5rem; color: var(--color-text);">${listItems}</ul>`;
}
}
if (text.includes('\n')) {
const lines = text.split('\n').filter(line => line.trim().length > 0);
if (lines.length > 1) {
const listItems = lines.map(line =>
`<li style="margin-bottom: 0.5rem; line-height: 1.6;">${line.trim()}</li>`
).join('');
return `<ul style="margin: 0; padding-left: 1.5rem; color: var(--color-text);">${listItems}</ul>`;
}
}
return `<p style="margin: 0; line-height: 1.6; color: var(--color-text);">${text}</p>`;
}
function renderBackgroundKnowledge(backgroundKnowledge) {
if (!backgroundKnowledge || backgroundKnowledge.length === 0) {
return '';
}
const conceptLinks = backgroundKnowledge.map(concept => `
<div class="concept-recommendation" style="background-color: var(--color-concept-bg); border: 1px solid var(--color-concept); border-radius: 0.5rem; padding: 1rem; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem;">
<button class="concept-link"
onclick="event.stopPropagation(); window.showToolDetails('${concept.concept_name}', 'secondary')"
style="background: none; border: none; color: var(--color-concept); font-weight: 600; cursor: pointer; text-decoration: underline; font-size: 0.875rem; padding: 0;"
onmouseover="this.style.color='var(--color-concept-hover)';"
onmouseout="this.style.color='var(--color-concept)';">
📚 ${concept.concept_name}
</button>
<span class="badge" style="background-color: var(--color-concept); color: white; font-size: 0.625rem;">Hintergrundwissen</span>
</div>
<p style="margin: 0; font-size: 0.8125rem; line-height: 1.5; color: var(--color-text-secondary);">
${concept.relevance}
</p>
</div>
`).join('');
return `
<div class="card" style="margin-bottom: 2rem; border-left: 4px solid var(--color-concept);">
<h4 style="margin: 0 0 1rem 0; color: var(--color-concept); 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="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/>
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>
</svg>
Empfohlenes Hintergrundwissen
</h4>
${conceptLinks}
</div>
`;
}
function displayWorkflowResults(recommendation, originalQuery) {
const toolsByPhase = {};
const phaseOrder = phases.map(phase => phase.id);
const phaseNames = phases.reduce((acc, phase) => {
acc[phase.id] = phase.name;
return acc;
}, {});
phaseOrder.forEach(phase => {
toolsByPhase[phase] = [];
});
recommendation.recommended_tools?.forEach(recTool => {
if (toolsByPhase[recTool.phase]) {
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>
${formatWorkflowSuggestion(recommendation.scenario_analysis)}
</div>
` : ''}
${renderBackgroundKnowledge(recommendation.background_knowledge)}
${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 ${tool.type === 'method' ? 'method' : hasValidProjectUrl ? 'hosted' : (tool.license !== 'Proprietary' ? 'oss' : '')}"
onclick="window.showToolDetails('${tool.name}')">
<div class="tool-rec-header">
<h4 class="tool-rec-name">
${tool.icon ? `<span style="margin-right: 0.5rem;">${tool.icon}</span>` : ''}
${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;">
${tool.type !== 'method' && hasValidProjectUrl ? '<span class="badge badge-primary">CC24-Server</span>' : ''}
${tool.knowledgebase === true ? '<span class="badge badge-error">📖</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.type === 'method' ? 'Methode' : 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>
${formatWorkflowSuggestion(recommendation.workflow_suggestion)}
</div>
` : ''}
${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>
<div style="color: white;">
${formatWorkflowSuggestion(recommendation.additional_notes).replace(/color: var\(--color-text\)/g, 'color: white')}
</div>
</div>
` : ''}
</div>
`;
aiResults.innerHTML = '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = resultsHTML;
aiResults.appendChild(tempDiv);
}
function displayToolResults(recommendation, originalQuery) {
function getSuitabilityText(score) {
const suitabilityTexts = {
high: 'GUT GEEIGNET',
medium: 'GEEIGNET',
low: 'VIELLEICHT GEEIGNET'
};
return suitabilityTexts[score] || 'GEEIGNET';
}
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 = `
<div class="tool-results-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;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg>
Passende Empfehlungen
</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.problem_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">
<circle cx="12" cy="12" r="10"/>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
Problem-Analyse
</h4>
${formatWorkflowSuggestion(recommendation.problem_analysis)}
</div>
` : ''}
${renderBackgroundKnowledge(recommendation.background_knowledge)}
<div class="tool-recommendations-grid" style="display: grid; gap: 1.5rem;">
${recommendation.recommended_tools?.map((toolRec, index) => {
const fullTool = tools.find(t => t.name === toolRec.name);
if (!fullTool) return '';
const hasValidProjectUrl = fullTool.projectUrl !== undefined &&
fullTool.projectUrl !== null &&
fullTool.projectUrl !== "" &&
fullTool.projectUrl.trim() !== "";
const isMethod = fullTool.type === 'method';
const suitabilityColors = {
high: 'var(--color-accent)',
medium: 'var(--color-warning)',
low: 'var(--color-text-secondary)'
};
const rankColors = {
1: 'var(--color-accent)',
2: 'var(--color-primary)',
3: 'var(--color-warning)'
};
return `
<div class="tool-detailed-recommendation card ${isMethod ? 'card-method' : hasValidProjectUrl ? 'card-hosted' : (fullTool.license !== 'Proprietary' ? 'card-oss' : '')}"
style="cursor: pointer; position: relative;"
onclick="window.showToolDetails('${fullTool.name}')">
<div class="tool-rank-badge" style="position: absolute; top: -8px; right: -8px; width: 32px; height: 32px; background-color: ${rankColors[toolRec.rank]}; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.125rem; box-shadow: var(--shadow-md);">
${toolRec.rank}
</div>
<div class="tool-rec-header" style="margin-bottom: 1rem;">
<h3 style="margin: 0 0 0.5rem 0; color: var(--color-text); font-size: 1.25rem;">${fullTool.name}</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;">
<span class="badge" style="background-color: ${suitabilityColors[toolRec.suitability_score]}; color: white; font-size: 0.8125rem;">
${getSuitabilityText(toolRec.suitability_score)}
</span>
${isMethod ? '<span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>' : ''}
${!isMethod && hasValidProjectUrl ? '<span class="badge badge-primary">CC24-Server</span>' : ''}
${!isMethod && fullTool.license !== 'Proprietary' ? '<span class="badge badge-success">Open Source</span>' : ''}
${fullTool.knowledgebase === true ? '<span class="badge badge-error">📖</span>' : ''}
</div>
</div>
<div>
${(() => {
const toolPhases = getToolPhases(fullTool);
return toolPhases ? `
<div style="margin-top: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; color: var(--color-text-secondary);">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex-shrink: 0;">
<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>
<span><strong>Anwendbare Phasen:</strong> ${toolPhases}</span>
</div>
</div>
` : '';
})()}
</div>
<div class="tool-detailed-explanation" style="margin-bottom: 1.5rem;">
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-accent); font-size: 1rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; 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>
Warum diese Methode?
</h4>
${formatWorkflowSuggestion(toolRec.detailed_explanation)}
${toolRec.implementation_approach ? `
<h4 style="margin: 0.8rem 0 0.75rem 0; color: var(--color-primary); font-size: 1rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;">
<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>
Anwendungsansatz
</h4>
${formatWorkflowSuggestion(toolRec.implementation_approach)}
` : ''}
</div>
${(toolRec.pros && toolRec.pros.length > 0) || (toolRec.cons && toolRec.cons.length > 0) ? `
<div class="pros-cons-section" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1.5rem;">
${toolRec.pros && toolRec.pros.length > 0 ? `
<div class="pros" style="background-color: var(--color-oss-bg); padding: 1rem; border-radius: 0.5rem; border-left: 3px solid var(--color-accent);">
<h5 style="margin: 0 0 0.5rem 0; color: var(--color-accent); font-size: 0.875rem; text-transform: uppercase;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
<polyline points="20,6 9,17 4,12"/>
</svg>
Vorteile
</h5>
<ul style="margin: 0; padding-left: 1rem; font-size: 0.875rem; line-height: 1.5;">
${toolRec.pros.map(pro => `<li style="margin-bottom: 0.25rem;">${pro}</li>`).join('')}
</ul>
</div>
` : ''}
${toolRec.cons && toolRec.cons.length > 0 ? `
<div class="cons" style="background-color: var(--color-hosted-bg); padding: 1rem; border-radius: 0.5rem; border-left: 3px solid var(--color-warning);">
<h5 style="margin: 0 0 0.5rem 0; color: var(--color-warning); font-size: 0.875rem; text-transform: uppercase;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
Nachteile
</h5>
<ul style="margin: 0; padding-left: 1rem; font-size: 0.875rem; line-height: 1.5;">
${toolRec.cons.map(con => `<li style="margin-bottom: 0.25rem;">${con}</li>`).join('')}
</ul>
</div>
` : ''}
</div>
` : ''}
<div class="tool-metadata" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.75rem; font-size: 0.8125rem; color: var(--color-text-secondary); margin-bottom: 1rem;">
${!isMethod ? '<div><strong>Plattformen:</strong> ' + fullTool.platforms.join(', ') + '</div>' : ''}
<div><strong>Skill Level:</strong> ${fullTool.skillLevel}</div>
${!isMethod ? '<div><strong>Lizenz:</strong> ' + fullTool.license + '</div>' : ''}
<div><strong>Typ:</strong> ${isMethod ? 'Methode' : fullTool.accessType}</div>
</div>
${toolRec.alternatives ? `
<div class="alternatives" style="background-color: var(--color-bg-secondary); padding: 1rem; border-radius: 0.5rem; margin-bottom: 1rem;">
<h5 style="margin: 0 0 0.5rem 0; color: var(--color-text-secondary); font-size: 0.875rem;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
<polyline points="3.27,6.96 12,12.01 20.73,6.96"/>
<line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>
Alternative Ansätze
</h5>
${formatWorkflowSuggestion(toolRec.alternatives)}
</div>
` : ''}
</div>
`;
}).join('')}
</div>
${recommendation.additional_considerations ? `
<div class="card" style="margin-top: 2rem; background-color: var(--color-bg-secondary); border-left: 4px solid var(--color-text-secondary);">
<h4 style="margin: 0 0 1rem 0; color: var(--color-text-secondary); 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>
Zusätzliche Überlegungen
</h4>
${formatWorkflowSuggestion(recommendation.additional_considerations)}
</div>
` : ''}
</div>
`;
aiResults.innerHTML = '';
const tempDiv = document.createElement('div');
tempDiv.innerHTML = resultsHTML;
aiResults.appendChild(tempDiv);
}
updateModeUI();
});
</script>