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>

View File

@ -0,0 +1,182 @@
// src/pages/api/ai/enhance-input.ts
import type { APIRoute } from 'astro';
import { withAPIAuth } from '../../../utils/auth.js';
import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js';
import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js';
export const prerender = false;
function getEnv(key: string): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing environment variable: ${key}`);
}
return value;
}
const AI_MODEL = getEnv('AI_MODEL');
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX = 5; // 5 enhancement requests per minute per user
function sanitizeInput(input: string): string {
return input
.replace(/```[\s\S]*?```/g, '[CODE_BLOCK_REMOVED]')
.replace(/\<\/?[^>]+(>|$)/g, '')
.replace(/\b(system|assistant|user)\s*[:]/gi, '[ROLE_REMOVED]')
.replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
.trim()
.slice(0, 1000); // Shorter limit for enhancement
}
function checkRateLimit(userId: string): boolean {
const now = Date.now();
const userLimit = rateLimitStore.get(userId);
if (!userLimit || now > userLimit.resetTime) {
rateLimitStore.set(userId, { count: 1, resetTime: now + RATE_LIMIT_WINDOW });
return true;
}
if (userLimit.count >= RATE_LIMIT_MAX) {
return false;
}
userLimit.count++;
return true;
}
function cleanupExpiredRateLimits() {
const now = Date.now();
for (const [userId, limit] of rateLimitStore.entries()) {
if (now > limit.resetTime) {
rateLimitStore.delete(userId);
}
}
}
// Clean up expired limits every 5 minutes
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
function createEnhancementPrompt(input: string): string {
return `Analysiere diese forensische Szenario-Beschreibung und schlage 2-3 kurze, präzise Fragen vor, die dem Nutzer helfen würden, vollständigere Informationen zu liefern.
Nutzer-Eingabe: "${input}"
Konzentriere dich auf wichtige Details die für eine forensische Untersuchung relevant sind:
- Betroffene Systeme/Plattformen
- Zeitrahmen/Timeline
- Verfügbare Evidenz
- Verdächtige Aktivitäten
- Technische Details
Antworte NUR mit einem JSON-Array von 2-3 kurzen Fragen (max. 60 Zeichen pro Frage):
["Frage 1?", "Frage 2?", "Frage 3?"]
Keine zusätzlichen Erklärungen.`;
}
export const POST: APIRoute = async ({ request }) => {
try {
const authResult = await withAPIAuth(request, 'ai');
if (!authResult.authenticated) {
return createAuthErrorResponse();
}
const userId = authResult.userId;
if (!checkRateLimit(userId)) {
return apiError.rateLimit('Enhancement rate limit exceeded');
}
const body = await request.json();
const { input } = body;
if (!input || typeof input !== 'string' || input.length < 20) {
return apiError.badRequest('Input too short for enhancement');
}
const sanitizedInput = sanitizeInput(input);
if (sanitizedInput.length < 20) {
return apiError.badRequest('Input too short after sanitization');
}
const systemPrompt = createEnhancementPrompt(sanitizedInput);
const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`;
const aiResponse = await enqueueApiCall(() =>
fetch(process.env.AI_API_ENDPOINT + '/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.AI_API_KEY}`
},
body: JSON.stringify({
model: AI_MODEL,
messages: [
{
role: 'user',
content: systemPrompt
}
],
max_tokens: 200,
temperature: 0.7
})
}), taskId);
if (!aiResponse.ok) {
console.error('AI enhancement error:', await aiResponse.text());
return apiServerError.unavailable('Enhancement service unavailable');
}
const aiData = await aiResponse.json();
const aiContent = aiData.choices?.[0]?.message?.content;
if (!aiContent) {
return apiServerError.unavailable('No enhancement response');
}
let questions;
try {
// Clean up the response and parse JSON
const cleanedContent = aiContent
.replace(/^```json\s*/i, '')
.replace(/\s*```\s*$/, '')
.trim();
questions = JSON.parse(cleanedContent);
if (!Array.isArray(questions) || questions.length === 0) {
throw new Error('Invalid questions format');
}
// Validate and clean questions
questions = questions
.filter(q => typeof q === 'string' && q.length > 5 && q.length < 100)
.slice(0, 3);
if (questions.length === 0) {
throw new Error('No valid questions found');
}
} catch (error) {
console.error('Failed to parse enhancement response:', aiContent);
return apiServerError.unavailable('Invalid enhancement response format');
}
console.log(`[AI Enhancement] User: ${userId}, Questions: ${questions.length}, Input length: ${sanitizedInput.length}`);
return new Response(JSON.stringify({
success: true,
questions,
taskId
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Enhancement error:', error);
return apiServerError.internal('Enhancement processing failed');
}
};

View File

@ -155,6 +155,11 @@ WICHTIGE REGELN:
8. WICHTIG: Erwähne relevante Hintergrundwissen-Konzepte wenn Tools verwendet werden, die related_concepts haben
9. Konzepte sind NICHT Tools - empfehle sie nicht als actionable Schritte, sondern als Wissensbasis
ENHANCED CONTEXTUAL ANALYSIS:
10. Analysiere das Szenario detailliert und identifiziere Schlüsselelemente, Bedrohungen und forensische Herausforderungen
11. Entwickle einen strategischen Untersuchungsansatz basierend auf dem spezifischen Szenario
12. Identifiziere zeitkritische oder besonders wichtige Faktoren für diesen Fall
SOFTWARE/METHODEN-AUSWAHL NACH PHASE:
${phaseDescriptions}
@ -163,16 +168,18 @@ ${domainAgnosticDescriptions}
ANTWORT-FORMAT (strict JSON):
{
"scenario_analysis": "Detaillierte Analyse des Szenarios auf Deutsch/English",
"scenario_analysis": "Detaillierte Analyse des Szenarios: Erkannte Schlüsselelemente, Art des Vorfalls, betroffene Systeme, potentielle Bedrohungen und forensische Herausforderungen",
"investigation_approach": "Strategischer Untersuchungsansatz für dieses spezifische Szenario: Prioritäten, Reihenfolge der Phasen, besondere Überlegungen",
"critical_considerations": "Zeitkritische Faktoren, wichtige Sicherheitsaspekte oder besondere Vorsichtsmaßnahmen für diesen Fall",
"recommended_tools": [
{
"name": "EXAKTER Name aus der Tools-Database",
"priority": "high|medium|low",
"phase": "${validPhases}",
"justification": "Warum diese Methode für diese Phase und Szenario geeignet ist"
"justification": "Warum diese Methode für diese Phase und dieses spezifische Szenario geeignet ist - mit Bezug zu den erkannten Schlüsselelementen"
}
],
"workflow_suggestion": "Vorgeschlagener Untersuchungsablauf",
"workflow_suggestion": "Vorgeschlagener Untersuchungsablauf mit konkreten Schritten für dieses Szenario",
"background_knowledge": [
{
"concept_name": "EXAKTER Name aus der Konzepte-Database",
@ -230,15 +237,22 @@ WICHTIGE REGELN:
10. WICHTIG: Erwähne relevante Hintergrundwissen-Konzepte wenn Tools verwendet werden, die related_concepts haben
11. Konzepte sind NICHT Tools - empfehle sie nicht als actionable Schritte, sondern als Wissensbasis
ENHANCED CONTEXTUAL ANALYSIS:
12. Analysiere das Problem detailliert und identifiziere technische Anforderungen, Herausforderungen und Erfolgsfaktoren
13. Entwickle einen strategischen Lösungsansatz basierend auf dem spezifischen Problem
14. Identifiziere wichtige Voraussetzungen oder Warnungen für die Anwendung
ANTWORT-FORMAT (strict JSON):
{
"problem_analysis": "Detaillierte Analyse des Problems/der Anforderung",
"problem_analysis": "Detaillierte Analyse des Problems: Erkannte technische Anforderungen, Herausforderungen, benötigte Fähigkeiten und Erfolgsfaktoren",
"investigation_approach": "Strategischer Lösungsansatz für dieses spezifische Problem: Herangehensweise, Prioritäten, optimale Anwendungsreihenfolge",
"critical_considerations": "Wichtige Voraussetzungen, potentielle Fallstricke oder Warnungen für die Anwendung der empfohlenen Lösungen",
"recommended_tools": [
{
"name": "EXAKTER Name aus der Tools-Database",
"rank": 1,
"suitability_score": "high|medium|low",
"detailed_explanation": "Detaillierte Erklärung, warum dieses Tool/diese Methode das Problem löst",
"detailed_explanation": "Detaillierte Erklärung, warum dieses Tool/diese Methode das spezifische Problem löst - mit Bezug zu den erkannten Anforderungen",
"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"],
@ -348,6 +362,10 @@ export const POST: APIRoute = async ({ request }) => {
if (mode === 'workflow') {
validatedRecommendation = {
...recommendation,
// Ensure all new fields are included with fallbacks
scenario_analysis: recommendation.scenario_analysis || recommendation.problem_analysis || '',
investigation_approach: recommendation.investigation_approach || '',
critical_considerations: recommendation.critical_considerations || '',
recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
if (!validToolNames.has(tool.name)) {
console.warn(`AI recommended unknown tool: ${tool.name}`);
@ -366,6 +384,10 @@ export const POST: APIRoute = async ({ request }) => {
} else {
validatedRecommendation = {
...recommendation,
// Ensure all new fields are included with fallbacks
problem_analysis: recommendation.problem_analysis || recommendation.scenario_analysis || '',
investigation_approach: recommendation.investigation_approach || '',
critical_considerations: recommendation.critical_considerations || '',
recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
if (!validToolNames.has(tool.name)) {
console.warn(`AI recommended unknown tool: ${tool.name}`);