diff --git a/website/app.py b/website/app.py
index d5da608..022963b 100644
--- a/website/app.py
+++ b/website/app.py
@@ -822,6 +822,12 @@ def chat_with_assistant():
print(f"Fehler bei der KI-Anfrage: {str(e)}")
return jsonify({"success": False, "error": "Fehler bei der Verarbeitung der Anfrage"}), 500
+# Route für Benutzer-Merkliste und persönliche Mindmap
+@app.route('/my-account')
+@login_required
+def my_account():
+ return render_template('my_account.html', current_year=current_year)
+
# Flask starten
if __name__ == '__main__':
with app.app_context():
diff --git a/website/static/js/modules/mindmap.js b/website/static/js/modules/mindmap.js
index 41d93cb..69385c8 100644
--- a/website/static/js/modules/mindmap.js
+++ b/website/static/js/modules/mindmap.js
@@ -32,6 +32,9 @@ class MindMapVisualization {
this.tooltipDiv = null;
this.isLoading = true;
+ // Lade die gemerkten Knoten
+ this.bookmarkedNodes = this.loadBookmarkedNodes();
+
// Sicherstellen, dass der Container bereit ist
if (this.container.node()) {
this.init();
@@ -154,10 +157,13 @@ class MindMapVisualization {
this.isLoading = false;
this.updateVisualization();
- // API-Aufruf mit längeren Timeout im Hintergrund durchführen
+ // Status auf bereit setzen - don't wait for API
+ this.container.attr('data-status', 'ready');
+
+ // API-Aufruf mit kürzerem Timeout im Hintergrund durchführen
try {
const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 Sekunden Timeout
+ const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout - reduced from 10
const response = await fetch('/api/mindmap', {
signal: controller.signal,
@@ -169,14 +175,15 @@ class MindMapVisualization {
clearTimeout(timeoutId);
if (!response.ok) {
- throw new Error(`HTTP Fehler: ${response.status}`);
+ console.warn(`HTTP Fehler: ${response.status}, verwende Standarddaten`);
+ return; // Keep using default data
}
const data = await response.json();
if (!data || !data.nodes || data.nodes.length === 0) {
console.warn('Keine Mindmap-Daten vorhanden, verwende weiterhin Standard-Daten.');
- return; // Behalte Standarddaten bei
+ return; // Keep using default data
}
// Flache Liste von Knoten und Verbindungen erstellen
@@ -187,14 +194,9 @@ class MindMapVisualization {
// Visualisierung aktualisieren mit den tatsächlichen Daten
this.updateVisualization();
- // Status auf bereit setzen
- this.container.attr('data-status', 'ready');
-
} catch (error) {
console.warn('Fehler beim Laden der Mindmap-Daten, verwende Standarddaten:', error);
- // Fallback zu Standarddaten ist bereits geschehen
- // Stellen Sie sicher, dass der Status korrekt gesetzt wird
- this.container.attr('data-status', 'ready');
+ // Already using default data, no action needed
}
} catch (error) {
@@ -366,11 +368,11 @@ class MindMapVisualization {
// Kreis für jeden Knoten
group.append('circle')
- .attr('class', 'node')
+ .attr('class', d => `node ${this.isNodeBookmarked(d.id) ? 'bookmarked' : ''}`)
.attr('r', this.nodeRadius)
.attr('fill', d => d.color || this.generateColorFromString(d.name))
- .attr('stroke', '#ffffff50')
- .attr('stroke-width', 2)
+ .attr('stroke', d => this.isNodeBookmarked(d.id) ? '#FFD700' : '#ffffff50')
+ .attr('stroke-width', d => this.isNodeBookmarked(d.id) ? 3 : 2)
.attr('filter', 'url(#glow)');
// Text-Label mit besserem Kontrast
@@ -398,7 +400,10 @@ class MindMapVisualization {
update => {
// Knoten aktualisieren
update.select('.node')
- .attr('fill', d => d.color || this.generateColorFromString(d.name));
+ .attr('class', d => `node ${this.isNodeBookmarked(d.id) ? 'bookmarked' : ''}`)
+ .attr('fill', d => d.color || this.generateColorFromString(d.name))
+ .attr('stroke', d => this.isNodeBookmarked(d.id) ? '#FFD700' : '#ffffff50')
+ .attr('stroke-width', d => this.isNodeBookmarked(d.id) ? 3 : 2);
// Text aktualisieren
update.select('.node-label')
@@ -465,6 +470,7 @@ class MindMapVisualization {
// Tooltip anzeigen
if (this.tooltipEnabled) {
+ const isBookmarked = this.isNodeBookmarked(d.id);
const tooltipContent = `
${d.name}
@@ -472,6 +478,12 @@ class MindMapVisualization {
Gedanken: ${d.thought_count}
+
+
+
`;
@@ -482,6 +494,20 @@ class MindMapVisualization {
.transition()
.duration(200)
.style('opacity', 1);
+
+ // Event-Listener für den Bookmark-Button hinzufügen
+ document.getElementById('bookmark-button').addEventListener('click', (e) => {
+ e.stopPropagation();
+ const nodeId = e.currentTarget.getAttribute('data-nodeid');
+ const isNowBookmarked = this.toggleBookmark(nodeId);
+
+ // Button-Text aktualisieren
+ if (isNowBookmarked) {
+ e.currentTarget.innerHTML = ' Gemerkt';
+ } else {
+ e.currentTarget.innerHTML = ' Merken';
+ }
+ });
}
// Knoten visuell hervorheben
@@ -489,7 +515,7 @@ class MindMapVisualization {
.transition()
.duration(200)
.attr('r', this.nodeRadius * 1.2)
- .attr('stroke', '#ffffff');
+ .attr('stroke', this.isNodeBookmarked(d.id) ? '#FFD700' : '#ffffff');
}
nodeMouseout(event, d) {
@@ -506,11 +532,13 @@ class MindMapVisualization {
// Knoten-Stil zurücksetzen, wenn nicht ausgewählt
const nodeElement = d3.select(event.currentTarget).select('circle');
if (d !== this.selectedNode) {
+ const isBookmarked = this.isNodeBookmarked(d.id);
nodeElement
.transition()
.duration(200)
.attr('r', this.nodeRadius)
- .attr('stroke', '#ffffff50');
+ .attr('stroke', isBookmarked ? '#FFD700' : '#ffffff50')
+ .attr('stroke-width', isBookmarked ? 3 : 2);
}
}
@@ -671,6 +699,78 @@ class MindMapVisualization {
return true;
}
+
+ // Lädt gemerkete Knoten aus dem LocalStorage
+ loadBookmarkedNodes() {
+ try {
+ const bookmarked = localStorage.getItem('bookmarkedNodes');
+ return bookmarked ? JSON.parse(bookmarked) : [];
+ } catch (error) {
+ console.error('Fehler beim Laden der gemerkten Knoten:', error);
+ return [];
+ }
+ }
+
+ // Speichert gemerkete Knoten im LocalStorage
+ saveBookmarkedNodes() {
+ try {
+ localStorage.setItem('bookmarkedNodes', JSON.stringify(this.bookmarkedNodes));
+ } catch (error) {
+ console.error('Fehler beim Speichern der gemerkten Knoten:', error);
+ }
+ }
+
+ // Prüft, ob ein Knoten gemerkt ist
+ isNodeBookmarked(nodeId) {
+ return this.bookmarkedNodes.includes(nodeId);
+ }
+
+ // Merkt einen Knoten oder hebt die Markierung auf
+ toggleBookmark(nodeId) {
+ const index = this.bookmarkedNodes.indexOf(nodeId);
+ if (index === -1) {
+ // Node hinzufügen
+ this.bookmarkedNodes.push(nodeId);
+ this.updateNodeAppearance(nodeId, true);
+ } else {
+ // Node entfernen
+ this.bookmarkedNodes.splice(index, 1);
+ this.updateNodeAppearance(nodeId, false);
+ }
+
+ // Änderungen speichern
+ this.saveBookmarkedNodes();
+
+ // Event auslösen für andere Komponenten
+ const event = new CustomEvent('nodeBookmarkToggled', {
+ detail: {
+ nodeId: nodeId,
+ isBookmarked: index === -1
+ }
+ });
+ document.dispatchEvent(event);
+
+ return index === -1; // true wenn jetzt gemerkt, false wenn Markierung aufgehoben
+ }
+
+ // Aktualisiert das Aussehen eines Knotens basierend auf Bookmark-Status
+ updateNodeAppearance(nodeId, isBookmarked) {
+ this.g.selectAll('.node-group')
+ .filter(d => d.id === nodeId)
+ .select('.node')
+ .classed('bookmarked', isBookmarked)
+ .attr('stroke', isBookmarked ? '#FFD700' : '#ffffff50')
+ .attr('stroke-width', isBookmarked ? 3 : 2);
+ }
+
+ // Aktualisiert das Aussehen aller gemerkten Knoten
+ updateAllBookmarkedNodes() {
+ this.g.selectAll('.node-group')
+ .each((d) => {
+ const isBookmarked = this.isNodeBookmarked(d.id);
+ this.updateNodeAppearance(d.id, isBookmarked);
+ });
+ }
}
// Exportiere die Klasse für die Verwendung in anderen Modulen
diff --git a/website/templates/base.html b/website/templates/base.html
index 7c552ab..4ca2b23 100644
--- a/website/templates/base.html
+++ b/website/templates/base.html
@@ -601,7 +601,7 @@
-
+
{% if current_user.is_authenticated %}