phase 2 + css
This commit is contained in:
parent
72d5f267f0
commit
867a651a55
@ -46,7 +46,7 @@ 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;"
|
||||
@ -58,13 +58,28 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
<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 id="prompting-spinner" style="display: none;">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2" style="animation: spin 1s linear infinite;">
|
||||
<path d="M21 12a9 9 0 11-6.219-8.56"/>
|
||||
</svg>
|
||||
</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 style="margin: 0 0 0.75rem 0; font-size: 0.875rem; color: var(--color-text-secondary); line-height: 1.5;">
|
||||
Zur besseren Analyse Ihres Szenarios könnten diese Informationen hilfreich sein:
|
||||
</p>
|
||||
<div id="questions-container" style="display: flex; flex-direction: column; gap: 0.375rem;"></div>
|
||||
<div id="questions-list" style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<!-- Questions will be populated here -->
|
||||
</div>
|
||||
<div style="margin-top: 1rem; text-align: right;">
|
||||
<button id="dismiss-suggestions" class="btn-icon" style="font-size: 0.75rem; padding: 0.25rem 0.5rem; border-radius: 0.25rem; background-color: var(--color-bg-secondary);">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem;">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
Ausblenden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -165,6 +180,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const aiResults = document.getElementById('ai-results');
|
||||
const aiDescription = document.getElementById('ai-description');
|
||||
|
||||
// Smart prompting elements
|
||||
const smartPromptingContainer = document.getElementById('smart-prompting-container');
|
||||
const promptingStatus = document.getElementById('prompting-status');
|
||||
const promptingSpinner = document.getElementById('prompting-spinner');
|
||||
const suggestedQuestions = document.getElementById('suggested-questions');
|
||||
const questionsList = document.getElementById('questions-list');
|
||||
const dismissSuggestions = document.getElementById('dismiss-suggestions');
|
||||
|
||||
const toggleSwitch = document.querySelector('.toggle-switch');
|
||||
const toggleSlider = document.querySelector('.toggle-slider');
|
||||
const workflowLabel = document.getElementById('workflow-label');
|
||||
@ -173,6 +196,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let currentRecommendation = null;
|
||||
let currentMode = 'workflow';
|
||||
|
||||
// Smart prompting state
|
||||
let enhancementTimeout;
|
||||
let enhancementAbortController;
|
||||
let suggestionsVisible = false;
|
||||
|
||||
if (!aiInput || !aiSubmitBtn || !aiLoading || !aiError || !aiResults) {
|
||||
console.error('AI interface elements not found');
|
||||
return;
|
||||
@ -197,11 +225,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const config = modeConfig[currentMode];
|
||||
|
||||
aiInput.placeholder = config.placeholder;
|
||||
|
||||
aiDescription.textContent = config.description;
|
||||
|
||||
submitBtnText.textContent = config.submitText;
|
||||
|
||||
loadingText.textContent = config.loadingText;
|
||||
|
||||
if (currentMode === 'workflow') {
|
||||
@ -228,10 +253,178 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
function switchToMode(mode) {
|
||||
if (currentMode !== mode) {
|
||||
currentMode = mode;
|
||||
resetSmartPrompting();
|
||||
updateModeUI();
|
||||
}
|
||||
}
|
||||
|
||||
// Smart Prompting Functions
|
||||
function showPromptingStatus(state) {
|
||||
if (!smartPromptingContainer || !promptingStatus || !promptingSpinner) return;
|
||||
|
||||
switch (state) {
|
||||
case 'analyzing':
|
||||
smartPromptingContainer.style.display = 'block';
|
||||
promptingStatus.textContent = '💡 KI analysiert Ihre Eingabe...';
|
||||
promptingSpinner.style.display = 'inline-block';
|
||||
suggestedQuestions.style.display = 'none';
|
||||
suggestionsVisible = false;
|
||||
break;
|
||||
case 'suggestions':
|
||||
promptingStatus.textContent = '✅ Verbesserungsvorschläge verfügbar';
|
||||
promptingSpinner.style.display = 'none';
|
||||
suggestedQuestions.style.display = 'block';
|
||||
suggestionsVisible = true;
|
||||
break;
|
||||
case 'rate-limited':
|
||||
promptingStatus.textContent = '⏸️ Verbesserungen verfügbar nach Hauptabfrage';
|
||||
promptingSpinner.style.display = 'none';
|
||||
suggestedQuestions.style.display = 'none';
|
||||
suggestionsVisible = false;
|
||||
break;
|
||||
case 'error':
|
||||
promptingStatus.textContent = '⚠️ Verbesserungen temporär nicht verfügbar';
|
||||
promptingSpinner.style.display = 'none';
|
||||
suggestedQuestions.style.display = 'none';
|
||||
suggestionsVisible = false;
|
||||
break;
|
||||
case 'hidden':
|
||||
smartPromptingContainer.style.display = 'none';
|
||||
suggestionsVisible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function displaySuggestions(suggestions) {
|
||||
if (!questionsList || !suggestions || suggestions.length === 0) return;
|
||||
|
||||
questionsList.innerHTML = '';
|
||||
|
||||
suggestions.forEach((question, index) => {
|
||||
const questionElement = document.createElement('div');
|
||||
questionElement.style.cssText = `
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
`;
|
||||
|
||||
questionElement.innerHTML = `
|
||||
<div style="display: flex; align-items: start; gap: 0.5rem;">
|
||||
<span style="color: var(--color-accent); font-weight: bold; flex-shrink: 0;">${index + 1}.</span>
|
||||
<span style="flex: 1;">${question}</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="2" style="flex-shrink: 0; margin-top: 0.125rem;">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/>
|
||||
<line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</svg>
|
||||
</div>
|
||||
`;
|
||||
|
||||
questionElement.addEventListener('mouseenter', () => {
|
||||
questionElement.style.backgroundColor = 'var(--color-bg-tertiary)';
|
||||
questionElement.style.borderColor = 'var(--color-accent)';
|
||||
});
|
||||
|
||||
questionElement.addEventListener('mouseleave', () => {
|
||||
questionElement.style.backgroundColor = 'var(--color-bg-secondary)';
|
||||
questionElement.style.borderColor = 'var(--color-border)';
|
||||
});
|
||||
|
||||
questionElement.addEventListener('click', () => {
|
||||
const currentText = aiInput.value.trim();
|
||||
const newText = currentText ? `${currentText}\n\n${question}` : question;
|
||||
aiInput.value = newText;
|
||||
aiInput.focus();
|
||||
|
||||
// Trigger input event to update character counter
|
||||
aiInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
|
||||
// Hide suggestions after adding
|
||||
showPromptingStatus('hidden');
|
||||
});
|
||||
|
||||
questionsList.appendChild(questionElement);
|
||||
});
|
||||
|
||||
showPromptingStatus('suggestions');
|
||||
}
|
||||
|
||||
async function triggerSmartPrompting() {
|
||||
const inputText = aiInput.value.trim();
|
||||
|
||||
if (inputText.length < 50) {
|
||||
showPromptingStatus('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel any previous enhancement call
|
||||
if (enhancementAbortController) {
|
||||
enhancementAbortController.abort();
|
||||
}
|
||||
|
||||
enhancementAbortController = new AbortController();
|
||||
|
||||
try {
|
||||
showPromptingStatus('analyzing');
|
||||
|
||||
const response = await fetch('/api/ai/enhance-input', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
input: inputText,
|
||||
taskId: `enhance_${Date.now()}`
|
||||
}),
|
||||
signal: enhancementAbortController.signal
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 429) {
|
||||
showPromptingStatus('rate-limited');
|
||||
} else {
|
||||
showPromptingStatus('error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.success && data.suggestions && data.suggestions.length > 0) {
|
||||
displaySuggestions(data.suggestions);
|
||||
} else {
|
||||
showPromptingStatus('hidden');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('Smart prompting failed:', error);
|
||||
showPromptingStatus('error');
|
||||
|
||||
// Auto-hide error after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (promptingStatus && promptingStatus.textContent.includes('nicht verfügbar')) {
|
||||
showPromptingStatus('hidden');
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function resetSmartPrompting() {
|
||||
clearTimeout(enhancementTimeout);
|
||||
if (enhancementAbortController) {
|
||||
enhancementAbortController.abort();
|
||||
}
|
||||
showPromptingStatus('hidden');
|
||||
}
|
||||
|
||||
toggleSwitch.addEventListener('click', () => {
|
||||
switchToMode(currentMode === 'workflow' ? 'tool' : 'workflow');
|
||||
});
|
||||
@ -267,6 +460,53 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
aiInput.addEventListener('input', updateCharacterCount);
|
||||
updateCharacterCount();
|
||||
|
||||
// Smart Prompting Input Handling
|
||||
aiInput.addEventListener('input', () => {
|
||||
const inputLength = aiInput.value.trim().length;
|
||||
|
||||
// Clear existing timeout
|
||||
clearTimeout(enhancementTimeout);
|
||||
|
||||
// Cancel any pending enhancement call
|
||||
if (enhancementAbortController) {
|
||||
enhancementAbortController.abort();
|
||||
}
|
||||
|
||||
// Hide suggestions if input is too short
|
||||
if (inputLength < 50) {
|
||||
showPromptingStatus('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide suggestions if they're currently visible (user is still typing)
|
||||
if (suggestionsVisible) {
|
||||
showPromptingStatus('hidden');
|
||||
}
|
||||
|
||||
// Show analyzing state after 1 second of typing (but only if long enough)
|
||||
setTimeout(() => {
|
||||
if (aiInput.value.trim().length >= 50) {
|
||||
showPromptingStatus('analyzing');
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Trigger AI enhancement after 4 seconds of no input
|
||||
enhancementTimeout = setTimeout(() => {
|
||||
if (aiInput.value.trim().length >= 50) {
|
||||
triggerSmartPrompting();
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
// Dismiss suggestions handler
|
||||
if (dismissSuggestions) {
|
||||
dismissSuggestions.addEventListener('click', () => {
|
||||
showPromptingStatus('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// ... [Keep all existing display functions - formatWorkflowSuggestion, renderBackgroundKnowledge, renderContextualAnalysis, displayWorkflowResults, displayToolResults] ...
|
||||
|
||||
function formatWorkflowSuggestion(text) {
|
||||
const numberedListPattern = /(\d+\.\s)/g;
|
||||
|
||||
@ -403,6 +643,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return html;
|
||||
}
|
||||
|
||||
// Keep existing display functions...
|
||||
function displayWorkflowResults(recommendation, originalQuery) {
|
||||
const toolsByPhase = {};
|
||||
|
||||
@ -765,6 +1006,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset smart prompting when submitting
|
||||
resetSmartPrompting();
|
||||
|
||||
const taskId = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
|
||||
|
||||
aiResults.style.display = 'none';
|
||||
|
@ -1923,3 +1923,99 @@ footer {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add to global.css - Smart Prompting Styles */
|
||||
.smart-prompting-container {
|
||||
margin-top: 1rem;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
.prompting-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.prompting-spinner {
|
||||
display: inline-block;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.suggested-questions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: var(--transition-fast);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.suggestion-item:hover {
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.suggestion-item .suggestion-number {
|
||||
color: var(--color-accent);
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.suggestion-item .suggestion-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.suggestion-item .suggestion-icon {
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.5rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Enhanced contextual analysis cards */
|
||||
.contextual-analysis-card {
|
||||
margin-bottom: 2rem;
|
||||
border-left: 4px solid;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
|
||||
.contextual-analysis-card:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.contextual-analysis-card.scenario {
|
||||
border-left-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.contextual-analysis-card.approach {
|
||||
border-left-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.contextual-analysis-card.critical {
|
||||
border-left-color: var(--color-warning);
|
||||
}
|
||||
|
||||
.analysis-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.analysis-header.scenario { color: var(--color-primary); }
|
||||
.analysis-header.approach { color: var(--color-accent); }
|
||||
.analysis-header.critical { color: var(--color-warning); }
|
Loading…
x
Reference in New Issue
Block a user