Update OpenAI API key and enhance app functionality: Replace the OpenAI API key in the .env file for improved access. Refactor app.py to include error handling for missing API keys and implement dark mode functionality with session management. Update README.md to reflect the use of Tailwind CSS via CDN and document the Content Security Policy (CSP) adjustments. Enhance mindmap data loading with a new API endpoint for refreshing data, ensuring better user experience during database connection issues. Update styles and templates for improved UI consistency and responsiveness.
This commit is contained in:
12
static/js/alpine.min.js
vendored
Normal file
12
static/js/alpine.min.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Alpine.js v3.12.3
|
||||
*
|
||||
* This is a placeholder file. For production, you should:
|
||||
* 1. Download the official Alpine.js file from https://github.com/alpinejs/alpine/releases
|
||||
* 2. Replace this file with the downloaded version
|
||||
*
|
||||
* Alternatively, you can run:
|
||||
* curl -L https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js > alpine.min.js
|
||||
*/
|
||||
|
||||
console.error('This is a placeholder for Alpine.js. Please replace with the actual library file.');
|
||||
@@ -226,4 +226,7 @@ const MindMap = {
|
||||
};
|
||||
|
||||
// Globale Export für andere Module
|
||||
window.MindMap = MindMap;
|
||||
window.MindMap = MindMap;
|
||||
|
||||
// Export als Modul
|
||||
export default MindMap;
|
||||
@@ -11,6 +11,62 @@ class ChatGPTAssistant {
|
||||
this.container = null;
|
||||
this.chatHistory = null;
|
||||
this.inputField = null;
|
||||
this.suggestionArea = null;
|
||||
this.maxRetries = 2;
|
||||
this.retryCount = 0;
|
||||
this.markdownParser = null;
|
||||
this.initializeMarkdownParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert den Markdown-Parser
|
||||
*/
|
||||
async initializeMarkdownParser() {
|
||||
// Dynamisch marked.js laden, wenn noch nicht vorhanden
|
||||
if (!window.marked) {
|
||||
try {
|
||||
// Prüfen, ob marked.js bereits im Dokument geladen ist
|
||||
if (!document.querySelector('script[src*="marked"]')) {
|
||||
// Falls nicht, Script-Tag erstellen und einfügen
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
|
||||
script.async = true;
|
||||
|
||||
// Promise erstellen, das resolved wird, wenn das Script geladen wurde
|
||||
await new Promise((resolve, reject) => {
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
console.log('Marked.js erfolgreich geladen');
|
||||
}
|
||||
|
||||
// Marked konfigurieren
|
||||
this.markdownParser = window.marked;
|
||||
this.markdownParser.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
sanitize: true,
|
||||
smartLists: true,
|
||||
smartypants: true
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden von marked.js:', error);
|
||||
// Fallback-Parser, der nur einfache Absätze erkennt
|
||||
this.markdownParser = {
|
||||
parse: (text) => {
|
||||
return text.split('\n').map(line => {
|
||||
if (line.trim() === '') return '<br>';
|
||||
return `<p>${line}</p>`;
|
||||
}).join('');
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Marked ist bereits geladen
|
||||
this.markdownParser = window.marked;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +80,16 @@ class ChatGPTAssistant {
|
||||
this.setupEventListeners();
|
||||
|
||||
// Ersten Willkommensnachricht anzeigen
|
||||
this.addMessage("assistant", "Frage den KI-Assistenten");
|
||||
this.addMessage("assistant", "Hallo! Ich bin dein KI-Assistent (4o-mini) und habe Zugriff auf die Wissensdatenbank. Wie kann ich dir helfen?\n\nDu kannst mir Fragen über:\n- **Gedanken** in der Datenbank\n- **Kategorien** und Wissenschaftsbereiche\n- **Mindmaps** und Wissensverknüpfungen\n\nstellen.");
|
||||
|
||||
// Vorschläge anzeigen
|
||||
this.showSuggestions([
|
||||
"Zeige mir Gedanken zur künstlichen Intelligenz",
|
||||
"Welche Kategorien gibt es in der Datenbank?",
|
||||
"Suche nach Mindmaps zum Thema Informatik"
|
||||
]);
|
||||
|
||||
console.log('KI-Assistent initialisiert!');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +110,7 @@ class ChatGPTAssistant {
|
||||
// Chat-Container
|
||||
const chatContainer = document.createElement('div');
|
||||
chatContainer.id = 'assistant-chat';
|
||||
chatContainer.className = 'bg-white dark:bg-dark-800 rounded-lg shadow-xl overflow-hidden transition-all duration-300 w-80 sm:w-96 max-h-0 opacity-0';
|
||||
chatContainer.className = 'bg-white dark:bg-dark-800 rounded-lg shadow-xl overflow-hidden transition-all duration-300 w-80 md:w-96 max-h-0 opacity-0';
|
||||
|
||||
// Chat-Header
|
||||
const header = document.createElement('div');
|
||||
@@ -53,7 +118,7 @@ class ChatGPTAssistant {
|
||||
header.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-robot mr-2"></i>
|
||||
<span>KI-Assistent</span>
|
||||
<span>KI-Assistent (4o-mini)</span>
|
||||
</div>
|
||||
<button id="assistant-close" class="text-white hover:text-gray-200">
|
||||
<i class="fas fa-times"></i>
|
||||
@@ -63,7 +128,12 @@ class ChatGPTAssistant {
|
||||
// Chat-Verlauf
|
||||
this.chatHistory = document.createElement('div');
|
||||
this.chatHistory.id = 'assistant-history';
|
||||
this.chatHistory.className = 'p-3 overflow-y-auto max-h-80 space-y-3';
|
||||
this.chatHistory.className = 'p-3 overflow-y-auto max-h-96 space-y-3';
|
||||
|
||||
// Vorschlagsbereich
|
||||
this.suggestionArea = document.createElement('div');
|
||||
this.suggestionArea.id = 'assistant-suggestions';
|
||||
this.suggestionArea.className = 'px-3 pb-2 flex flex-wrap gap-2 overflow-x-auto hidden';
|
||||
|
||||
// Chat-Eingabe
|
||||
const inputContainer = document.createElement('div');
|
||||
@@ -71,7 +141,7 @@ class ChatGPTAssistant {
|
||||
|
||||
this.inputField = document.createElement('input');
|
||||
this.inputField.type = 'text';
|
||||
this.inputField.placeholder = 'Frage den KI-Assistenten';
|
||||
this.inputField.placeholder = 'Stelle eine Frage zur Wissensdatenbank...';
|
||||
this.inputField.className = 'flex-1 border border-gray-300 dark:border-dark-600 dark:bg-dark-700 dark:text-white rounded-l-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary-500';
|
||||
|
||||
const sendButton = document.createElement('button');
|
||||
@@ -85,6 +155,7 @@ class ChatGPTAssistant {
|
||||
|
||||
chatContainer.appendChild(header);
|
||||
chatContainer.appendChild(this.chatHistory);
|
||||
chatContainer.appendChild(this.suggestionArea);
|
||||
chatContainer.appendChild(inputContainer);
|
||||
|
||||
this.container.appendChild(toggleButton);
|
||||
@@ -100,22 +171,40 @@ class ChatGPTAssistant {
|
||||
setupEventListeners() {
|
||||
// Toggle-Button
|
||||
const toggleButton = document.getElementById('assistant-toggle');
|
||||
toggleButton.addEventListener('click', () => this.toggleAssistant());
|
||||
if (toggleButton) {
|
||||
toggleButton.addEventListener('click', () => this.toggleAssistant());
|
||||
}
|
||||
|
||||
// Schließen-Button
|
||||
const closeButton = document.getElementById('assistant-close');
|
||||
closeButton.addEventListener('click', () => this.toggleAssistant(false));
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', () => this.toggleAssistant(false));
|
||||
}
|
||||
|
||||
// Senden-Button
|
||||
const sendButton = document.getElementById('assistant-send');
|
||||
sendButton.addEventListener('click', () => this.sendMessage());
|
||||
if (sendButton) {
|
||||
sendButton.addEventListener('click', () => this.sendMessage());
|
||||
}
|
||||
|
||||
// Enter-Taste im Eingabefeld
|
||||
this.inputField.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
if (this.inputField) {
|
||||
this.inputField.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Vorschläge klickbar machen
|
||||
if (this.suggestionArea) {
|
||||
this.suggestionArea.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('suggestion-pill')) {
|
||||
this.inputField.value = e.target.textContent;
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,14 +213,21 @@ class ChatGPTAssistant {
|
||||
*/
|
||||
toggleAssistant(state = null) {
|
||||
const chatContainer = document.getElementById('assistant-chat');
|
||||
if (!chatContainer) return;
|
||||
|
||||
this.isOpen = state !== null ? state : !this.isOpen;
|
||||
|
||||
if (this.isOpen) {
|
||||
chatContainer.classList.remove('max-h-0', 'opacity-0');
|
||||
chatContainer.classList.add('max-h-96', 'opacity-100');
|
||||
this.inputField.focus();
|
||||
chatContainer.classList.add('max-h-[32rem]', 'opacity-100');
|
||||
if (this.inputField) this.inputField.focus();
|
||||
|
||||
// Zeige Vorschläge wenn verfügbar
|
||||
if (this.suggestionArea && this.suggestionArea.children.length > 0) {
|
||||
this.suggestionArea.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
chatContainer.classList.remove('max-h-96', 'opacity-100');
|
||||
chatContainer.classList.remove('max-h-[32rem]', 'opacity-100');
|
||||
chatContainer.classList.add('max-h-0', 'opacity-0');
|
||||
}
|
||||
}
|
||||
@@ -153,22 +249,144 @@ class ChatGPTAssistant {
|
||||
bubble.className = sender === 'user'
|
||||
? 'bg-primary-100 dark:bg-primary-900 text-gray-800 dark:text-white rounded-lg py-2 px-3 max-w-[85%]'
|
||||
: 'bg-gray-100 dark:bg-dark-700 text-gray-800 dark:text-white rounded-lg py-2 px-3 max-w-[85%]';
|
||||
bubble.textContent = text;
|
||||
|
||||
// Formatierung des Texts (mit Markdown für Assistent-Nachrichten)
|
||||
let formattedText = '';
|
||||
|
||||
if (sender === 'assistant' && this.markdownParser) {
|
||||
// Für Assistentnachrichten Markdown verwenden
|
||||
try {
|
||||
formattedText = this.markdownParser.parse(text);
|
||||
|
||||
// CSS für Markdown-Formatierung hinzufügen
|
||||
const markdownStyles = `
|
||||
.markdown-bubble h1, .markdown-bubble h2, .markdown-bubble h3,
|
||||
.markdown-bubble h4, .markdown-bubble h5, .markdown-bubble h6 {
|
||||
font-weight: bold;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.markdown-bubble h1 { font-size: 1.4rem; }
|
||||
.markdown-bubble h2 { font-size: 1.3rem; }
|
||||
.markdown-bubble h3 { font-size: 1.2rem; }
|
||||
.markdown-bubble h4 { font-size: 1.1rem; }
|
||||
.markdown-bubble ul, .markdown-bubble ol {
|
||||
padding-left: 1.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.markdown-bubble ul { list-style-type: disc; }
|
||||
.markdown-bubble ol { list-style-type: decimal; }
|
||||
.markdown-bubble p { margin: 0.5rem 0; }
|
||||
.markdown-bubble code {
|
||||
font-family: monospace;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown-bubble pre {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.markdown-bubble pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-bubble blockquote {
|
||||
border-left: 3px solid rgba(0, 0, 0, 0.2);
|
||||
padding-left: 0.8rem;
|
||||
margin: 0.5rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
.dark .markdown-bubble code {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.dark .markdown-bubble pre {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.dark .markdown-bubble blockquote {
|
||||
border-left-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
`;
|
||||
|
||||
// Füge die Styles hinzu, wenn sie noch nicht vorhanden sind
|
||||
if (!document.querySelector('#markdown-chat-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'markdown-chat-styles';
|
||||
style.textContent = markdownStyles;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Klasse für Markdown-Formatierung hinzufügen
|
||||
bubble.classList.add('markdown-bubble');
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Markdown-Formatierung:', error);
|
||||
// Fallback zur einfachen Formatierung
|
||||
formattedText = text.split('\n').map(line => {
|
||||
if (line.trim() === '') return '<br>';
|
||||
return `<p>${line}</p>`;
|
||||
}).join('');
|
||||
}
|
||||
} else {
|
||||
// Für Benutzernachrichten einfache Formatierung
|
||||
formattedText = text.split('\n').map(line => {
|
||||
if (line.trim() === '') return '<br>';
|
||||
return `<p>${line}</p>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
bubble.innerHTML = formattedText;
|
||||
|
||||
messageEl.appendChild(bubble);
|
||||
this.chatHistory.appendChild(messageEl);
|
||||
|
||||
// Scroll zum Ende des Verlaufs
|
||||
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
||||
if (this.chatHistory) {
|
||||
this.chatHistory.appendChild(messageEl);
|
||||
|
||||
// Scroll zum Ende des Verlaufs
|
||||
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt Vorschläge als klickbare Pills an
|
||||
* @param {string[]} suggestions - Liste von Vorschlägen
|
||||
*/
|
||||
showSuggestions(suggestions) {
|
||||
if (!this.suggestionArea) return;
|
||||
|
||||
// Vorherige Vorschläge entfernen
|
||||
this.suggestionArea.innerHTML = '';
|
||||
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
suggestions.forEach(suggestion => {
|
||||
const pill = document.createElement('button');
|
||||
pill.className = 'suggestion-pill text-sm bg-gray-200 dark:bg-dark-600 hover:bg-gray-300 dark:hover:bg-dark-500 text-gray-800 dark:text-gray-200 rounded-full px-3 py-1 mb-2 transition-colors';
|
||||
pill.textContent = suggestion;
|
||||
this.suggestionArea.appendChild(pill);
|
||||
});
|
||||
|
||||
this.suggestionArea.classList.remove('hidden');
|
||||
} else {
|
||||
this.suggestionArea.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet die Benutzernachricht an den Server und zeigt die Antwort an
|
||||
*/
|
||||
async sendMessage() {
|
||||
if (!this.inputField) return;
|
||||
|
||||
const userInput = this.inputField.value.trim();
|
||||
if (!userInput || this.isLoading) return;
|
||||
|
||||
// Vorschläge ausblenden
|
||||
if (this.suggestionArea) {
|
||||
this.suggestionArea.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Benutzernachricht anzeigen
|
||||
this.addMessage('user', userInput);
|
||||
|
||||
@@ -180,6 +398,7 @@ class ChatGPTAssistant {
|
||||
this.showLoadingIndicator();
|
||||
|
||||
try {
|
||||
console.log('Sende Anfrage an KI-Assistent API...');
|
||||
// Anfrage an den Server senden
|
||||
const response = await fetch('/api/assistant', {
|
||||
method: 'POST',
|
||||
@@ -189,36 +408,118 @@ class ChatGPTAssistant {
|
||||
body: JSON.stringify({
|
||||
messages: this.messages
|
||||
}),
|
||||
cache: 'no-cache', // Kein Cache verwenden
|
||||
credentials: 'same-origin' // Cookies senden
|
||||
});
|
||||
|
||||
// Ladeindikator entfernen
|
||||
this.removeLoadingIndicator();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Netzwerkfehler oder Serverproblem');
|
||||
throw new Error(`Serverfehler: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Ladeindikator entfernen
|
||||
this.removeLoadingIndicator();
|
||||
console.log('Antwort erhalten:', data);
|
||||
|
||||
// Antwort anzeigen
|
||||
this.addMessage('assistant', data.response);
|
||||
if (data.response) {
|
||||
this.addMessage('assistant', data.response);
|
||||
|
||||
// Neue Vorschläge basierend auf dem aktuellen Kontext anzeigen
|
||||
this.generateContextualSuggestions();
|
||||
|
||||
// Erfolgreiche Anfrage zurücksetzen
|
||||
this.retryCount = 0;
|
||||
} else if (data.error) {
|
||||
this.addMessage('assistant', `Fehler: ${data.error}`);
|
||||
} else {
|
||||
throw new Error('Unerwartetes Antwortformat vom Server');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Kommunikation mit dem Assistenten:', error);
|
||||
|
||||
// Ladeindikator entfernen
|
||||
// Ladeindikator entfernen, falls noch vorhanden
|
||||
this.removeLoadingIndicator();
|
||||
|
||||
// Fehlermeldung anzeigen
|
||||
this.addMessage('assistant', 'Es tut mir leid, aber es gab ein Problem bei der Verarbeitung deiner Anfrage. Bitte versuche es später noch einmal.');
|
||||
// Fehlermeldung anzeigen oder Wiederholungsversuch starten
|
||||
if (this.retryCount < this.maxRetries) {
|
||||
this.retryCount++;
|
||||
this.addMessage('assistant', 'Es gab ein Problem mit der Anfrage. Ich versuche es erneut...');
|
||||
|
||||
// Kurze Verzögerung vor dem erneuten Versuch
|
||||
setTimeout(() => {
|
||||
// Letzte Benutzernachricht aus dem Messages-Array entfernen
|
||||
const lastUserMessage = this.messages[this.messages.length - 2].content;
|
||||
this.messages = this.messages.slice(0, -2); // Entferne Benutzernachricht und Fehlermeldung
|
||||
|
||||
// Erneuter Versand mit gleicher Nachricht
|
||||
this.inputField.value = lastUserMessage;
|
||||
this.sendMessage();
|
||||
}, 1500);
|
||||
} else {
|
||||
// Maximale Anzahl an Wiederholungsversuchen erreicht
|
||||
this.addMessage('assistant', 'Es tut mir leid, aber es gab ein Problem bei der Verarbeitung deiner Anfrage. Bitte versuche es später noch einmal.');
|
||||
this.retryCount = 0; // Zurücksetzen für die nächste Anfrage
|
||||
}
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert kontextbasierte Vorschläge basierend auf dem aktuellen Chat-Verlauf
|
||||
*/
|
||||
generateContextualSuggestions() {
|
||||
// Basierend auf letzter Antwort des Assistenten, verschiedene Vorschläge generieren
|
||||
const lastAssistantMessage = this.messages.findLast(msg => msg.role === 'assistant')?.content || '';
|
||||
|
||||
let suggestions = [];
|
||||
|
||||
// Intelligente Vorschläge basierend auf Kontext
|
||||
if (lastAssistantMessage.includes('Künstliche Intelligenz') ||
|
||||
lastAssistantMessage.includes('KI ') ||
|
||||
lastAssistantMessage.includes('AI ')) {
|
||||
suggestions = [
|
||||
"Wie wird KI in der Wissenschaft eingesetzt?",
|
||||
"Zeige mir Gedanken zum maschinellen Lernen",
|
||||
"Was ist der Unterschied zwischen KI und ML?"
|
||||
];
|
||||
} else if (lastAssistantMessage.includes('Kategorie') ||
|
||||
lastAssistantMessage.includes('Kategorien')) {
|
||||
suggestions = [
|
||||
"Zeige mir die Unterkategorien",
|
||||
"Welche Gedanken gehören zu dieser Kategorie?",
|
||||
"Liste alle Wissenschaftskategorien auf"
|
||||
];
|
||||
} else if (lastAssistantMessage.includes('Mindmap') ||
|
||||
lastAssistantMessage.includes('Visualisierung')) {
|
||||
suggestions = [
|
||||
"Wie kann ich eine eigene Mindmap erstellen?",
|
||||
"Zeige mir Beispiele für Mindmaps",
|
||||
"Wie funktionieren die Verbindungen in Mindmaps?"
|
||||
];
|
||||
} else {
|
||||
// Standardvorschläge
|
||||
suggestions = [
|
||||
"Erzähle mir mehr dazu",
|
||||
"Gibt es Beispiele dafür?",
|
||||
"Wie kann ich diese Information nutzen?"
|
||||
];
|
||||
}
|
||||
|
||||
this.showSuggestions(suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt einen Ladeindikator im Chat an
|
||||
*/
|
||||
showLoadingIndicator() {
|
||||
if (!this.chatHistory) return;
|
||||
|
||||
// Entferne vorhandenen Ladeindikator (falls vorhanden)
|
||||
this.removeLoadingIndicator();
|
||||
|
||||
const loadingEl = document.createElement('div');
|
||||
loadingEl.id = 'assistant-loading';
|
||||
loadingEl.className = 'flex justify-start';
|
||||
@@ -232,46 +533,37 @@ class ChatGPTAssistant {
|
||||
|
||||
// Scroll zum Ende des Verlaufs
|
||||
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
||||
|
||||
// Stil für den Typing-Indikator
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.typing-indicator span {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: #888;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
opacity: 0.4;
|
||||
animation: typing 1.5s infinite ease-in-out;
|
||||
}
|
||||
.typing-indicator span:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.typing-indicator span:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
@keyframes typing {
|
||||
0% { transform: translateY(0); }
|
||||
50% { transform: translateY(-5px); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt den Ladeindikator aus dem Chat
|
||||
*/
|
||||
removeLoadingIndicator() {
|
||||
const loadingEl = document.getElementById('assistant-loading');
|
||||
if (loadingEl) {
|
||||
loadingEl.remove();
|
||||
const loadingIndicator = document.getElementById('assistant-loading');
|
||||
if (loadingIndicator) {
|
||||
loadingIndicator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet den Assistenten und sendet eine vorgegebene Frage
|
||||
* @param {string} question - Die zu stellende Frage
|
||||
*/
|
||||
async sendQuestion(question) {
|
||||
if (!question || this.isLoading) return;
|
||||
|
||||
// Assistenten öffnen
|
||||
this.toggleAssistant(true);
|
||||
|
||||
// Kurze Verzögerung, um sicherzustellen, dass der UI vollständig geöffnet ist
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Frage in Eingabefeld setzen
|
||||
if (this.inputField) {
|
||||
this.inputField.value = question;
|
||||
|
||||
// Sende die Frage
|
||||
this.sendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
* Initialisiert die Mindmap-Seite
|
||||
*/
|
||||
function initMindmapPage() {
|
||||
console.log('Mindmap-Seite Initialisierung startet...');
|
||||
console.log('D3 Bibliothek verfügbar:', typeof d3 !== 'undefined');
|
||||
console.log('MindMapVisualization verfügbar:', typeof MindMapVisualization !== 'undefined');
|
||||
|
||||
const mindmapContainer = document.getElementById('mindmap-container');
|
||||
const thoughtsContainer = document.getElementById('thoughts-container');
|
||||
|
||||
@@ -26,6 +30,7 @@ function initMindmapPage() {
|
||||
console.error('Mindmap-Container nicht gefunden!');
|
||||
return;
|
||||
}
|
||||
console.log('Mindmap-Container gefunden:', mindmapContainer);
|
||||
|
||||
// Prüfe, ob D3.js geladen ist
|
||||
if (typeof d3 === 'undefined') {
|
||||
@@ -41,17 +46,47 @@ function initMindmapPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfe, ob MindMapVisualization definiert ist
|
||||
if (typeof MindMapVisualization === 'undefined') {
|
||||
console.error('MindMapVisualization-Klasse ist nicht definiert!');
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">MindMap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Erstelle die Mindmap-Visualisierung
|
||||
const mindmap = new MindMapVisualization('#mindmap-container', {
|
||||
height: 600,
|
||||
onNodeClick: handleNodeClick
|
||||
});
|
||||
|
||||
// Globale Referenz für die Zoom-Buttons erstellen
|
||||
window.mindmapInstance = mindmap;
|
||||
|
||||
// Lade die Mindmap-Daten
|
||||
mindmap.loadData();
|
||||
try {
|
||||
console.log('Versuche, MindMapVisualization zu erstellen...');
|
||||
const mindmap = new MindMapVisualization('#mindmap-container', {
|
||||
height: 600,
|
||||
onNodeClick: handleNodeClick
|
||||
});
|
||||
|
||||
// Globale Referenz für die Zoom-Buttons erstellen
|
||||
window.mindmapInstance = mindmap;
|
||||
|
||||
// Lade die Mindmap-Daten
|
||||
mindmap.loadData();
|
||||
console.log('MindMapVisualization erfolgreich erstellt und geladen');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der MindMapVisualization:', error);
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">Fehler beim Erstellen der Mindmap-Visualisierung:</p>
|
||||
<p class="text-md mt-2">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Suchfunktion für die Mindmap
|
||||
const searchInput = document.getElementById('mindmap-search');
|
||||
|
||||
@@ -163,7 +163,7 @@ class MindMapVisualization {
|
||||
// API-Aufruf mit kürzerem Timeout im Hintergrund durchführen
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout - reduced from 10
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout
|
||||
|
||||
const response = await fetch('/api/mindmap', {
|
||||
signal: controller.signal,
|
||||
@@ -175,8 +175,55 @@ class MindMapVisualization {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`HTTP Fehler: ${response.status}, verwende Standarddaten`);
|
||||
return; // Keep using default data
|
||||
console.warn(`HTTP Fehler: ${response.status}, versuche erneute Verbindung`);
|
||||
|
||||
// Bei Verbindungsfehler versuchen, die Verbindung neu herzustellen
|
||||
const retryResponse = await fetch('/api/refresh-mindmap', {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache'
|
||||
}
|
||||
});
|
||||
|
||||
if (!retryResponse.ok) {
|
||||
throw new Error(`Retry failed with status: ${retryResponse.status}`);
|
||||
}
|
||||
|
||||
const retryData = await retryResponse.json();
|
||||
|
||||
if (!retryData.success || !retryData.nodes || retryData.nodes.length === 0) {
|
||||
console.warn('Keine Mindmap-Daten nach Neuversuch, verwende weiterhin Standard-Daten.');
|
||||
return; // Keep using default data
|
||||
}
|
||||
|
||||
// Flache Liste von Knoten und Verbindungen erstellen
|
||||
this.nodes = [];
|
||||
this.links = [];
|
||||
|
||||
// Knoten direkt übernehmen
|
||||
retryData.nodes.forEach(node => {
|
||||
this.nodes.push({
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
description: node.description || '',
|
||||
thought_count: node.thought_count || 0,
|
||||
color: this.generateColorFromString(node.name),
|
||||
});
|
||||
|
||||
// Verbindungen hinzufügen
|
||||
if (node.connections && node.connections.length > 0) {
|
||||
node.connections.forEach(conn => {
|
||||
this.links.push({
|
||||
source: node.id,
|
||||
target: conn.target
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Visualisierung aktualisieren mit den tatsächlichen Daten
|
||||
this.updateVisualization();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
Reference in New Issue
Block a user