new AI features
This commit is contained in:
@@ -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'] || [];
|
||||
---
|
||||
|
||||
<!-- AI Query Interface -->
|
||||
@@ -21,13 +20,35 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add th
|
||||
</svg>
|
||||
KI-gestützte Tool-Empfehlungen
|
||||
</h2>
|
||||
<p class="text-muted" style="max-width: 700px; margin: 0 auto; line-height: 1.6;">
|
||||
<p id="ai-description" class="text-muted" style="max-width: 700px; margin: 0 auto; line-height: 1.6;">
|
||||
Beschreiben Sie Ihr forensisches Szenario und erhalten Sie maßgeschneiderte Tool-Empfehlungen
|
||||
basierend auf bewährten DFIR-Workflows und der verfügbaren Software-Datenbank.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ai-input-container" style="max-width: 800px; margin: 0 auto;">
|
||||
<!-- Mode Toggle -->
|
||||
<div class="ai-mode-toggle" style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.75rem; border: 1px solid var(--color-border);">
|
||||
<span id="workflow-label" class="toggle-label active" style="font-weight: 500; color: var(--color-primary); cursor: pointer; transition: var(--transition-fast);">
|
||||
<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>
|
||||
Workflow-Empfehlung
|
||||
</span>
|
||||
|
||||
<div class="toggle-switch" style="position: relative; width: 50px; height: 24px; background-color: var(--color-primary); border-radius: 12px; cursor: pointer; transition: var(--transition-fast);">
|
||||
<div class="toggle-slider" style="position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background-color: white; border-radius: 50%; transition: var(--transition-fast); transform: translateX(0);"></div>
|
||||
</div>
|
||||
|
||||
<span id="tool-label" class="toggle-label" style="font-weight: 500; color: var(--color-text-secondary); cursor: pointer; transition: var(--transition-fast);">
|
||||
<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="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>
|
||||
Spezifisches Tool
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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'"
|
||||
@@ -55,7 +76,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add th
|
||||
<path d="M9 9a3 3 0 1 1 6 0c0 .749-.269 1.433-.73 1.96L11 14v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1l-3.27-3.04A3 3 0 0 1 5 9a3 3 0 0 1 6 0"/>
|
||||
<path d="M12 17h.01"/>
|
||||
</svg>
|
||||
Empfehlungen generieren
|
||||
<span id="submit-btn-text">Empfehlungen generieren</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +91,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || []; // Add th
|
||||
<path d="M12 17h.01"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p style="color: var(--color-text-secondary);">Analysiere Szenario und generiere Empfehlungen...</p>
|
||||
<p id="loading-text" style="color: var(--color-text-secondary);">Analysiere Szenario und generiere Empfehlungen...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
@@ -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 = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
|
||||
<path d="M14.828 14.828a4 4 0 0 1-5.656 0"/>
|
||||
<path d="M9 9a3 3 0 1 1 6 0c0 .749-.269 1.433-.73 1.96L11 14v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1l-3.27-3.04A3 3 0 0 1 5 9a3 3 0 0 1 6 0"/>
|
||||
<path d="M12 17h.01"/>
|
||||
</svg>
|
||||
Empfehlungen generieren
|
||||
`;
|
||||
const 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 `<p style="margin: 0; line-height: 1.6; color: var(--color-text);">${text}</p>`;
|
||||
}
|
||||
|
||||
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', () => {
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
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 = `
|
||||
<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 Tool-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>
|
||||
` : ''}
|
||||
|
||||
<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 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 ${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>
|
||||
${hasValidProjectUrl ? '<span class="badge badge-primary">Self-Hosted</span>' : ''}
|
||||
${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 dieses Tool?
|
||||
</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;">
|
||||
<div><strong>Plattformen:</strong> ${fullTool.platforms.join(', ')}</div>
|
||||
<div><strong>Skill Level:</strong> ${fullTool.skillLevel}</div>
|
||||
<div><strong>Lizenz:</strong> ${fullTool.license}</div>
|
||||
<div><strong>Typ:</strong> ${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);
|
||||
}
|
||||
|
||||
// Initialize UI
|
||||
updateModeUI();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user