remove mindless dev comments
This commit is contained in:
parent
3973479ae4
commit
57c507915f
@ -211,12 +211,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script define:vars={{ tools, phases, domainAgnosticSoftware }}>
|
<script define:vars={{ tools, phases, domainAgnosticSoftware }}>
|
||||||
// ===================================================================
|
|
||||||
// SIMPLIFIED AI QUERY INTERFACE - CONSOLIDATED & MAINTAINABLE
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
// Enhanced AI Interface JavaScript - Fix for text rendering issues in AIQueryInterface.astro
|
|
||||||
// This code replaces the existing <script> section in AIQueryInterface.astro
|
|
||||||
|
|
||||||
class AIQueryInterface {
|
class AIQueryInterface {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -234,7 +228,6 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializeElements() {
|
initializeElements() {
|
||||||
// Core elements
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
input: document.getElementById('ai-query-input'),
|
input: document.getElementById('ai-query-input'),
|
||||||
submitBtn: document.getElementById('ai-submit-btn'),
|
submitBtn: document.getElementById('ai-submit-btn'),
|
||||||
@ -247,13 +240,11 @@ class AIQueryInterface {
|
|||||||
results: document.getElementById('ai-results'),
|
results: document.getElementById('ai-results'),
|
||||||
charCounter: document.getElementById('ai-char-counter'),
|
charCounter: document.getElementById('ai-char-counter'),
|
||||||
|
|
||||||
// Mode toggle
|
|
||||||
toggleSwitch: document.querySelector('.toggle-switch'),
|
toggleSwitch: document.querySelector('.toggle-switch'),
|
||||||
toggleSlider: document.querySelector('.toggle-slider'),
|
toggleSlider: document.querySelector('.toggle-slider'),
|
||||||
workflowLabel: document.getElementById('workflow-label'),
|
workflowLabel: document.getElementById('workflow-label'),
|
||||||
toolLabel: document.getElementById('tool-label'),
|
toolLabel: document.getElementById('tool-label'),
|
||||||
|
|
||||||
// Smart prompting
|
|
||||||
promptingContainer: document.getElementById('smart-prompting-container'),
|
promptingContainer: document.getElementById('smart-prompting-container'),
|
||||||
promptingStatus: document.getElementById('prompting-status'),
|
promptingStatus: document.getElementById('prompting-status'),
|
||||||
promptingSpinner: document.getElementById('prompting-spinner'),
|
promptingSpinner: document.getElementById('prompting-spinner'),
|
||||||
@ -262,7 +253,6 @@ class AIQueryInterface {
|
|||||||
dismissSuggestions: document.getElementById('dismiss-suggestions'),
|
dismissSuggestions: document.getElementById('dismiss-suggestions'),
|
||||||
smartHint: document.getElementById('smart-prompting-hint'),
|
smartHint: document.getElementById('smart-prompting-hint'),
|
||||||
|
|
||||||
// Queue status
|
|
||||||
queueStatus: document.getElementById('queue-status'),
|
queueStatus: document.getElementById('queue-status'),
|
||||||
queueLength: document.getElementById('queue-length'),
|
queueLength: document.getElementById('queue-length'),
|
||||||
estimatedTime: document.getElementById('estimated-time'),
|
estimatedTime: document.getElementById('estimated-time'),
|
||||||
@ -273,7 +263,6 @@ class AIQueryInterface {
|
|||||||
microTaskCounter: document.getElementById('micro-task-counter')
|
microTaskCounter: document.getElementById('micro-task-counter')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate critical elements
|
|
||||||
if (!this.elements.input || !this.elements.submitBtn || !this.elements.results) {
|
if (!this.elements.input || !this.elements.submitBtn || !this.elements.results) {
|
||||||
console.error('[AI Interface] Critical elements not found');
|
console.error('[AI Interface] Critical elements not found');
|
||||||
return false;
|
return false;
|
||||||
@ -282,12 +271,10 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
// Mode toggle
|
|
||||||
this.elements.toggleSwitch?.addEventListener('click', () => this.toggleMode());
|
this.elements.toggleSwitch?.addEventListener('click', () => this.toggleMode());
|
||||||
this.elements.workflowLabel?.addEventListener('click', () => this.setMode('workflow'));
|
this.elements.workflowLabel?.addEventListener('click', () => this.setMode('workflow'));
|
||||||
this.elements.toolLabel?.addEventListener('click', () => this.setMode('tool'));
|
this.elements.toolLabel?.addEventListener('click', () => this.setMode('tool'));
|
||||||
|
|
||||||
// Input handling
|
|
||||||
this.elements.input?.addEventListener('input', () => this.handleInputChange());
|
this.elements.input?.addEventListener('input', () => this.handleInputChange());
|
||||||
this.elements.input?.addEventListener('keydown', (e) => {
|
this.elements.input?.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||||
@ -296,17 +283,11 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Submit
|
|
||||||
this.elements.submitBtn?.addEventListener('click', () => this.handleSubmit());
|
this.elements.submitBtn?.addEventListener('click', () => this.handleSubmit());
|
||||||
|
|
||||||
// Smart prompting
|
|
||||||
this.elements.dismissSuggestions?.addEventListener('click', () => this.hideSmartPrompting());
|
this.elements.dismissSuggestions?.addEventListener('click', () => this.hideSmartPrompting());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// MODE MANAGEMENT
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
getModeConfig() {
|
getModeConfig() {
|
||||||
return {
|
return {
|
||||||
workflow: {
|
workflow: {
|
||||||
@ -345,7 +326,6 @@ class AIQueryInterface {
|
|||||||
if (this.elements.submitBtnText) this.elements.submitBtnText.textContent = config.submitText;
|
if (this.elements.submitBtnText) this.elements.submitBtnText.textContent = config.submitText;
|
||||||
if (this.elements.loadingText) this.elements.loadingText.textContent = config.loadingText;
|
if (this.elements.loadingText) this.elements.loadingText.textContent = config.loadingText;
|
||||||
|
|
||||||
// Update toggle UI
|
|
||||||
if (this.currentMode === 'workflow') {
|
if (this.currentMode === 'workflow') {
|
||||||
if (this.elements.toggleSlider) this.elements.toggleSlider.style.transform = 'translateX(0)';
|
if (this.elements.toggleSlider) this.elements.toggleSlider.style.transform = 'translateX(0)';
|
||||||
if (this.elements.toggleSwitch) this.elements.toggleSwitch.style.backgroundColor = 'var(--color-primary)';
|
if (this.elements.toggleSwitch) this.elements.toggleSwitch.style.backgroundColor = 'var(--color-primary)';
|
||||||
@ -371,8 +351,6 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Previous methods remain the same until displayResults...]
|
|
||||||
|
|
||||||
handleInputChange() {
|
handleInputChange() {
|
||||||
this.updateCharacterCount();
|
this.updateCharacterCount();
|
||||||
|
|
||||||
@ -493,10 +471,6 @@ class AIQueryInterface {
|
|||||||
if (this.elements.smartHint) this.showElement(this.elements.smartHint);
|
if (this.elements.smartHint) this.showElement(this.elements.smartHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// MAIN SUBMIT HANDLER
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
async handleSubmit() {
|
async handleSubmit() {
|
||||||
const query = this.elements.input.value.trim();
|
const query = this.elements.input.value.trim();
|
||||||
|
|
||||||
@ -512,14 +486,12 @@ class AIQueryInterface {
|
|||||||
|
|
||||||
const taskId = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
|
const taskId = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
|
||||||
|
|
||||||
// Reset state
|
|
||||||
this.hideSmartPrompting();
|
this.hideSmartPrompting();
|
||||||
this.hideResults();
|
this.hideResults();
|
||||||
this.hideError();
|
this.hideError();
|
||||||
this.showLoading();
|
this.showLoading();
|
||||||
this.startQueueMonitoring(taskId);
|
this.startQueueMonitoring(taskId);
|
||||||
|
|
||||||
// Disable submit
|
|
||||||
this.elements.submitBtn.disabled = true;
|
this.elements.submitBtn.disabled = true;
|
||||||
this.elements.submitBtnText.textContent = this.currentMode === 'workflow' ?
|
this.elements.submitBtnText.textContent = this.currentMode === 'workflow' ?
|
||||||
'Generiere Empfehlungen...' : 'Suche passende Methode...';
|
'Generiere Empfehlungen...' : 'Suche passende Methode...';
|
||||||
@ -545,7 +517,6 @@ class AIQueryInterface {
|
|||||||
throw new Error(data.error || 'Unknown error');
|
throw new Error(data.error || 'Unknown error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store and display results
|
|
||||||
this.currentRecommendation = data.recommendation;
|
this.currentRecommendation = data.recommendation;
|
||||||
this.displayResults(data.recommendation, query);
|
this.displayResults(data.recommendation, query);
|
||||||
|
|
||||||
@ -579,8 +550,6 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [Queue monitoring methods remain the same...]
|
|
||||||
|
|
||||||
startQueueMonitoring(taskId) {
|
startQueueMonitoring(taskId) {
|
||||||
if (this.elements.queueStatus) {
|
if (this.elements.queueStatus) {
|
||||||
this.showElement(this.elements.queueStatus);
|
this.showElement(this.elements.queueStatus);
|
||||||
@ -592,7 +561,6 @@ class AIQueryInterface {
|
|||||||
|
|
||||||
this.startMicroTaskProgress();
|
this.startMicroTaskProgress();
|
||||||
|
|
||||||
// Start polling queue status
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.updateQueueStatus(taskId);
|
this.updateQueueStatus(taskId);
|
||||||
this.statusInterval = setInterval(() => this.updateQueueStatus(taskId), 1000);
|
this.statusInterval = setInterval(() => this.updateQueueStatus(taskId), 1000);
|
||||||
@ -606,14 +574,12 @@ class AIQueryInterface {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Update queue display
|
|
||||||
if (this.elements.queueLength) this.elements.queueLength.textContent = data.queueLength || 0;
|
if (this.elements.queueLength) this.elements.queueLength.textContent = data.queueLength || 0;
|
||||||
if (this.elements.estimatedTime) {
|
if (this.elements.estimatedTime) {
|
||||||
this.elements.estimatedTime.textContent = data.estimatedWaitTime > 0 ?
|
this.elements.estimatedTime.textContent = data.estimatedWaitTime > 0 ?
|
||||||
this.formatDuration(data.estimatedWaitTime) : 'Verarbeitung läuft...';
|
this.formatDuration(data.estimatedWaitTime) : 'Verarbeitung läuft...';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update position
|
|
||||||
if (this.elements.positionBadge) {
|
if (this.elements.positionBadge) {
|
||||||
if (data.currentPosition) {
|
if (data.currentPosition) {
|
||||||
this.elements.positionBadge.textContent = data.currentPosition;
|
this.elements.positionBadge.textContent = data.currentPosition;
|
||||||
@ -622,7 +588,6 @@ class AIQueryInterface {
|
|||||||
this.elements.progressBar.style.width = `${progress}%`;
|
this.elements.progressBar.style.width = `${progress}%`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle processing states
|
|
||||||
const stateIcons = {
|
const stateIcons = {
|
||||||
processing: '⚡',
|
processing: '⚡',
|
||||||
completed: '✅',
|
completed: '✅',
|
||||||
@ -664,14 +629,12 @@ class AIQueryInterface {
|
|||||||
const steps = ['scenario', 'approach', 'considerations', 'tools', 'knowledge', 'final'];
|
const steps = ['scenario', 'approach', 'considerations', 'tools', 'knowledge', 'final'];
|
||||||
const stepElements = this.elements.microTaskProgress.querySelectorAll('.micro-step');
|
const stepElements = this.elements.microTaskProgress.querySelectorAll('.micro-step');
|
||||||
|
|
||||||
// Reset all steps
|
|
||||||
stepElements.forEach(step => {
|
stepElements.forEach(step => {
|
||||||
step.classList.remove('active', 'completed', 'failed');
|
step.classList.remove('active', 'completed', 'failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.microTaskInterval = setInterval(() => {
|
this.microTaskInterval = setInterval(() => {
|
||||||
if (this.currentMicroTaskStep < steps.length) {
|
if (this.currentMicroTaskStep < steps.length) {
|
||||||
// Complete previous step
|
|
||||||
if (this.currentMicroTaskStep > 0) {
|
if (this.currentMicroTaskStep > 0) {
|
||||||
const prevStep = this.elements.microTaskProgress.querySelector(`[data-step="${steps[this.currentMicroTaskStep - 1]}"]`);
|
const prevStep = this.elements.microTaskProgress.querySelector(`[data-step="${steps[this.currentMicroTaskStep - 1]}"]`);
|
||||||
if (prevStep) {
|
if (prevStep) {
|
||||||
@ -680,20 +643,17 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate current step
|
|
||||||
const currentStep = this.elements.microTaskProgress.querySelector(`[data-step="${steps[this.currentMicroTaskStep]}"]`);
|
const currentStep = this.elements.microTaskProgress.querySelector(`[data-step="${steps[this.currentMicroTaskStep]}"]`);
|
||||||
if (currentStep) {
|
if (currentStep) {
|
||||||
currentStep.classList.add('active');
|
currentStep.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update counter
|
|
||||||
if (this.elements.microTaskCounter) {
|
if (this.elements.microTaskCounter) {
|
||||||
this.elements.microTaskCounter.textContent = `${this.currentMicroTaskStep + 1}/${steps.length}`;
|
this.elements.microTaskCounter.textContent = `${this.currentMicroTaskStep + 1}/${steps.length}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentMicroTaskStep++;
|
this.currentMicroTaskStep++;
|
||||||
} else {
|
} else {
|
||||||
// Complete final step
|
|
||||||
const lastStep = this.elements.microTaskProgress.querySelector(`[data-step="${steps[steps.length - 1]}"]`);
|
const lastStep = this.elements.microTaskProgress.querySelector(`[data-step="${steps[steps.length - 1]}"]`);
|
||||||
if (lastStep) {
|
if (lastStep) {
|
||||||
lastStep.classList.remove('active');
|
lastStep.classList.remove('active');
|
||||||
@ -710,10 +670,6 @@ class AIQueryInterface {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// RESULTS DISPLAY - ENHANCED with proper text formatting
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
displayResults(recommendation, originalQuery) {
|
displayResults(recommendation, originalQuery) {
|
||||||
if (this.currentMode === 'workflow') {
|
if (this.currentMode === 'workflow') {
|
||||||
this.displayWorkflowResults(recommendation, originalQuery);
|
this.displayWorkflowResults(recommendation, originalQuery);
|
||||||
@ -725,7 +681,6 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
displayWorkflowResults(recommendation, originalQuery) {
|
displayWorkflowResults(recommendation, originalQuery) {
|
||||||
// Group tools by phase
|
|
||||||
const toolsByPhase = {};
|
const toolsByPhase = {};
|
||||||
const phaseOrder = phases.map(phase => phase.id);
|
const phaseOrder = phases.map(phase => phase.id);
|
||||||
const phaseNames = phases.reduce((acc, phase) => {
|
const phaseNames = phases.reduce((acc, phase) => {
|
||||||
@ -787,7 +742,6 @@ class AIQueryInterface {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ENHANCED: Fixed text rendering to prevent markdown/list interpretation
|
|
||||||
renderContextualAnalysis(recommendation, mode) {
|
renderContextualAnalysis(recommendation, mode) {
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
@ -1052,15 +1006,9 @@ class AIQueryInterface {
|
|||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// ENHANCED UTILITY METHODS with proper text sanitization
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
// CRITICAL: Sanitize text to prevent markdown interpretation
|
|
||||||
sanitizeText(text) {
|
sanitizeText(text) {
|
||||||
if (typeof text !== 'string') return '';
|
if (typeof text !== 'string') return '';
|
||||||
|
|
||||||
// Remove/escape potential markdown and HTML
|
|
||||||
return text
|
return text
|
||||||
// Remove markdown headers
|
// Remove markdown headers
|
||||||
.replace(/^#{1,6}\s+/gm, '')
|
.replace(/^#{1,6}\s+/gm, '')
|
||||||
@ -1096,13 +1044,11 @@ class AIQueryInterface {
|
|||||||
const hasValidProjectUrl = this.isToolHosted(tool);
|
const hasValidProjectUrl = this.isToolHosted(tool);
|
||||||
|
|
||||||
if (context === 'recommendation') {
|
if (context === 'recommendation') {
|
||||||
// For tool-recommendation elements, use simple class names
|
|
||||||
if (isMethod) return 'method';
|
if (isMethod) return 'method';
|
||||||
if (hasValidProjectUrl) return 'hosted';
|
if (hasValidProjectUrl) return 'hosted';
|
||||||
if (tool.license !== 'Proprietary') return 'oss';
|
if (tool.license !== 'Proprietary') return 'oss';
|
||||||
return '';
|
return '';
|
||||||
} else {
|
} else {
|
||||||
// For card elements, use card- prefixed class names
|
|
||||||
if (isMethod) return 'card-method';
|
if (isMethod) return 'card-method';
|
||||||
if (hasValidProjectUrl) return 'card-hosted';
|
if (hasValidProjectUrl) return 'card-hosted';
|
||||||
if (tool.license !== 'Proprietary') return 'card-oss';
|
if (tool.license !== 'Proprietary') return 'card-oss';
|
||||||
@ -1137,7 +1083,6 @@ class AIQueryInterface {
|
|||||||
return `${Math.ceil(ms / 60000)}m`;
|
return `${Math.ceil(ms / 60000)}m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// State management
|
|
||||||
showElement(element) {
|
showElement(element) {
|
||||||
if (element) {
|
if (element) {
|
||||||
element.style.display = 'block';
|
element.style.display = 'block';
|
||||||
@ -1179,14 +1124,9 @@ class AIQueryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// INITIALIZATION
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const aiInterface = new AIQueryInterface();
|
const aiInterface = new AIQueryInterface();
|
||||||
|
|
||||||
// Global restore function for compatibility
|
|
||||||
window.restoreAIResults = () => {
|
window.restoreAIResults = () => {
|
||||||
if (aiInterface.currentRecommendation && aiInterface.elements.results) {
|
if (aiInterface.currentRecommendation && aiInterface.elements.results) {
|
||||||
aiInterface.showResults();
|
aiInterface.showResults();
|
||||||
|
@ -77,41 +77,33 @@ const displayedScenarios = scenarios.slice(0, maxDisplayed);
|
|||||||
|
|
||||||
if (!mainSearchInput) return;
|
if (!mainSearchInput) return;
|
||||||
|
|
||||||
// Check if this scenario is already active (allow deselection)
|
|
||||||
if (clickedChip && clickedChip.classList.contains('active')) {
|
if (clickedChip && clickedChip.classList.contains('active')) {
|
||||||
// Deselect: clear search and remove active state
|
|
||||||
mainSearchInput.value = '';
|
mainSearchInput.value = '';
|
||||||
document.querySelectorAll('.suggestion-chip').forEach(chip => {
|
document.querySelectorAll('.suggestion-chip').forEach(chip => {
|
||||||
chip.classList.remove('active');
|
chip.classList.remove('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear the targeted search input too
|
|
||||||
const targetedInput = document.getElementById('targeted-search-input');
|
const targetedInput = document.getElementById('targeted-search-input');
|
||||||
if (targetedInput) {
|
if (targetedInput) {
|
||||||
targetedInput.value = '';
|
targetedInput.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger search to show all results
|
|
||||||
const inputEvent = new Event('input', { bubbles: true });
|
const inputEvent = new Event('input', { bubbles: true });
|
||||||
mainSearchInput.dispatchEvent(inputEvent);
|
mainSearchInput.dispatchEvent(inputEvent);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply new search
|
|
||||||
mainSearchInput.value = scenarioId;
|
mainSearchInput.value = scenarioId;
|
||||||
|
|
||||||
// Trigger existing search functionality
|
|
||||||
const inputEvent = new Event('input', { bubbles: true });
|
const inputEvent = new Event('input', { bubbles: true });
|
||||||
mainSearchInput.dispatchEvent(inputEvent);
|
mainSearchInput.dispatchEvent(inputEvent);
|
||||||
|
|
||||||
// Switch to grid view
|
|
||||||
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
|
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
|
||||||
if (gridToggle && !gridToggle.classList.contains('active')) {
|
if (gridToggle && !gridToggle.classList.contains('active')) {
|
||||||
gridToggle.click();
|
gridToggle.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visual feedback
|
|
||||||
document.querySelectorAll('.suggestion-chip').forEach(chip => {
|
document.querySelectorAll('.suggestion-chip').forEach(chip => {
|
||||||
chip.classList.remove('active');
|
chip.classList.remove('active');
|
||||||
});
|
});
|
||||||
@ -119,17 +111,14 @@ const displayedScenarios = scenarios.slice(0, maxDisplayed);
|
|||||||
clickedChip.classList.add('active');
|
clickedChip.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to results with better positioning
|
|
||||||
window.scrollToElementById('tools-grid');
|
window.scrollToElementById('tools-grid');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle showing all scenarios
|
|
||||||
window.toggleAllScenarios = function() {
|
window.toggleAllScenarios = function() {
|
||||||
const suggestionsContainer = document.getElementById('scenario-suggestions');
|
const suggestionsContainer = document.getElementById('scenario-suggestions');
|
||||||
const moreBtn = document.getElementById('more-scenarios-btn');
|
const moreBtn = document.getElementById('more-scenarios-btn');
|
||||||
|
|
||||||
if (!showingAllScenarios) {
|
if (!showingAllScenarios) {
|
||||||
// Show additional scenarios
|
|
||||||
const additionalScenarios = allScenarios.slice(maxDisplay);
|
const additionalScenarios = allScenarios.slice(maxDisplay);
|
||||||
additionalScenarios.forEach(scenario => {
|
additionalScenarios.forEach(scenario => {
|
||||||
const chip = document.createElement('div');
|
const chip = document.createElement('div');
|
||||||
@ -146,14 +135,12 @@ const displayedScenarios = scenarios.slice(0, maxDisplayed);
|
|||||||
moreBtn.textContent = 'Weniger anzeigen';
|
moreBtn.textContent = 'Weniger anzeigen';
|
||||||
showingAllScenarios = true;
|
showingAllScenarios = true;
|
||||||
} else {
|
} else {
|
||||||
// Hide additional scenarios
|
|
||||||
document.querySelectorAll('.additional-scenario').forEach(chip => chip.remove());
|
document.querySelectorAll('.additional-scenario').forEach(chip => chip.remove());
|
||||||
moreBtn.textContent = `+ ${allScenarios.length - maxDisplay} weitere Szenarien`;
|
moreBtn.textContent = `+ ${allScenarios.length - maxDisplay} weitere Szenarien`;
|
||||||
showingAllScenarios = false;
|
showingAllScenarios = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle targeted search input
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const targetedInput = document.getElementById('targeted-search-input');
|
const targetedInput = document.getElementById('targeted-search-input');
|
||||||
if (targetedInput) {
|
if (targetedInput) {
|
||||||
|
@ -5,7 +5,6 @@ const data = await getToolsData();
|
|||||||
const domains = data.domains;
|
const domains = data.domains;
|
||||||
const phases = data.phases;
|
const phases = data.phases;
|
||||||
|
|
||||||
// Extract unique values dynamically - NO HARD-CODING
|
|
||||||
const skillLevels = [...new Set(data.tools.map(tool => tool.skillLevel))].filter(Boolean).sort();
|
const skillLevels = [...new Set(data.tools.map(tool => tool.skillLevel))].filter(Boolean).sort();
|
||||||
const platforms = [...new Set(data.tools.flatMap(tool => tool.platforms || []))].filter(Boolean).sort();
|
const platforms = [...new Set(data.tools.flatMap(tool => tool.platforms || []))].filter(Boolean).sort();
|
||||||
const licenses = [...new Set(data.tools.map(tool => tool.license))].filter(Boolean).sort();
|
const licenses = [...new Set(data.tools.map(tool => tool.license))].filter(Boolean).sort();
|
||||||
@ -287,7 +286,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
window.toolsData = toolsData;
|
window.toolsData = toolsData;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Cache DOM elements
|
|
||||||
const elements = {
|
const elements = {
|
||||||
searchInput: document.getElementById('search-input'),
|
searchInput: document.getElementById('search-input'),
|
||||||
clearSearch: document.getElementById('clear-search'),
|
clearSearch: document.getElementById('clear-search'),
|
||||||
@ -312,25 +310,21 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
tags: document.getElementById('reset-tags'),
|
tags: document.getElementById('reset-tags'),
|
||||||
all: document.getElementById('reset-all-filters')
|
all: document.getElementById('reset-all-filters')
|
||||||
},
|
},
|
||||||
// Collapsible elements
|
|
||||||
toggleAdvanced: document.getElementById('toggle-advanced'),
|
toggleAdvanced: document.getElementById('toggle-advanced'),
|
||||||
toggleTags: document.getElementById('toggle-tags'),
|
toggleTags: document.getElementById('toggle-tags'),
|
||||||
advancedContent: document.getElementById('advanced-filters-content'),
|
advancedContent: document.getElementById('advanced-filters-content'),
|
||||||
tagContent: document.getElementById('tag-filters-content')
|
tagContent: document.getElementById('tag-filters-content')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Verify critical elements exist
|
|
||||||
if (!elements.searchInput || !elements.domainSelect) {
|
if (!elements.searchInput || !elements.domainSelect) {
|
||||||
console.error('Critical filter elements not found');
|
console.error('Critical filter elements not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// State management
|
|
||||||
let selectedTags = new Set();
|
let selectedTags = new Set();
|
||||||
let selectedPhase = '';
|
let selectedPhase = '';
|
||||||
let isTagCloudExpanded = false;
|
let isTagCloudExpanded = false;
|
||||||
|
|
||||||
// Collapsible functionality
|
|
||||||
function toggleCollapsible(toggleBtn, content, storageKey) {
|
function toggleCollapsible(toggleBtn, content, storageKey) {
|
||||||
const isCollapsed = toggleBtn.getAttribute('data-collapsed') === 'true';
|
const isCollapsed = toggleBtn.getAttribute('data-collapsed') === 'true';
|
||||||
const newState = !isCollapsed;
|
const newState = !isCollapsed;
|
||||||
@ -338,22 +332,17 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
toggleBtn.setAttribute('data-collapsed', newState.toString());
|
toggleBtn.setAttribute('data-collapsed', newState.toString());
|
||||||
|
|
||||||
if (newState) {
|
if (newState) {
|
||||||
// Collapse
|
|
||||||
content.classList.add('hidden');
|
content.classList.add('hidden');
|
||||||
toggleBtn.style.transform = 'rotate(0deg)';
|
toggleBtn.style.transform = 'rotate(0deg)';
|
||||||
} else {
|
} else {
|
||||||
// Expand
|
|
||||||
content.classList.remove('hidden');
|
content.classList.remove('hidden');
|
||||||
toggleBtn.style.transform = 'rotate(180deg)';
|
toggleBtn.style.transform = 'rotate(180deg)';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store state in sessionStorage
|
|
||||||
sessionStorage.setItem(storageKey, newState.toString());
|
sessionStorage.setItem(storageKey, newState.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize collapsible sections (collapsed by default)
|
|
||||||
function initializeCollapsible() {
|
function initializeCollapsible() {
|
||||||
// Advanced filters
|
|
||||||
const advancedCollapsed = sessionStorage.getItem('advanced-collapsed') !== 'false';
|
const advancedCollapsed = sessionStorage.getItem('advanced-collapsed') !== 'false';
|
||||||
elements.toggleAdvanced.setAttribute('data-collapsed', advancedCollapsed.toString());
|
elements.toggleAdvanced.setAttribute('data-collapsed', advancedCollapsed.toString());
|
||||||
if (advancedCollapsed) {
|
if (advancedCollapsed) {
|
||||||
@ -364,7 +353,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
elements.toggleAdvanced.style.transform = 'rotate(180deg)';
|
elements.toggleAdvanced.style.transform = 'rotate(180deg)';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag filters
|
|
||||||
const tagsCollapsed = sessionStorage.getItem('tags-collapsed') !== 'false';
|
const tagsCollapsed = sessionStorage.getItem('tags-collapsed') !== 'false';
|
||||||
elements.toggleTags.setAttribute('data-collapsed', tagsCollapsed.toString());
|
elements.toggleTags.setAttribute('data-collapsed', tagsCollapsed.toString());
|
||||||
if (tagsCollapsed) {
|
if (tagsCollapsed) {
|
||||||
@ -376,7 +364,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to check if tool is hosted
|
|
||||||
function isToolHosted(tool) {
|
function isToolHosted(tool) {
|
||||||
return tool.projectUrl !== undefined &&
|
return tool.projectUrl !== undefined &&
|
||||||
tool.projectUrl !== null &&
|
tool.projectUrl !== null &&
|
||||||
@ -384,7 +371,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
tool.projectUrl.trim() !== "";
|
tool.projectUrl.trim() !== "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize tag cloud
|
|
||||||
function initTagCloud() {
|
function initTagCloud() {
|
||||||
const visibleCount = 20;
|
const visibleCount = 20;
|
||||||
elements.tagCloudItems.forEach((item, index) => {
|
elements.tagCloudItems.forEach((item, index) => {
|
||||||
@ -394,7 +380,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle tag cloud expansion
|
|
||||||
function toggleTagCloud() {
|
function toggleTagCloud() {
|
||||||
isTagCloudExpanded = !isTagCloudExpanded;
|
isTagCloudExpanded = !isTagCloudExpanded;
|
||||||
const visibleCount = 20;
|
const visibleCount = 20;
|
||||||
@ -428,7 +413,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter tag cloud based on search
|
|
||||||
function filterTagCloud() {
|
function filterTagCloud() {
|
||||||
const searchTerm = elements.searchInput.value.toLowerCase();
|
const searchTerm = elements.searchInput.value.toLowerCase();
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
@ -458,7 +442,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
elements.tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
|
elements.tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update selected tags display
|
|
||||||
function updateSelectedTags() {
|
function updateSelectedTags() {
|
||||||
if (selectedTags.size === 0) {
|
if (selectedTags.size === 0) {
|
||||||
elements.selectedTags.style.display = 'none';
|
elements.selectedTags.style.display = 'none';
|
||||||
@ -478,7 +461,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add event listeners for remove buttons
|
|
||||||
elements.selectedTags.querySelectorAll('.remove-tag').forEach(btn => {
|
elements.selectedTags.querySelectorAll('.remove-tag').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const tag = btn.getAttribute('data-tag');
|
const tag = btn.getAttribute('data-tag');
|
||||||
@ -487,10 +469,8 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add/remove tags - FIXED: Update ALL matching elements
|
|
||||||
function addTag(tag) {
|
function addTag(tag) {
|
||||||
selectedTags.add(tag);
|
selectedTags.add(tag);
|
||||||
// FIXED: Use querySelectorAll to update ALL matching tag elements
|
|
||||||
document.querySelectorAll(`[data-tag="${tag}"]`).forEach(element => {
|
document.querySelectorAll(`[data-tag="${tag}"]`).forEach(element => {
|
||||||
element.classList.add('active');
|
element.classList.add('active');
|
||||||
});
|
});
|
||||||
@ -500,7 +480,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
|
|
||||||
function removeTag(tag) {
|
function removeTag(tag) {
|
||||||
selectedTags.delete(tag);
|
selectedTags.delete(tag);
|
||||||
// FIXED: Use querySelectorAll to update ALL matching tag elements
|
|
||||||
document.querySelectorAll(`[data-tag="${tag}"]`).forEach(element => {
|
document.querySelectorAll(`[data-tag="${tag}"]`).forEach(element => {
|
||||||
element.classList.remove('active');
|
element.classList.remove('active');
|
||||||
});
|
});
|
||||||
@ -508,7 +487,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
filterTools();
|
filterTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update results counter
|
|
||||||
function updateResultsCounter(count) {
|
function updateResultsCounter(count) {
|
||||||
const total = window.toolsData.length;
|
const total = window.toolsData.length;
|
||||||
elements.resultsCounter.textContent = count === total
|
elements.resultsCounter.textContent = count === total
|
||||||
@ -516,7 +494,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
: `${count} von ${total} Tools`;
|
: `${count} von ${total} Tools`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main filter function
|
|
||||||
function filterTools() {
|
function filterTools() {
|
||||||
const searchTerm = elements.searchInput.value.trim().toLowerCase();
|
const searchTerm = elements.searchInput.value.trim().toLowerCase();
|
||||||
const selectedDomain = elements.domainSelect.value;
|
const selectedDomain = elements.domainSelect.value;
|
||||||
@ -529,11 +506,9 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
const hostedOnly = elements.hostedOnly.checked;
|
const hostedOnly = elements.hostedOnly.checked;
|
||||||
const knowledgebaseOnly = elements.knowledgebaseOnly.checked;
|
const knowledgebaseOnly = elements.knowledgebaseOnly.checked;
|
||||||
|
|
||||||
// Use phase from either dropdown or button selection
|
|
||||||
const activePhase = selectedPhaseFromSelect || selectedPhase;
|
const activePhase = selectedPhaseFromSelect || selectedPhase;
|
||||||
|
|
||||||
const filtered = window.toolsData.filter(tool => {
|
const filtered = window.toolsData.filter(tool => {
|
||||||
// Search filter
|
|
||||||
if (searchTerm && !(
|
if (searchTerm && !(
|
||||||
tool.name.toLowerCase().includes(searchTerm) ||
|
tool.name.toLowerCase().includes(searchTerm) ||
|
||||||
tool.description.toLowerCase().includes(searchTerm) ||
|
tool.description.toLowerCase().includes(searchTerm) ||
|
||||||
@ -542,52 +517,42 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domain filter
|
|
||||||
if (selectedDomain && !(tool.domains || []).includes(selectedDomain)) {
|
if (selectedDomain && !(tool.domains || []).includes(selectedDomain)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase filter
|
|
||||||
if (activePhase && !(tool.phases || []).includes(activePhase)) {
|
if (activePhase && !(tool.phases || []).includes(activePhase)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type filter
|
|
||||||
if (selectedType && tool.type !== selectedType) {
|
if (selectedType && tool.type !== selectedType) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skill level filter
|
|
||||||
if (selectedSkill && tool.skillLevel !== selectedSkill) {
|
if (selectedSkill && tool.skillLevel !== selectedSkill) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Platform filter
|
|
||||||
if (selectedPlatform && !(tool.platforms || []).includes(selectedPlatform)) {
|
if (selectedPlatform && !(tool.platforms || []).includes(selectedPlatform)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// License filter - NO MORE HARD-CODED LOGIC
|
|
||||||
if (selectedLicense && tool.license !== selectedLicense) {
|
if (selectedLicense && tool.license !== selectedLicense) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Access type filter
|
|
||||||
if (selectedAccess && tool.accessType !== selectedAccess) {
|
if (selectedAccess && tool.accessType !== selectedAccess) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hosted only filter (CC24-Server tools)
|
|
||||||
if (hostedOnly && !isToolHosted(tool)) {
|
if (hostedOnly && !isToolHosted(tool)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Knowledgebase only filter
|
|
||||||
if (knowledgebaseOnly && !tool.knowledgebase) {
|
if (knowledgebaseOnly && !tool.knowledgebase) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag filter
|
|
||||||
if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => (tool.tags || []).includes(tag))) {
|
if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => (tool.tags || []).includes(tag))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -595,18 +560,15 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply search prioritization if there's a search term
|
|
||||||
const finalResults = searchTerm && window.prioritizeSearchResults
|
const finalResults = searchTerm && window.prioritizeSearchResults
|
||||||
? window.prioritizeSearchResults(filtered, searchTerm)
|
? window.prioritizeSearchResults(filtered, searchTerm)
|
||||||
: filtered;
|
: filtered;
|
||||||
|
|
||||||
updateResultsCounter(finalResults.length);
|
updateResultsCounter(finalResults.length);
|
||||||
|
|
||||||
// Dispatch event for other components
|
|
||||||
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: finalResults }));
|
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: finalResults }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset functions
|
|
||||||
function resetPrimaryFilters() {
|
function resetPrimaryFilters() {
|
||||||
elements.domainSelect.value = '';
|
elements.domainSelect.value = '';
|
||||||
elements.phaseSelect.value = '';
|
elements.phaseSelect.value = '';
|
||||||
@ -627,7 +589,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
|
|
||||||
function resetTags() {
|
function resetTags() {
|
||||||
selectedTags.clear();
|
selectedTags.clear();
|
||||||
// FIXED: Update ALL tag elements
|
|
||||||
document.querySelectorAll('.tag-cloud-item').forEach(item => {
|
document.querySelectorAll('.tag-cloud-item').forEach(item => {
|
||||||
item.classList.remove('active');
|
item.classList.remove('active');
|
||||||
});
|
});
|
||||||
@ -644,7 +605,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
filterTagCloud();
|
filterTagCloud();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
elements.searchInput.addEventListener('input', (e) => {
|
elements.searchInput.addEventListener('input', (e) => {
|
||||||
const hasValue = e.target.value.length > 0;
|
const hasValue = e.target.value.length > 0;
|
||||||
elements.clearSearch.classList.toggle('hidden', !hasValue);
|
elements.clearSearch.classList.toggle('hidden', !hasValue);
|
||||||
@ -685,7 +645,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const view = btn.getAttribute('data-view');
|
const view = btn.getAttribute('data-view');
|
||||||
|
|
||||||
// Simple toggle like the old version
|
|
||||||
elements.viewToggles.forEach(b => {
|
elements.viewToggles.forEach(b => {
|
||||||
b.classList.toggle('active', b.getAttribute('data-view') === view);
|
b.classList.toggle('active', b.getAttribute('data-view') === view);
|
||||||
});
|
});
|
||||||
@ -701,13 +660,11 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset button listeners
|
|
||||||
elements.resetButtons.primary.addEventListener('click', resetPrimaryFilters);
|
elements.resetButtons.primary.addEventListener('click', resetPrimaryFilters);
|
||||||
elements.resetButtons.advanced.addEventListener('click', resetAdvancedFilters);
|
elements.resetButtons.advanced.addEventListener('click', resetAdvancedFilters);
|
||||||
elements.resetButtons.tags.addEventListener('click', resetTags);
|
elements.resetButtons.tags.addEventListener('click', resetTags);
|
||||||
elements.resetButtons.all.addEventListener('click', resetAllFilters);
|
elements.resetButtons.all.addEventListener('click', resetAllFilters);
|
||||||
|
|
||||||
// Collapsible toggle listeners
|
|
||||||
elements.toggleAdvanced.addEventListener('click', () => {
|
elements.toggleAdvanced.addEventListener('click', () => {
|
||||||
toggleCollapsible(elements.toggleAdvanced, elements.advancedContent, 'advanced-collapsed');
|
toggleCollapsible(elements.toggleAdvanced, elements.advancedContent, 'advanced-collapsed');
|
||||||
});
|
});
|
||||||
@ -716,11 +673,9 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
toggleCollapsible(elements.toggleTags, elements.tagContent, 'tags-collapsed');
|
toggleCollapsible(elements.toggleTags, elements.tagContent, 'tags-collapsed');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Expose functions globally for backwards compatibility
|
|
||||||
window.clearTagFilters = resetTags;
|
window.clearTagFilters = resetTags;
|
||||||
window.clearAllFilters = resetAllFilters;
|
window.clearAllFilters = resetAllFilters;
|
||||||
|
|
||||||
// Initialize
|
|
||||||
initializeCollapsible();
|
initializeCollapsible();
|
||||||
initTagCloud();
|
initTagCloud();
|
||||||
filterTagCloud();
|
filterTagCloud();
|
||||||
|
@ -716,7 +716,6 @@ domains.forEach((domain: any) => {
|
|||||||
const primaryModal = document.getElementById('tool-details-primary');
|
const primaryModal = document.getElementById('tool-details-primary');
|
||||||
const secondaryModal = document.getElementById('tool-details-secondary');
|
const secondaryModal = document.getElementById('tool-details-secondary');
|
||||||
|
|
||||||
// Debounce rapid calls
|
|
||||||
if (window.modalHideInProgress) return;
|
if (window.modalHideInProgress) return;
|
||||||
window.modalHideInProgress = true;
|
window.modalHideInProgress = true;
|
||||||
|
|
||||||
@ -753,19 +752,15 @@ domains.forEach((domain: any) => {
|
|||||||
if (contributeButtonSecondary) contributeButtonSecondary.style.display = 'none';
|
if (contributeButtonSecondary) contributeButtonSecondary.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consolidated state checking with safety checks
|
|
||||||
const primaryActive = primaryModal && primaryModal.classList.contains('active');
|
const primaryActive = primaryModal && primaryModal.classList.contains('active');
|
||||||
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
|
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
|
||||||
|
|
||||||
// Update overlay and body classes atomically
|
|
||||||
if (!primaryActive && !secondaryActive) {
|
if (!primaryActive && !secondaryActive) {
|
||||||
if (overlay) overlay.classList.remove('active');
|
if (overlay) overlay.classList.remove('active');
|
||||||
document.body.classList.remove('modals-side-by-side');
|
document.body.classList.remove('modals-side-by-side');
|
||||||
} else if (primaryActive && secondaryActive) {
|
} else if (primaryActive && secondaryActive) {
|
||||||
// Both active - ensure side-by-side class
|
|
||||||
document.body.classList.add('modals-side-by-side');
|
document.body.classList.add('modals-side-by-side');
|
||||||
} else {
|
} else {
|
||||||
// Only one active - remove side-by-side class
|
|
||||||
document.body.classList.remove('modals-side-by-side');
|
document.body.classList.remove('modals-side-by-side');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
2
src/env.d.ts
vendored
2
src/env.d.ts
vendored
@ -27,12 +27,10 @@ declare global {
|
|||||||
showIfAuthenticated: (selector: string, context?: string) => Promise<void>;
|
showIfAuthenticated: (selector: string, context?: string) => Promise<void>;
|
||||||
setupAuthButtons: (selector?: string) => void;
|
setupAuthButtons: (selector?: string) => void;
|
||||||
|
|
||||||
// Consolidated scroll utilities
|
|
||||||
scrollToElement: (element: Element | null, options?: ScrollIntoViewOptions) => void;
|
scrollToElement: (element: Element | null, options?: ScrollIntoViewOptions) => void;
|
||||||
scrollToElementById: (elementId: string, options?: ScrollIntoViewOptions) => void;
|
scrollToElementById: (elementId: string, options?: ScrollIntoViewOptions) => void;
|
||||||
scrollToElementBySelector: (selector: string, options?: ScrollIntoViewOptions) => void;
|
scrollToElementBySelector: (selector: string, options?: ScrollIntoViewOptions) => void;
|
||||||
|
|
||||||
// Additional global functions that might be called
|
|
||||||
applyScenarioSearch?: (scenarioId: string) => void;
|
applyScenarioSearch?: (scenarioId: string) => void;
|
||||||
selectPhase?: (phase: string) => void;
|
selectPhase?: (phase: string) => void;
|
||||||
selectApproach?: (approach: string) => void;
|
selectApproach?: (approach: string) => void;
|
||||||
|
@ -21,7 +21,6 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Move utility functions OUTSIDE DOMContentLoaded to avoid race conditions
|
|
||||||
function createToolSlug(toolName) {
|
function createToolSlug(toolName) {
|
||||||
if (!toolName || typeof toolName !== 'string') {
|
if (!toolName || typeof toolName !== 'string') {
|
||||||
console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName);
|
console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName);
|
||||||
@ -51,16 +50,14 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
tool.projectUrl.trim() !== "";
|
tool.projectUrl.trim() !== "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consolidated scrolling utility - also moved outside DOMContentLoaded
|
|
||||||
function scrollToElement(element, options = {}) {
|
function scrollToElement(element, options = {}) {
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
// Calculate target position manually to avoid double-scroll
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const headerHeight = document.querySelector('nav')?.offsetHeight || 80;
|
const headerHeight = document.querySelector('nav')?.offsetHeight || 80;
|
||||||
const elementRect = element.getBoundingClientRect();
|
const elementRect = element.getBoundingClientRect();
|
||||||
const absoluteElementTop = elementRect.top + window.pageYOffset;
|
const absoluteElementTop = elementRect.top + window.pageYOffset;
|
||||||
const targetPosition = absoluteElementTop - headerHeight - 20; // Adjust this 20 as needed
|
const targetPosition = absoluteElementTop - headerHeight - 20;
|
||||||
|
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: targetPosition,
|
top: targetPosition,
|
||||||
@ -69,7 +66,6 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience functions for common scroll targets
|
|
||||||
function scrollToElementById(elementId, options = {}) {
|
function scrollToElementById(elementId, options = {}) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
scrollToElement(element, options);
|
scrollToElement(element, options);
|
||||||
@ -80,7 +76,6 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
scrollToElement(element, options);
|
scrollToElement(element, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple search prioritization - exact tag matches first
|
|
||||||
function prioritizeSearchResults(tools, searchTerm) {
|
function prioritizeSearchResults(tools, searchTerm) {
|
||||||
if (!searchTerm || !searchTerm.trim()) {
|
if (!searchTerm || !searchTerm.trim()) {
|
||||||
return tools;
|
return tools;
|
||||||
@ -95,16 +90,13 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
const aExactTag = aTagsLower.includes(lowerSearchTerm);
|
const aExactTag = aTagsLower.includes(lowerSearchTerm);
|
||||||
const bExactTag = bTagsLower.includes(lowerSearchTerm);
|
const bExactTag = bTagsLower.includes(lowerSearchTerm);
|
||||||
|
|
||||||
// If one has exact tag match and other doesn't, prioritize the exact match
|
|
||||||
if (aExactTag && !bExactTag) return -1;
|
if (aExactTag && !bExactTag) return -1;
|
||||||
if (!aExactTag && bExactTag) return 1;
|
if (!aExactTag && bExactTag) return 1;
|
||||||
|
|
||||||
// Otherwise maintain original order (or sort by name as secondary)
|
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach to window immediately - BEFORE DOMContentLoaded
|
|
||||||
(window as any).createToolSlug = createToolSlug;
|
(window as any).createToolSlug = createToolSlug;
|
||||||
(window as any).findToolByIdentifier = findToolByIdentifier;
|
(window as any).findToolByIdentifier = findToolByIdentifier;
|
||||||
(window as any).isToolHosted = isToolHosted;
|
(window as any).isToolHosted = isToolHosted;
|
||||||
|
@ -14,14 +14,13 @@ function getEnv(key: string): string {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the analyzer AI for smart prompting (smaller, faster model)
|
|
||||||
const AI_ENDPOINT = getEnv('AI_ANALYZER_ENDPOINT');
|
const AI_ENDPOINT = getEnv('AI_ANALYZER_ENDPOINT');
|
||||||
const AI_API_KEY = getEnv('AI_ANALYZER_API_KEY');
|
const AI_API_KEY = getEnv('AI_ANALYZER_API_KEY');
|
||||||
const AI_MODEL = getEnv('AI_ANALYZER_MODEL');
|
const AI_MODEL = getEnv('AI_ANALYZER_MODEL');
|
||||||
|
|
||||||
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
||||||
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
|
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
|
||||||
const RATE_LIMIT_MAX = 5; // 5 enhancement requests per minute per user
|
const RATE_LIMIT_MAX = 5;
|
||||||
|
|
||||||
function sanitizeInput(input: string): string {
|
function sanitizeInput(input: string): string {
|
||||||
return input
|
return input
|
||||||
@ -30,7 +29,7 @@ function sanitizeInput(input: string): string {
|
|||||||
.replace(/\b(system|assistant|user)\s*[:]/gi, '[ROLE_REMOVED]')
|
.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]')
|
.replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
|
||||||
.trim()
|
.trim()
|
||||||
.slice(0, 1000); // Shorter limit for enhancement
|
.slice(0, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkRateLimit(userId: string): boolean {
|
function checkRateLimit(userId: string): boolean {
|
||||||
@ -59,7 +58,6 @@ function cleanupExpiredRateLimits() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up expired limits every 5 minutes
|
|
||||||
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
|
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
|
||||||
|
|
||||||
function createEnhancementPrompt(input: string): string {
|
function createEnhancementPrompt(input: string): string {
|
||||||
@ -140,7 +138,6 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
],
|
],
|
||||||
max_tokens: 300,
|
max_tokens: 300,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
// Enhanced: Better parameters for consistent forensics output
|
|
||||||
top_p: 0.9,
|
top_p: 0.9,
|
||||||
frequency_penalty: 0.2,
|
frequency_penalty: 0.2,
|
||||||
presence_penalty: 0.1
|
presence_penalty: 0.1
|
||||||
@ -171,27 +168,23 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
throw new Error('Response is not an array');
|
throw new Error('Response is not an array');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced validation and cleaning for forensics context
|
|
||||||
questions = questions
|
questions = questions
|
||||||
.filter(q => typeof q === 'string' && q.length > 20 && q.length < 200) // More appropriate length for forensics questions
|
.filter(q => typeof q === 'string' && q.length > 20 && q.length < 200)
|
||||||
.filter(q => q.includes('?')) // Must be a question
|
.filter(q => q.includes('?'))
|
||||||
.filter(q => {
|
.filter(q => {
|
||||||
// Enhanced: Filter for forensics-relevant questions
|
|
||||||
const forensicsTerms = ['forensisch', 'log', 'dump', 'image', 'artefakt', 'evidence', 'incident', 'system', 'netzwerk', 'zeitraum', 'verfügbar'];
|
const forensicsTerms = ['forensisch', 'log', 'dump', 'image', 'artefakt', 'evidence', 'incident', 'system', 'netzwerk', 'zeitraum', 'verfügbar'];
|
||||||
const lowerQ = q.toLowerCase();
|
const lowerQ = q.toLowerCase();
|
||||||
return forensicsTerms.some(term => lowerQ.includes(term));
|
return forensicsTerms.some(term => lowerQ.includes(term));
|
||||||
})
|
})
|
||||||
.map(q => q.trim())
|
.map(q => q.trim())
|
||||||
.slice(0, 3); // Max 3 questions
|
.slice(0, 3);
|
||||||
|
|
||||||
// If no valid forensics questions, return empty array (means input is complete)
|
|
||||||
if (questions.length === 0) {
|
if (questions.length === 0) {
|
||||||
questions = [];
|
questions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to parse enhancement response:', aiContent);
|
console.error('Failed to parse enhancement response:', aiContent);
|
||||||
// If parsing fails, assume input is complete enough
|
|
||||||
questions = [];
|
questions = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ interface KnowledgebaseContributionData {
|
|||||||
|
|
||||||
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
||||||
const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1 hour
|
const RATE_LIMIT_WINDOW = 60 * 60 * 1000; // 1 hour
|
||||||
const RATE_LIMIT_MAX = 3; // Max 3 submissions per hour per user
|
const RATE_LIMIT_MAX = 3;
|
||||||
|
|
||||||
function checkRateLimit(userEmail: string): boolean {
|
function checkRateLimit(userEmail: string): boolean {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// src/pages/api/upload/media.ts (UPDATED - Using consolidated API responses)
|
// src/pages/api/upload/media.ts
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { withAPIAuth } from '../../../utils/auth.js';
|
import { withAPIAuth } from '../../../utils/auth.js';
|
||||||
import { apiResponse, apiError, apiServerError, apiSpecial, handleAPIRequest } from '../../../utils/api.js';
|
import { apiResponse, apiError, apiServerError, apiSpecial, handleAPIRequest } from '../../../utils/api.js';
|
||||||
|
@ -173,29 +173,24 @@ const phases = data.phases;
|
|||||||
<script define:vars={{ toolsData: data.tools, phases: data.phases }}>
|
<script define:vars={{ toolsData: data.tools, phases: data.phases }}>
|
||||||
window.toolsData = toolsData;
|
window.toolsData = toolsData;
|
||||||
|
|
||||||
// Approach selection functionality
|
|
||||||
window.selectApproach = function(approach) {
|
window.selectApproach = function(approach) {
|
||||||
console.log(`Selected approach: ${approach}`);
|
console.log(`Selected approach: ${approach}`);
|
||||||
|
|
||||||
// Hide any existing results
|
|
||||||
const aiResults = document.getElementById('ai-results');
|
const aiResults = document.getElementById('ai-results');
|
||||||
if (aiResults) aiResults.style.display = 'none';
|
if (aiResults) aiResults.style.display = 'none';
|
||||||
|
|
||||||
// Visual feedback for selection
|
|
||||||
document.querySelectorAll('.approach-card').forEach(card => {
|
document.querySelectorAll('.approach-card').forEach(card => {
|
||||||
card.classList.remove('selected');
|
card.classList.remove('selected');
|
||||||
});
|
});
|
||||||
document.querySelector(`.approach-card.${approach}`).classList.add('selected');
|
document.querySelector(`.approach-card.${approach}`).classList.add('selected');
|
||||||
|
|
||||||
if (approach === 'methodology') {
|
if (approach === 'methodology') {
|
||||||
// Show NIST methodology section
|
|
||||||
const methodologySection = document.getElementById('methodology-section');
|
const methodologySection = document.getElementById('methodology-section');
|
||||||
if (methodologySection) {
|
if (methodologySection) {
|
||||||
methodologySection.classList.add('active');
|
methodologySection.classList.add('active');
|
||||||
window.scrollToElementById('methodology-section');
|
window.scrollToElementById('methodology-section');
|
||||||
}
|
}
|
||||||
} else if (approach === 'targeted') {
|
} else if (approach === 'targeted') {
|
||||||
// Show targeted scenarios section
|
|
||||||
const targetedSection = document.getElementById('targeted-section');
|
const targetedSection = document.getElementById('targeted-section');
|
||||||
if (targetedSection) {
|
if (targetedSection) {
|
||||||
targetedSection.classList.add('active');
|
targetedSection.classList.add('active');
|
||||||
@ -204,34 +199,28 @@ const phases = data.phases;
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Phase selection function (integrates with existing ToolFilters)
|
|
||||||
window.selectPhase = function(phase) {
|
window.selectPhase = function(phase) {
|
||||||
console.log(`Selected NIST phase: ${phase}`);
|
console.log(`Selected NIST phase: ${phase}`);
|
||||||
|
|
||||||
// Remove active class from all phase cards
|
|
||||||
document.querySelectorAll('.phase-card').forEach(card => {
|
document.querySelectorAll('.phase-card').forEach(card => {
|
||||||
card.classList.remove('active');
|
card.classList.remove('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add active class to selected phase card
|
|
||||||
const selectedCard = document.querySelector(`.phase-card.phase-${phase}`);
|
const selectedCard = document.querySelector(`.phase-card.phase-${phase}`);
|
||||||
if (selectedCard) {
|
if (selectedCard) {
|
||||||
selectedCard.classList.add('active');
|
selectedCard.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use existing phase filter functionality
|
|
||||||
const existingPhaseButton = document.querySelector(`[data-phase="${phase}"]`);
|
const existingPhaseButton = document.querySelector(`[data-phase="${phase}"]`);
|
||||||
if (existingPhaseButton && !existingPhaseButton.classList.contains('active')) {
|
if (existingPhaseButton && !existingPhaseButton.classList.contains('active')) {
|
||||||
existingPhaseButton.click();
|
existingPhaseButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to grid view to show results
|
|
||||||
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
|
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
|
||||||
if (gridToggle && !gridToggle.classList.contains('active')) {
|
if (gridToggle && !gridToggle.classList.contains('active')) {
|
||||||
gridToggle.click();
|
gridToggle.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to results using consolidated utility
|
|
||||||
window.scrollToElementById('tools-grid');
|
window.scrollToElementById('tools-grid');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -296,7 +285,6 @@ const phases = data.phases;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add smooth scrolling to filters section after layout settles
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.scrollToElementById('filters-section');
|
window.scrollToElementById('filters-section');
|
||||||
}, 150);
|
}, 150);
|
||||||
@ -369,7 +357,6 @@ const phases = data.phases;
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Tool card not found in grid:', toolName);
|
console.warn('Tool card not found in grid:', toolName);
|
||||||
// Fallback to tools grid
|
|
||||||
window.scrollToElementById('tools-grid');
|
window.scrollToElementById('tools-grid');
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
@ -407,7 +394,6 @@ const phases = data.phases;
|
|||||||
window.scrollToElement(firstMatch);
|
window.scrollToElement(firstMatch);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Tool chip not found in matrix:', toolName);
|
console.warn('Tool chip not found in matrix:', toolName);
|
||||||
// Fallback to matrix container
|
|
||||||
window.scrollToElementById('matrix-container');
|
window.scrollToElementById('matrix-container');
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// src/utils/aiPipeline.ts - FIXED: Critical error corrections
|
// src/utils/aiPipeline.ts
|
||||||
|
|
||||||
import { getCompressedToolsDataForAI } from './dataService.js';
|
import { getCompressedToolsDataForAI } from './dataService.js';
|
||||||
import { embeddingsService, type EmbeddingData } from './embeddings.js';
|
import { embeddingsService, type EmbeddingData } from './embeddings.js';
|
||||||
@ -36,7 +36,6 @@ interface AnalysisContext {
|
|||||||
filteredData: any;
|
filteredData: any;
|
||||||
contextHistory: string[];
|
contextHistory: string[];
|
||||||
|
|
||||||
// FIXED: Add max context length tracking
|
|
||||||
maxContextLength: number;
|
maxContextLength: number;
|
||||||
currentContextLength: number;
|
currentContextLength: number;
|
||||||
|
|
||||||
@ -47,7 +46,6 @@ interface AnalysisContext {
|
|||||||
selectedTools?: Array<{tool: any, phase: string, priority: string, justification?: string}>;
|
selectedTools?: Array<{tool: any, phase: string, priority: string, justification?: string}>;
|
||||||
backgroundKnowledge?: Array<{concept: any, relevance: string}>;
|
backgroundKnowledge?: Array<{concept: any, relevance: string}>;
|
||||||
|
|
||||||
// FIXED: Add seen tools tracking to prevent duplicates
|
|
||||||
seenToolNames: Set<string>;
|
seenToolNames: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +56,6 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
private similarityThreshold: number;
|
private similarityThreshold: number;
|
||||||
private microTaskDelay: number;
|
private microTaskDelay: number;
|
||||||
|
|
||||||
// FIXED: Add proper token management
|
|
||||||
private maxContextTokens: number;
|
private maxContextTokens: number;
|
||||||
private maxPromptTokens: number;
|
private maxPromptTokens: number;
|
||||||
|
|
||||||
@ -74,7 +71,6 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
this.similarityThreshold = 0.3;
|
this.similarityThreshold = 0.3;
|
||||||
this.microTaskDelay = parseInt(process.env.AI_MICRO_TASK_DELAY_MS || '500', 10);
|
this.microTaskDelay = parseInt(process.env.AI_MICRO_TASK_DELAY_MS || '500', 10);
|
||||||
|
|
||||||
// FIXED: Token management
|
|
||||||
this.maxContextTokens = parseInt(process.env.AI_MAX_CONTEXT_TOKENS || '4000', 10);
|
this.maxContextTokens = parseInt(process.env.AI_MAX_CONTEXT_TOKENS || '4000', 10);
|
||||||
this.maxPromptTokens = parseInt(process.env.AI_MAX_PROMPT_TOKENS || '1500', 10);
|
this.maxPromptTokens = parseInt(process.env.AI_MAX_PROMPT_TOKENS || '1500', 10);
|
||||||
}
|
}
|
||||||
@ -87,27 +83,22 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Estimate token count (rough approximation)
|
|
||||||
private estimateTokens(text: string): number {
|
private estimateTokens(text: string): number {
|
||||||
return Math.ceil(text.length / 4); // Rough estimate: 4 chars per token
|
return Math.ceil(text.length / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Manage context history with token limits
|
|
||||||
private addToContextHistory(context: AnalysisContext, newEntry: string): void {
|
private addToContextHistory(context: AnalysisContext, newEntry: string): void {
|
||||||
const entryTokens = this.estimateTokens(newEntry);
|
const entryTokens = this.estimateTokens(newEntry);
|
||||||
|
|
||||||
// Add new entry
|
|
||||||
context.contextHistory.push(newEntry);
|
context.contextHistory.push(newEntry);
|
||||||
context.currentContextLength += entryTokens;
|
context.currentContextLength += entryTokens;
|
||||||
|
|
||||||
// Prune old entries if exceeding limits
|
|
||||||
while (context.currentContextLength > this.maxContextTokens && context.contextHistory.length > 1) {
|
while (context.currentContextLength > this.maxContextTokens && context.contextHistory.length > 1) {
|
||||||
const removed = context.contextHistory.shift()!;
|
const removed = context.contextHistory.shift()!;
|
||||||
context.currentContextLength -= this.estimateTokens(removed);
|
context.currentContextLength -= this.estimateTokens(removed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Safe JSON parsing with validation
|
|
||||||
private safeParseJSON(jsonString: string, fallback: any = null): any {
|
private safeParseJSON(jsonString: string, fallback: any = null): any {
|
||||||
try {
|
try {
|
||||||
const cleaned = jsonString
|
const cleaned = jsonString
|
||||||
@ -124,7 +115,6 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Add tool deduplication
|
|
||||||
private addToolToSelection(context: AnalysisContext, tool: any, phase: string, priority: string, justification?: string): boolean {
|
private addToolToSelection(context: AnalysisContext, tool: any, phase: string, priority: string, justification?: string): boolean {
|
||||||
if (context.seenToolNames.has(tool.name)) {
|
if (context.seenToolNames.has(tool.name)) {
|
||||||
console.log(`[AI PIPELINE] Skipping duplicate tool: ${tool.name}`);
|
console.log(`[AI PIPELINE] Skipping duplicate tool: ${tool.name}`);
|
||||||
@ -166,8 +156,7 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
|
|
||||||
console.log(`[IMPROVED PIPELINE] Embeddings found: ${toolNames.size} tools, ${conceptNames.size} concepts`);
|
console.log(`[IMPROVED PIPELINE] Embeddings found: ${toolNames.size} tools, ${conceptNames.size} concepts`);
|
||||||
|
|
||||||
// FIXED: Use your expected flow - get full data of embeddings results
|
if (toolNames.size >= 15) {
|
||||||
if (toolNames.size >= 15) { // Reasonable threshold for quality
|
|
||||||
candidateTools = toolsData.tools.filter((tool: any) => toolNames.has(tool.name));
|
candidateTools = toolsData.tools.filter((tool: any) => toolNames.has(tool.name));
|
||||||
candidateConcepts = toolsData.concepts.filter((concept: any) => conceptNames.has(concept.name));
|
candidateConcepts = toolsData.concepts.filter((concept: any) => conceptNames.has(concept.name));
|
||||||
selectionMethod = 'embeddings_candidates';
|
selectionMethod = 'embeddings_candidates';
|
||||||
@ -186,7 +175,6 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
selectionMethod = 'full_dataset';
|
selectionMethod = 'full_dataset';
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: NOW AI ANALYZES FULL DATA of the candidates
|
|
||||||
console.log(`[IMPROVED PIPELINE] AI will analyze FULL DATA of ${candidateTools.length} candidate tools`);
|
console.log(`[IMPROVED PIPELINE] AI will analyze FULL DATA of ${candidateTools.length} candidate tools`);
|
||||||
const finalSelection = await this.aiSelectionWithFullData(userQuery, candidateTools, candidateConcepts, mode, selectionMethod);
|
const finalSelection = await this.aiSelectionWithFullData(userQuery, candidateTools, candidateConcepts, mode, selectionMethod);
|
||||||
|
|
||||||
@ -199,8 +187,6 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/utils/aiPipeline.ts - FIXED: De-biased AI selection prompt
|
|
||||||
|
|
||||||
private async aiSelectionWithFullData(
|
private async aiSelectionWithFullData(
|
||||||
userQuery: string,
|
userQuery: string,
|
||||||
candidateTools: any[],
|
candidateTools: any[],
|
||||||
@ -212,7 +198,6 @@ class ImprovedMicroTaskAIPipeline {
|
|||||||
? 'The user wants a COMPREHENSIVE WORKFLOW with multiple tools/methods across different phases. Select 15-25 tools that cover the full investigation lifecycle.'
|
? 'The user wants a COMPREHENSIVE WORKFLOW with multiple tools/methods across different phases. Select 15-25 tools that cover the full investigation lifecycle.'
|
||||||
: 'The user wants SPECIFIC TOOLS/METHODS that directly solve their particular problem. Select 3-8 tools that are most relevant and effective.';
|
: 'The user wants SPECIFIC TOOLS/METHODS that directly solve their particular problem. Select 3-8 tools that are most relevant and effective.';
|
||||||
|
|
||||||
// FIXED: Give AI the COMPLETE tool data, not truncated
|
|
||||||
const toolsWithFullData = candidateTools.map((tool: any) => ({
|
const toolsWithFullData = candidateTools.map((tool: any) => ({
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
type: tool.type,
|
type: tool.type,
|
||||||
@ -307,7 +292,7 @@ Respond with ONLY this JSON format:
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.callAI(prompt, 2500); // More tokens for bias prevention logic
|
const response = await this.callAI(prompt, 2500);
|
||||||
|
|
||||||
const result = this.safeParseJSON(response, null);
|
const result = this.safeParseJSON(response, null);
|
||||||
|
|
||||||
@ -325,7 +310,6 @@ Respond with ONLY this JSON format:
|
|||||||
console.log(`[IMPROVED PIPELINE] AI selected: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`);
|
console.log(`[IMPROVED PIPELINE] AI selected: ${result.selectedTools.length} tools, ${result.selectedConcepts.length} concepts`);
|
||||||
console.log(`[IMPROVED PIPELINE] AI reasoning: ${result.reasoning}`);
|
console.log(`[IMPROVED PIPELINE] AI reasoning: ${result.reasoning}`);
|
||||||
|
|
||||||
// Return the actual tool/concept objects
|
|
||||||
const selectedTools = candidateTools.filter(tool => result.selectedTools.includes(tool.name));
|
const selectedTools = candidateTools.filter(tool => result.selectedTools.includes(tool.name));
|
||||||
const selectedConcepts = candidateConcepts.filter(concept => result.selectedConcepts.includes(concept.name));
|
const selectedConcepts = candidateConcepts.filter(concept => result.selectedConcepts.includes(concept.name));
|
||||||
|
|
||||||
@ -339,7 +323,6 @@ Respond with ONLY this JSON format:
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[IMPROVED PIPELINE] AI selection failed:', error);
|
console.error('[IMPROVED PIPELINE] AI selection failed:', error);
|
||||||
|
|
||||||
// Emergency fallback with bias awareness
|
|
||||||
console.log('[IMPROVED PIPELINE] Using emergency keyword-based selection');
|
console.log('[IMPROVED PIPELINE] Using emergency keyword-based selection');
|
||||||
return this.emergencyKeywordSelection(userQuery, candidateTools, candidateConcepts, mode);
|
return this.emergencyKeywordSelection(userQuery, candidateTools, candidateConcepts, mode);
|
||||||
}
|
}
|
||||||
@ -349,7 +332,6 @@ Respond with ONLY this JSON format:
|
|||||||
const queryLower = userQuery.toLowerCase();
|
const queryLower = userQuery.toLowerCase();
|
||||||
const keywords = queryLower.split(/\s+/).filter(word => word.length > 3);
|
const keywords = queryLower.split(/\s+/).filter(word => word.length > 3);
|
||||||
|
|
||||||
// Score tools based on keyword matches in full data
|
|
||||||
const scoredTools = candidateTools.map(tool => {
|
const scoredTools = candidateTools.map(tool => {
|
||||||
const toolText = (
|
const toolText = (
|
||||||
tool.name + ' ' +
|
tool.name + ' ' +
|
||||||
@ -385,18 +367,15 @@ Respond with ONLY this JSON format:
|
|||||||
private async callMicroTaskAI(prompt: string, context: AnalysisContext, maxTokens: number = 300): Promise<MicroTaskResult> {
|
private async callMicroTaskAI(prompt: string, context: AnalysisContext, maxTokens: number = 300): Promise<MicroTaskResult> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
// FIXED: Build context prompt with token management
|
|
||||||
let contextPrompt = prompt;
|
let contextPrompt = prompt;
|
||||||
if (context.contextHistory.length > 0) {
|
if (context.contextHistory.length > 0) {
|
||||||
const contextSection = `BISHERIGE ANALYSE:\n${context.contextHistory.join('\n\n')}\n\nAKTUELLE AUFGABE:\n`;
|
const contextSection = `BISHERIGE ANALYSE:\n${context.contextHistory.join('\n\n')}\n\nAKTUELLE AUFGABE:\n`;
|
||||||
const combinedPrompt = contextSection + prompt;
|
const combinedPrompt = contextSection + prompt;
|
||||||
|
|
||||||
// Check if combined prompt exceeds limits
|
|
||||||
if (this.estimateTokens(combinedPrompt) <= this.maxPromptTokens) {
|
if (this.estimateTokens(combinedPrompt) <= this.maxPromptTokens) {
|
||||||
contextPrompt = combinedPrompt;
|
contextPrompt = combinedPrompt;
|
||||||
} else {
|
} else {
|
||||||
console.warn('[AI PIPELINE] Context too long, using prompt only');
|
console.warn('[AI PIPELINE] Context too long, using prompt only');
|
||||||
// Could implement smarter context truncation here
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,7 +430,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen, Aufzählun
|
|||||||
context.problemAnalysis = result.content;
|
context.problemAnalysis = result.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Use new context management
|
|
||||||
this.addToContextHistory(context, `${isWorkflow ? 'Szenario' : 'Problem'}-Analyse: ${result.content.slice(0, 200)}...`);
|
this.addToContextHistory(context, `${isWorkflow ? 'Szenario' : 'Problem'}-Analyse: ${result.content.slice(0, 200)}...`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,7 +537,6 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format (kein zusätzlicher Text):
|
|||||||
const result = await this.callMicroTaskAI(prompt, context, 450);
|
const result = await this.callMicroTaskAI(prompt, context, 450);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// FIXED: Safe JSON parsing with validation
|
|
||||||
const selections = this.safeParseJSON(result.content, []);
|
const selections = this.safeParseJSON(result.content, []);
|
||||||
|
|
||||||
if (Array.isArray(selections)) {
|
if (Array.isArray(selections)) {
|
||||||
@ -570,7 +547,6 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format (kein zusätzlicher Text):
|
|||||||
validSelections.forEach((sel: any) => {
|
validSelections.forEach((sel: any) => {
|
||||||
const tool = phaseTools.find((t: any) => t.name === sel.toolName);
|
const tool = phaseTools.find((t: any) => t.name === sel.toolName);
|
||||||
if (tool) {
|
if (tool) {
|
||||||
// FIXED: Use deduplication helper
|
|
||||||
this.addToolToSelection(context, tool, phase.id, sel.priority, sel.justification);
|
this.addToolToSelection(context, tool, phase.id, sel.priority, sel.justification);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -603,7 +579,6 @@ Bewerten Sie nach forensischen Standards und antworten Sie AUSSCHLIESSLICH mit d
|
|||||||
const result = await this.callMicroTaskAI(prompt, context, 650);
|
const result = await this.callMicroTaskAI(prompt, context, 650);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// FIXED: Safe JSON parsing
|
|
||||||
const evaluation = this.safeParseJSON(result.content, {
|
const evaluation = this.safeParseJSON(result.content, {
|
||||||
suitability_score: 'medium',
|
suitability_score: 'medium',
|
||||||
detailed_explanation: 'Evaluation failed',
|
detailed_explanation: 'Evaluation failed',
|
||||||
@ -613,7 +588,6 @@ Bewerten Sie nach forensischen Standards und antworten Sie AUSSCHLIESSLICH mit d
|
|||||||
alternatives: ''
|
alternatives: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
// FIXED: Use deduplication helper
|
|
||||||
this.addToolToSelection(context, {
|
this.addToolToSelection(context, {
|
||||||
...tool,
|
...tool,
|
||||||
evaluation: {
|
evaluation: {
|
||||||
@ -661,7 +635,6 @@ Antworten Sie AUSSCHLIESSLICH mit diesem JSON-Format:
|
|||||||
const result = await this.callMicroTaskAI(prompt, context, 400);
|
const result = await this.callMicroTaskAI(prompt, context, 400);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// FIXED: Safe JSON parsing
|
|
||||||
const selections = this.safeParseJSON(result.content, []);
|
const selections = this.safeParseJSON(result.content, []);
|
||||||
|
|
||||||
if (Array.isArray(selections)) {
|
if (Array.isArray(selections)) {
|
||||||
@ -745,7 +718,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
|
|||||||
const toolsData = await getCompressedToolsDataForAI();
|
const toolsData = await getCompressedToolsDataForAI();
|
||||||
const filteredData = await this.getIntelligentCandidates(userQuery, toolsData, mode);
|
const filteredData = await this.getIntelligentCandidates(userQuery, toolsData, mode);
|
||||||
|
|
||||||
// FIXED: Initialize context with proper state management
|
|
||||||
const context: AnalysisContext = {
|
const context: AnalysisContext = {
|
||||||
userQuery,
|
userQuery,
|
||||||
mode,
|
mode,
|
||||||
@ -753,7 +725,7 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
|
|||||||
contextHistory: [],
|
contextHistory: [],
|
||||||
maxContextLength: this.maxContextTokens,
|
maxContextLength: this.maxContextTokens,
|
||||||
currentContextLength: 0,
|
currentContextLength: 0,
|
||||||
seenToolNames: new Set<string>() // FIXED: Add deduplication tracking
|
seenToolNames: new Set<string>()
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`[IMPROVED PIPELINE] Starting micro-tasks with ${filteredData.tools.length} tools visible`);
|
console.log(`[IMPROVED PIPELINE] Starting micro-tasks with ${filteredData.tools.length} tools visible`);
|
||||||
@ -777,7 +749,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
|
|||||||
|
|
||||||
// Task 4: Tool Selection/Evaluation (mode-dependent)
|
// Task 4: Tool Selection/Evaluation (mode-dependent)
|
||||||
if (mode === 'workflow') {
|
if (mode === 'workflow') {
|
||||||
// Select tools for each phase
|
|
||||||
const phases = toolsData.phases || [];
|
const phases = toolsData.phases || [];
|
||||||
for (const phase of phases) {
|
for (const phase of phases) {
|
||||||
const toolSelectionResult = await this.selectToolsForPhase(context, phase);
|
const toolSelectionResult = await this.selectToolsForPhase(context, phase);
|
||||||
@ -785,7 +756,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
|
|||||||
await this.delay(this.microTaskDelay);
|
await this.delay(this.microTaskDelay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Evaluate top 3 tools for specific problem
|
|
||||||
const topTools = filteredData.tools.slice(0, 3);
|
const topTools = filteredData.tools.slice(0, 3);
|
||||||
for (let i = 0; i < topTools.length; i++) {
|
for (let i = 0; i < topTools.length; i++) {
|
||||||
const evaluationResult = await this.evaluateSpecificTool(context, topTools[i], i + 1);
|
const evaluationResult = await this.evaluateSpecificTool(context, topTools[i], i + 1);
|
||||||
@ -831,7 +801,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build recommendation (same structure but using fixed context)
|
|
||||||
private buildRecommendation(context: AnalysisContext, mode: string, finalContent: string): any {
|
private buildRecommendation(context: AnalysisContext, mode: string, finalContent: string): any {
|
||||||
const isWorkflow = mode === 'workflow';
|
const isWorkflow = mode === 'workflow';
|
||||||
|
|
||||||
@ -876,7 +845,6 @@ WICHTIG: Antworten Sie NUR in fließendem deutschen Text ohne Listen oder Markdo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global instance
|
|
||||||
const aiPipeline = new ImprovedMicroTaskAIPipeline();
|
const aiPipeline = new ImprovedMicroTaskAIPipeline();
|
||||||
|
|
||||||
export { aiPipeline, type AnalysisResult };
|
export { aiPipeline, type AnalysisResult };
|
@ -111,7 +111,6 @@ export const apiSpecial = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const apiWithHeaders = {
|
export const apiWithHeaders = {
|
||||||
// Success with custom headers (e.g., Set-Cookie)
|
|
||||||
successWithHeaders: (data: any, headers: Record<string, string>): Response =>
|
successWithHeaders: (data: any, headers: Record<string, string>): Response =>
|
||||||
createAPIResponse(data, 200, headers),
|
createAPIResponse(data, 200, headers),
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user