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:
2025-04-27 16:56:16 +02:00
parent 2d8cdc052f
commit 4a3092a4d2
42 changed files with 2458 additions and 878 deletions

View File

@@ -14,9 +14,9 @@
<meta name="keywords" content="systades, wissen, visualisierung, lernen, gedanken, theorie">
<meta name="author" content="Systades-Team">
<!-- Tailwind CSS über CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
<!-- Tailwind CSS - CDN Version -->
<script src="https://cdn.tailwindcss.com" nonce="{{ csp_nonce }}"></script>
<script nonce="{{ csp_nonce }}">
tailwind.config = {
darkMode: 'class',
theme: {
@@ -63,13 +63,12 @@
}
</script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Local Font Files -->
<link href="{{ url_for('static', filename='fonts/inter.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='fonts/jetbrains-mono.css') }}" rel="stylesheet">
<!-- Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Icons - Self-hosted Font Awesome -->
<link href="{{ url_for('static', filename='css/all.min.css') }}" rel="stylesheet">
<!-- Assistent CSS -->
<link href="{{ url_for('static', filename='css/assistant.css') }}" rel="stylesheet">
@@ -80,23 +79,23 @@
<!-- Base-Styles ausgelagert in eigene Datei -->
<link href="{{ url_for('static', filename='css/base-styles.css') }}" rel="stylesheet">
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js"></script>
<!-- Alpine.js - Self-hosted -->
<script src="{{ url_for('static', filename='js/alpine.min.js') }}" defer nonce="{{ csp_nonce }}"></script>
<!-- Neural Network Background CSS -->
<link href="{{ url_for('static', filename='css/neural-network-background.css') }}" rel="stylesheet">
<!-- Neural Network Background Script -->
<script src="{{ url_for('static', filename='neural-network-background.js') }}"></script>
<script src="{{ url_for('static', filename='neural-network-background.js') }}" nonce="{{ csp_nonce }}"></script>
<!-- Hauptmodul laden (als ES6 Modul) -->
<script type="module">
<script type="module" nonce="{{ csp_nonce }}">
import MindMap from "{{ url_for('static', filename='js/main.js') }}";
// Alpine.js-Integration
document.addEventListener('alpine:init', () => {
Alpine.data('layout', () => ({
darkMode: true, // Default to dark mode
mobileMenuOpen: false,
mobileMenuOpen: false, // Mobile Menü standardmäßig geschlossen
userMenuOpen: false,
showSettingsModal: false,
@@ -151,6 +150,14 @@
}));
});
// Setze einen globalen Alpine-Initialisierer
window.addEventListener('DOMContentLoaded', () => {
// Fallback für Alpine-Initialisierung
if (typeof Alpine !== 'undefined' && !document.body.hasAttribute('x-data')) {
document.body.setAttribute('x-data', 'layout()');
}
});
// MindMap global verfügbar machen (für Alpine.js und andere nicht-Module Skripte)
window.MindMap = MindMap;
</script>
@@ -214,11 +221,31 @@
background-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
}
/* Alpine.js x-cloak für ausgeblendete Elemente */
[x-cloak] { display: none !important; }
/* Grundlegende Klassen, um sicherzustellen, dass Tailwind geladen wird */
.nav-link {
@apply text-gray-300 hover:text-white transition-colors duration-200;
}
.nav-link-active {
@apply text-white font-medium;
}
.nav-link-light {
@apply text-gray-600 hover:text-gray-900 transition-colors duration-200;
}
.nav-link-light-active {
@apply text-gray-900 font-medium;
}
</style>
</head>
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark">
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark bg-gray-900 text-white" x-data="layout()">
<!-- App-Container -->
<div id="app-container" class="flex flex-col min-h-screen" x-data="layout">
<div id="app-container" class="flex flex-col min-h-screen">
<!-- Hauptnavigation -->
<nav class="sticky top-0 left-0 right-0 z-50 transition-all duration-300 py-4 px-5 border-b glass-morphism"
x-bind:class="darkMode ? 'glass-navbar-dark border-white/10' : 'glass-navbar-light border-gray-200/50'">
@@ -389,6 +416,7 @@
<!-- Mobile Menü -->
<div x-show="mobileMenuOpen"
x-cloak
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0"
@@ -569,7 +597,7 @@
{% block scripts %}{% endblock %}
<!-- KI-Chat Initialisierung -->
<script type="module">
<script type="module" nonce="{{ csp_nonce }}">
// Importiere und initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
// dass er auf jeder Seite verfügbar ist, selbst wenn MindMap nicht geladen ist
import ChatGPTAssistant from "{{ url_for('static', filename='js/modules/chatgpt-assistant.js') }}";

View File

@@ -3,21 +3,31 @@
{% block title %}403 - Zugriff verweigert{% endblock %}
{% block content %}
<div class="min-h-[65vh] flex flex-col items-center justify-center px-4 py-12">
<div class="glass-effect max-w-2xl w-full p-8 rounded-lg border border-gray-300/20 dark:border-gray-700/30 shadow-lg">
<div class="min-h-[75vh] flex flex-col items-center justify-center px-4 py-12 bg-gradient-to-b from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
<div class="glass-effect max-w-2xl w-full p-6 md:p-10 rounded-xl border border-gray-300/20 dark:border-gray-700/30 shadow-xl transform transition-all duration-300 hover:shadow-2xl">
<div class="text-center">
<h1 class="text-6xl font-bold text-primary-600 dark:text-primary-400 mb-4">403</h1>
<h2 class="text-2xl font-semibold mb-4">Zugriff verweigert</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8">Sie haben nicht die erforderlichen Berechtigungen, um auf diese Seite zuzugreifen. Bitte melden Sie sich an oder nutzen Sie ein Konto mit entsprechenden Rechten.</p>
<div class="flex justify-center mb-6">
<div class="relative">
<h1 class="text-7xl md:text-8xl font-extrabold text-primary-600 dark:text-primary-400 opacity-90">403</h1>
<div class="absolute -top-4 -right-4 w-12 h-12 bg-red-500 rounded-full flex items-center justify-center animate-pulse">
<i class="fa-solid fa-lock text-white text-xl"></i>
</div>
</div>
</div>
<h2 class="text-2xl md:text-3xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Zugriff verweigert</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8 max-w-lg mx-auto text-base md:text-lg">Sie haben nicht die erforderlichen Berechtigungen, um auf diese Seite zuzugreifen. Bitte melden Sie sich an oder nutzen Sie ein Konto mit entsprechenden Rechten.</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('index') }}" class="btn-primary">
<a href="{{ url_for('index') }}" class="btn-primary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-home mr-2"></i>Zur Startseite
</a>
<a href="javascript:history.back()" class="btn-secondary">
<a href="javascript:history.back()" class="btn-secondary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-arrow-left mr-2"></i>Zurück
</a>
</div>
</div>
</div>
<div class="mt-8 text-sm text-gray-500 dark:text-gray-400">
<p>Benötigen Sie Hilfe? <a href="#" class="text-primary-600 dark:text-primary-400 hover:underline">Kontaktieren Sie uns</a></p>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -3,21 +3,31 @@
{% block title %}404 - Seite nicht gefunden{% endblock %}
{% block content %}
<div class="min-h-[65vh] flex flex-col items-center justify-center px-4 py-12">
<div class="glass-effect max-w-2xl w-full p-8 rounded-lg border border-gray-300/20 dark:border-gray-700/30 shadow-lg">
<div class="min-h-[75vh] flex flex-col items-center justify-center px-4 py-12 bg-gradient-to-b from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
<div class="glass-effect max-w-2xl w-full p-6 md:p-10 rounded-xl border border-gray-300/20 dark:border-gray-700/30 shadow-xl transform transition-all duration-300 hover:shadow-2xl">
<div class="text-center">
<h1 class="text-6xl font-bold text-primary-600 dark:text-primary-400 mb-4">404</h1>
<h2 class="text-2xl font-semibold mb-4">Seite nicht gefunden</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8">Die gesuchte Seite existiert nicht oder wurde verschoben. Bitte prüfen Sie die URL oder nutzen Sie die Navigation.</p>
<div class="flex justify-center mb-6">
<div class="relative">
<h1 class="text-7xl md:text-8xl font-extrabold text-primary-600 dark:text-primary-400 opacity-90">404</h1>
<div class="absolute -top-4 -right-4 w-12 h-12 bg-yellow-500 rounded-full flex items-center justify-center animate-pulse">
<i class="fa-solid fa-question text-white text-xl"></i>
</div>
</div>
</div>
<h2 class="text-2xl md:text-3xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Seite nicht gefunden</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8 max-w-lg mx-auto text-base md:text-lg">Die gesuchte Seite existiert nicht oder wurde verschoben. Bitte prüfen Sie die URL oder nutzen Sie die Navigation.</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('index') }}" class="btn-primary">
<a href="{{ url_for('index') }}" class="btn-primary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-home mr-2"></i>Zur Startseite
</a>
<a href="javascript:history.back()" class="btn-secondary">
<a href="javascript:history.back()" class="btn-secondary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-arrow-left mr-2"></i>Zurück
</a>
</div>
</div>
</div>
<div class="mt-8 text-sm text-gray-500 dark:text-gray-400">
<p>Benötigen Sie Hilfe? <a href="#" class="text-primary-600 dark:text-primary-400 hover:underline">Kontaktieren Sie uns</a></p>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -3,21 +3,31 @@
{% block title %}429 - Zu viele Anfragen{% endblock %}
{% block content %}
<div class="min-h-[65vh] flex flex-col items-center justify-center px-4 py-12">
<div class="glass-effect max-w-2xl w-full p-8 rounded-lg border border-gray-300/20 dark:border-gray-700/30 shadow-lg">
<div class="min-h-[75vh] flex flex-col items-center justify-center px-4 py-12 bg-gradient-to-b from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
<div class="glass-effect max-w-2xl w-full p-6 md:p-10 rounded-xl border border-gray-300/20 dark:border-gray-700/30 shadow-xl transform transition-all duration-300 hover:shadow-2xl">
<div class="text-center">
<h1 class="text-6xl font-bold text-primary-600 dark:text-primary-400 mb-4">429</h1>
<h2 class="text-2xl font-semibold mb-4">Zu viele Anfragen</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8">Sie haben zu viele Anfragen in kurzer Zeit gestellt. Bitte warten Sie einen Moment und versuchen Sie es dann erneut.</p>
<div class="flex justify-center mb-6">
<div class="relative">
<h1 class="text-7xl md:text-8xl font-extrabold text-primary-600 dark:text-primary-400 opacity-90">429</h1>
<div class="absolute -top-4 -right-4 w-12 h-12 bg-orange-500 rounded-full flex items-center justify-center animate-pulse">
<i class="fa-solid fa-hourglass-half text-white text-xl"></i>
</div>
</div>
</div>
<h2 class="text-2xl md:text-3xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Zu viele Anfragen</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8 max-w-lg mx-auto text-base md:text-lg">Sie haben zu viele Anfragen in kurzer Zeit gestellt. Bitte warten Sie einen Moment und versuchen Sie es dann erneut.</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('index') }}" class="btn-primary">
<a href="{{ url_for('index') }}" class="btn-primary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-home mr-2"></i>Zur Startseite
</a>
<a href="javascript:history.back()" class="btn-secondary">
<a href="javascript:history.back()" class="btn-secondary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-arrow-left mr-2"></i>Zurück
</a>
</div>
</div>
</div>
<div class="mt-8 text-sm text-gray-500 dark:text-gray-400">
<p>Benötigen Sie Hilfe? <a href="#" class="text-primary-600 dark:text-primary-400 hover:underline">Kontaktieren Sie uns</a></p>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -3,21 +3,31 @@
{% block title %}500 - Serverfehler{% endblock %}
{% block content %}
<div class="min-h-[65vh] flex flex-col items-center justify-center px-4 py-12">
<div class="glass-effect max-w-2xl w-full p-8 rounded-lg border border-gray-300/20 dark:border-gray-700/30 shadow-lg">
<div class="min-h-[75vh] flex flex-col items-center justify-center px-4 py-12 bg-gradient-to-b from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
<div class="glass-effect max-w-2xl w-full p-6 md:p-10 rounded-xl border border-gray-300/20 dark:border-gray-700/30 shadow-xl transform transition-all duration-300 hover:shadow-2xl">
<div class="text-center">
<h1 class="text-6xl font-bold text-primary-600 dark:text-primary-400 mb-4">500</h1>
<h2 class="text-2xl font-semibold mb-4">Interner Serverfehler</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8">Es ist ein Fehler auf unserem Server aufgetreten. Unser Team wurde informiert und arbeitet bereits an einer Lösung. Bitte versuchen Sie es später erneut.</p>
<div class="flex justify-center mb-6">
<div class="relative">
<h1 class="text-7xl md:text-8xl font-extrabold text-primary-600 dark:text-primary-400 opacity-90">500</h1>
<div class="absolute -top-4 -right-4 w-12 h-12 bg-red-600 rounded-full flex items-center justify-center animate-pulse">
<i class="fa-solid fa-exclamation-triangle text-white text-xl"></i>
</div>
</div>
</div>
<h2 class="text-2xl md:text-3xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Interner Serverfehler</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8 max-w-lg mx-auto text-base md:text-lg">Es ist ein Fehler auf unserem Server aufgetreten. Unser Team wurde informiert und arbeitet bereits an einer Lösung. Bitte versuchen Sie es später erneut.</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('index') }}" class="btn-primary">
<a href="{{ url_for('index') }}" class="btn-primary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-home mr-2"></i>Zur Startseite
</a>
<a href="javascript:history.back()" class="btn-secondary">
<a href="javascript:history.back()" class="btn-secondary transform transition-transform duration-300 hover:scale-105 px-6 py-3 rounded-lg">
<i class="fa-solid fa-arrow-left mr-2"></i>Zurück
</a>
</div>
</div>
</div>
<div class="mt-8 text-sm text-gray-500 dark:text-gray-400">
<p>Benötigen Sie Hilfe? <a href="#" class="text-primary-600 dark:text-primary-400 hover:underline">Kontaktieren Sie uns</a></p>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -253,85 +253,99 @@
</div>
<!-- Chat Interface Preview -->
<div class="max-w-3xl mx-auto embedded-chat">
<!-- Chat Header -->
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-3">
<i class="fa-solid fa-robot text-sm"></i>
<div class="max-w-3xl mx-auto">
<div class="embedded-chat" id="demo-chat">
<!-- Chat Header -->
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-3">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<span class="font-medium text-gray-800 dark:text-gray-200">Systades Assistent (4o-mini)</span>
</div>
<span class="font-medium text-gray-800 dark:text-gray-200">Systades Assistent</span>
</div>
<div>
<button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<i class="fa-solid fa-expand"></i>
</button>
</div>
</div>
<!-- Chat Messages -->
<div id="embedded-chat-messages" class="border-b border-gray-200 dark:border-gray-700">
<!-- Assistant Message -->
<div class="mb-4 flex">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-700 dark:text-gray-300">
Hallo! Ich bin dein Systades-Assistent. Wie kann ich dir heute helfen? Du kannst mir Fragen zu deinen Gedanken stellen,
Verbindungen zwischen Konzepten finden oder Informationen zusammenfassen lassen.
</p>
<div>
<button id="open-real-assistant" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<i class="fa-solid fa-expand"></i>
</button>
</div>
</div>
<!-- User Message -->
<div class="mb-4 flex justify-end">
<div class="bg-purple-100 dark:bg-purple-900/30 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-800 dark:text-gray-200">
Kann ich mit deiner Hilfe eine Mindmap zum Thema Künstliche Intelligenz erstellen?
</p>
<!-- Chat Messages -->
<div id="embedded-chat-messages" class="border-b border-gray-200 dark:border-gray-700">
<!-- Assistant Message -->
<div class="mb-4 flex">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<div class="text-gray-700 dark:text-gray-300 markdown-content">
<p>Hallo! Ich bin dein Systades-Assistent. Wie kann ich dir heute helfen?</p>
<p>Du kannst mir Fragen zu:</p>
<ul>
<li><strong>Gedanken</strong> in der Datenbank</li>
<li><strong>Kategorien</strong> und Wissensgebieten</li>
<li><strong>Mindmaps</strong> und Visualisierungsmöglichkeiten</li>
</ul>
<p>stellen.</p>
</div>
</div>
</div>
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-gray-700 dark:text-gray-300 ml-2 flex-shrink-0">
<i class="fa-solid fa-user text-sm"></i>
<!-- User Message -->
<div class="mb-4 flex justify-end">
<div class="bg-purple-100 dark:bg-purple-900/30 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-800 dark:text-gray-200">
Kann ich mit deiner Hilfe eine Mindmap zum Thema Künstliche Intelligenz erstellen?
</p>
</div>
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-gray-700 dark:text-gray-300 ml-2 flex-shrink-0">
<i class="fa-solid fa-user text-sm"></i>
</div>
</div>
</div>
<!-- Assistant Typing -->
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3">
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
<!-- Assistant Response -->
<div class="mb-4 flex" id="demo-ai-response">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<div class="text-gray-700 dark:text-gray-300 markdown-content">
<p>Ja, natürlich! Ich kann dir dabei helfen, eine Mindmap zum Thema <strong>Künstliche Intelligenz</strong> zu erstellen.</p>
<p>Du kannst wie folgt vorgehen:</p>
<ol>
<li>Gehe zur <strong>Mindmap</strong>-Ansicht</li>
<li>Suche nach dem Knoten "Künstliche Intelligenz" unter der Kategorie "Technologie"</li>
<li>Füge diesen Knoten zu deiner persönlichen Mindmap hinzu</li>
<li>Ergänze verwandte Themen wie <em>Machine Learning, Neural Networks oder Data Science</em></li>
</ol>
<p>Soll ich dir noch mehr spezifische Informationen zu KI-Teilgebieten geben?</p>
</div>
</div>
</div>
</div>
</div>
<!-- Chat Input -->
<div class="p-4">
<div class="flex">
<input type="text" placeholder="Stelle eine Frage..." class="mystical-input flex-grow" disabled>
<button class="ml-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-2 rounded-lg disabled:opacity-50" disabled>
<i class="fa-solid fa-paper-plane"></i>
</button>
</div>
<!-- Quick Queries -->
<div class="mt-3 flex flex-wrap gap-2">
<span class="text-xs text-gray-500 dark:text-gray-400 mr-1">Beispiele:</span>
<button class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300" disabled>Verbindungen finden</button>
<button class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300" disabled>Zusammenfassen</button>
<button class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300" disabled>Mindmap erstellen</button>
<!-- Chat Input -->
<div class="p-4">
<div class="flex">
<input type="text" placeholder="Stelle eine Frage..." class="mystical-input flex-grow" disabled>
<button class="ml-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-2 rounded-lg disabled:opacity-50" disabled>
<i class="fa-solid fa-paper-plane"></i>
</button>
</div>
<!-- Quick Queries -->
<div class="mt-3 flex flex-wrap gap-2">
<span class="text-xs text-gray-500 dark:text-gray-400 mr-1">Beispiele:</span>
<button data-question="Was sind die wichtigsten Grundlagen der Künstlichen Intelligenz?" class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer transition-colors">KI-Grundlagen</button>
<button data-question="Wie kann ich eine Mindmap zum Thema Neuronale Netzwerke erstellen?" class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer transition-colors">Mindmap erstellen</button>
<button data-question="Zeige mir alle verfügbaren Kategorien in der Datenbank" class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer transition-colors">Datenbank durchsuchen</button>
</div>
</div>
</div>
</div>
<!-- Try it Button -->
<div class="text-center mt-10">
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.toggleAssistant(true)"
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.sendQuestion('Hallo! Ich möchte mehr über die Funktionen der Wissensdatenbank erfahren. Was kann ich hier alles machen?')"
class="mystical-button mystical-button-primary inline-flex items-center">
<i class="fa-solid fa-robot mr-2"></i>
KI-Assistenten ausprobieren
@@ -432,33 +446,75 @@
{% block extra_js %}
<script>
// Simulate assistant typing and response
setTimeout(() => {
const chatMessages = document.getElementById('embedded-chat-messages');
const typingIndicator = chatMessages.querySelector('.flex:last-child');
if (typingIndicator) {
// Create assistant response
const response = document.createElement('div');
response.className = 'mb-4 flex';
response.innerHTML = `
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-700 dark:text-gray-300">
Natürlich! Ich kann dir dabei helfen, eine Mindmap zum Thema KI zu erstellen. Beginnen wir mit zentralen Konzepten wie Machine Learning, Neural Networks und Natural Language Processing. Möchtest du einen bestimmten Aspekt der KI genauer betrachten?
</p>
</div>
`;
// Remove typing indicator and add response
typingIndicator.remove();
chatMessages.appendChild(response);
// Scroll to bottom
chatMessages.scrollTop = chatMessages.scrollHeight;
document.addEventListener('DOMContentLoaded', function() {
// Expand-Button mit dem echten Assistenten verknüpfen
const openRealAssistantBtn = document.getElementById('open-real-assistant');
if (openRealAssistantBtn) {
openRealAssistantBtn.addEventListener('click', function() {
if (window.MindMap && window.MindMap.assistant) {
window.MindMap.assistant.toggleAssistant(true);
}
});
}
}, 3000);
// Auch die Beispiel-Buttons im Demo-Chat klickbar machen
const quickQueryButtons = document.querySelectorAll('.quick-query-btn');
quickQueryButtons.forEach(button => {
button.addEventListener('click', function() {
if (window.MindMap && window.MindMap.assistant) {
const question = button.getAttribute('data-question');
if (question) {
window.MindMap.assistant.sendQuestion(question);
} else {
// Fallback auf den Button-Text, falls kein data-question Attribut gesetzt ist
const queryText = button.textContent.trim();
window.MindMap.assistant.sendQuestion(queryText);
}
}
});
});
// Styling für die markdown-content hinzufügen
const style = document.createElement('style');
style.textContent = `
.markdown-content h1, .markdown-content h2, .markdown-content h3,
.markdown-content h4, .markdown-content h5, .markdown-content h6 {
font-weight: bold;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.markdown-content h1 { font-size: 1.4rem; }
.markdown-content h2 { font-size: 1.3rem; }
.markdown-content h3 { font-size: 1.2rem; }
.markdown-content h4 { font-size: 1.1rem; }
.markdown-content ul, .markdown-content ol {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.markdown-content ul { list-style-type: disc; }
.markdown-content ol { list-style-type: decimal; }
.markdown-content p { margin: 0.5rem 0; }
.markdown-content code {
font-family: monospace;
background-color: rgba(0, 0, 0, 0.1);
padding: 1px 4px;
border-radius: 3px;
}
.markdown-content pre {
background-color: rgba(0, 0, 0, 0.1);
padding: 0.5rem;
border-radius: 4px;
overflow-x: auto;
margin: 0.5rem 0;
}
.dark .markdown-content code {
background-color: rgba(255, 255, 255, 0.1);
}
.dark .markdown-content pre {
background-color: rgba(255, 255, 255, 0.1);
}
`;
document.head.appendChild(style);
});
</script>
{% endblock %}

View File

@@ -405,44 +405,14 @@
</div>
<!-- Category Tree -->
<div class="category-tree">
<!-- Recursive template for categories -->
<script type="text/x-template" id="category-template">
<div class="category-item pl-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
:class="[level > 0 ? 'ml-' + (level * 2) : '']"
@click="toggleCategory(category.id)">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fa-solid" :class="[isExpanded ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 text-sm transition-transform']"></i>
<span :class="{'font-medium': isActive}">
<i class="fa-solid mr-2" :class="category.icon || 'fa-folder'"></i>
${category.name}
</span>
</div>
<span class="node-counter">${category.nodes.length}</span>
</div>
<!-- Nodes for this category -->
<div x-show="isExpanded && isActive" class="mt-2 node-list pl-4">
<div v-for="node in category.nodes" class="node-item p-2 mb-2"
:style="{ borderLeftColor: node.color_code }"
@click.stop="addNodeToCanvas(node)">
<div class="flex items-center justify-between">
<div>${node.name}</div>
<span class="node-counter">${node.thought_count}</span>
</div>
</div>
</div>
<!-- Subcategories recursive -->
<div x-show="isExpanded" class="mt-2">
<template v-for="child in category.children">
<category-item :category="child" :level="level + 1"></category-item>
</template>
</div>
<div class="category-tree" id="category-tree">
<div id="categories-container" class="space-y-1">
<!-- Categories will be loaded dynamically -->
<div class="py-3 text-center text-gray-500 dark:text-gray-400" id="loading-categories">
<div class="spinner mx-auto mb-2"></div>
<p>Kategorien werden geladen...</p>
</div>
</script>
<!-- Root categories rendered here -->
<div id="categories-container"></div>
</div>
</div>
<!-- View Mode Toggle -->
@@ -474,7 +444,12 @@
<!-- User Mindmap List -->
<div class="space-y-2 max-h-60 overflow-y-auto mb-3">
<!-- Will be populated by JS -->
<div id="user-mindmaps-list" class="space-y-2"></div>
<div id="user-mindmaps-list" class="space-y-2">
<div class="py-3 text-center text-gray-500 dark:text-gray-400" id="loading-mindmaps">
<div class="spinner mx-auto mb-2"></div>
<p>Mindmaps werden geladen...</p>
</div>
</div>
</div>
<!-- Add New Mindmap Button -->
@@ -510,93 +485,349 @@
<!-- Custom D3 Extensions -->
<script src="{{ url_for('static', filename='d3-extensions.js') }}"></script>
<!-- Mindmap Script -->
<script src="{{ url_for('static', filename='mindmap.js') }}"></script>
<!-- Mindmap Visualisierungsmodul -->
<script src="{{ url_for('static', filename='js/modules/mindmap.js') }}"></script>
<script>
// Initialize the public mindmap
document.addEventListener('DOMContentLoaded', function() {
console.log('Mindmap-Seite wird initialisiert...');
// Prüfe, ob D3.js geladen ist
if (typeof d3 === 'undefined') {
console.error('D3.js Bibliothek ist nicht geladen!');
document.getElementById('mindmap-container').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">D3.js konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>' +
'</div>';
return;
}
// Prüfe, ob MindMapVisualization geladen ist
if (typeof MindMapVisualization === 'undefined') {
console.error('MindMapVisualization-Klasse ist nicht geladen!');
document.getElementById('mindmap-container').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">Die Mindmap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>' +
'</div>';
return;
}
// Set up for dark/light mode changes
const isDarkMode = document.documentElement.classList.contains('dark');
// Initialize the node tooltip
const tooltip = document.getElementById('node-tooltip');
// Initialize mindmap
const mindmap = new MindMap({
container: '#mindmap-canvas',
apiEndpoint: '/api/mindmap/public',
isDarkMode: isDarkMode,
tooltip: tooltip
});
try {
console.log('Erstelle MindMapVisualization...');
// Mindmap-Visualisierung initialisieren
const mindmap = new MindMapVisualization('#mindmap-canvas', {
height: document.getElementById('mindmap-container').clientHeight || 600,
tooltipEnabled: true,
onNodeClick: function(node) {
console.log('Knoten geklickt:', node);
}
});
// Speichere als globale Instanz für Zugriff über Buttons
window.mindmapInstance = mindmap;
// Lade die Mindmap-Daten
mindmap.loadData();
console.log('Mindmap-Visualisierung erfolgreich erstellt');
// Load public mindmap data
mindmap.loadData().then(() => {
console.log('Mindmap data loaded');
});
// View mode toggle
document.getElementById('view-all').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('view-focused').classList.remove('active');
if (window.mindmapInstance && window.mindmapInstance.setViewMode) {
window.mindmapInstance.setViewMode('all');
}
});
document.getElementById('view-focused').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('view-all').classList.remove('active');
if (window.mindmapInstance && window.mindmapInstance.setViewMode) {
window.mindmapInstance.setViewMode('focus');
}
});
// Zoom controls
document.getElementById('zoom-in').addEventListener('click', function() {
if (window.mindmapInstance) {
const svg = d3.select('#mindmap-container svg');
const currentZoom = d3.zoomTransform(svg.node());
const newScale = currentZoom.k * 1.3;
svg.transition().duration(300).call(
d3.zoom().transform,
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
);
}
});
document.getElementById('zoom-out').addEventListener('click', function() {
if (window.mindmapInstance) {
const svg = d3.select('#mindmap-container svg');
const currentZoom = d3.zoomTransform(svg.node());
const newScale = currentZoom.k / 1.3;
svg.transition().duration(300).call(
d3.zoom().transform,
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
);
}
});
document.getElementById('reset-view').addEventListener('click', function() {
if (window.mindmapInstance) {
const svg = d3.select('#mindmap-container svg');
svg.transition().duration(500).call(
d3.zoom().transform,
d3.zoomIdentity.scale(1)
);
}
});
// Handle dark mode toggle
document.addEventListener('darkModeToggled', function(event) {
const isDark = event.detail.isDark;
if (window.mindmapInstance && window.mindmapInstance.updateTheme) {
window.mindmapInstance.updateTheme(isDark);
}
});
// Load categories
loadCategories();
// Search functionality
const searchInput = document.getElementById('category-search');
if (searchInput) {
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
if (window.mindmapInstance && window.mindmapInstance.filterBySearchTerm) {
window.mindmapInstance.filterBySearchTerm(searchTerm);
}
// Filter categories in the sidebar
filterCategoriesInSidebar(searchTerm);
});
}
} catch (error) {
console.error('Fehler bei der Initialisierung der Mindmap:', error);
const errorContainer = document.getElementById('mindmap-container');
errorContainer.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 bei der Initialisierung der Mindmap:</p>' +
'<p class="text-md mt-2">' + (error.message || 'Unbekannter Fehler') + '</p>' +
'</div>';
}
});
// Kategorien laden
function loadCategories() {
const categoriesContainer = document.getElementById('categories-container');
const loadingElement = document.getElementById('loading-categories');
// View mode toggle
document.getElementById('view-all').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('view-focused').classList.remove('active');
mindmap.setViewMode('all');
});
document.getElementById('view-focused').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('view-all').classList.remove('active');
mindmap.setViewMode('focus');
});
// Zoom controls
document.getElementById('zoom-in').addEventListener('click', function() {
mindmap.zoomIn();
});
document.getElementById('zoom-out').addEventListener('click', function() {
mindmap.zoomOut();
});
document.getElementById('reset-view').addEventListener('click', function() {
mindmap.resetView();
});
// Handle dark mode toggle
document.addEventListener('darkModeToggled', function(event) {
const isDark = event.detail.isDark;
mindmap.updateTheme(isDark);
});
// Search functionality
const searchInput = document.getElementById('category-search');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
mindmap.searchCategories(searchTerm);
});
{% if current_user.is_authenticated %}
// Load user mindmaps
fetch('/api/mindmap/user')
.then(response => response.json())
.then(data => {
const mindmapsList = document.getElementById('user-mindmaps-list');
mindmapsList.innerHTML = '';
fetch('/api/categories')
.then(response => {
if (!response.ok) {
throw new Error('Kategorien konnten nicht geladen werden');
}
return response.json();
})
.then(categories => {
// Loading-Anzeige entfernen
if (loadingElement) {
loadingElement.remove();
}
if (data.length === 0) {
mindmapsList.innerHTML = `
<div class="text-center text-gray-500 dark:text-gray-400 py-2">
Keine Mindmaps gefunden
// Kategorien rendern
renderCategories(categories, categoriesContainer);
})
.catch(error => {
console.error('Fehler beim Laden der Kategorien:', error);
if (loadingElement) {
loadingElement.innerHTML = `
<div class="text-red-500 mb-2">
<i class="fa-solid fa-exclamation-circle"></i>
</div>
<p>Fehler beim Laden der Kategorien</p>
`;
}
});
}
// Kategorien rekursiv rendern
function renderCategories(categories, container, level = 0) {
categories.forEach(category => {
const categoryElement = document.createElement('div');
categoryElement.className = 'category-item pl-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700';
categoryElement.setAttribute('data-category-id', category.id);
categoryElement.style.marginLeft = level > 0 ? `${level * 12}px` : '0';
const hasChildren = category.children && category.children.length > 0;
const hasNodes = category.nodes && category.nodes.length > 0;
categoryElement.innerHTML = `
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fa-solid fa-chevron-right mr-2 text-sm transition-transform ${hasChildren ? '' : 'opacity-0'}"></i>
<span>
<i class="fa-solid ${category.icon || 'fa-folder'} mr-2"></i>
${category.name}
</span>
</div>
<span class="node-counter">${hasNodes ? category.nodes.length : 0}</span>
</div>
`;
container.appendChild(categoryElement);
// Bereich für Knoten und Unterkategorien erstellen
const expandableArea = document.createElement('div');
expandableArea.className = 'hidden mt-2';
expandableArea.setAttribute('data-category-expand', category.id);
container.appendChild(expandableArea);
// Knoten für diese Kategorie anzeigen
if (hasNodes) {
const nodesContainer = document.createElement('div');
nodesContainer.className = 'node-list pl-4';
category.nodes.forEach(node => {
const nodeItem = document.createElement('div');
nodeItem.className = 'node-item p-2 mb-2';
nodeItem.style.borderLeft = `3px solid ${node.color_code || '#9F7AEA'}`;
nodeItem.setAttribute('data-node-id', node.id);
nodeItem.innerHTML = `
<div class="flex items-center justify-between">
<div>${node.name}</div>
<span class="node-counter">${node.thought_count || 0}</span>
</div>
`;
nodeItem.addEventListener('click', function(e) {
e.stopPropagation();
if (window.mindmapInstance && window.mindmapInstance.focusNode) {
window.mindmapInstance.focusNode(node.id);
}
});
nodesContainer.appendChild(nodeItem);
});
expandableArea.appendChild(nodesContainer);
}
// Unterkategorien rekursiv rendern
if (hasChildren) {
const childrenContainer = document.createElement('div');
childrenContainer.className = 'mt-2';
expandableArea.appendChild(childrenContainer);
renderCategories(category.children, childrenContainer, level + 1);
}
// Event-Listener für Aufklappen/Zuklappen
categoryElement.addEventListener('click', function() {
const expandArea = document.querySelector(`[data-category-expand="${category.id}"]`);
const chevron = this.querySelector('.fa-chevron-right');
if (expandArea) {
if (expandArea.classList.contains('hidden')) {
expandArea.classList.remove('hidden');
if (chevron) chevron.style.transform = 'rotate(90deg)';
} else {
expandArea.classList.add('hidden');
if (chevron) chevron.style.transform = 'rotate(0)';
}
}
});
});
}
// Categories filtering
function filterCategoriesInSidebar(searchTerm) {
if (!searchTerm) {
// Show all categories
document.querySelectorAll('.category-item').forEach(el => {
el.style.display = '';
});
return;
}
// Hide/show categories based on search
document.querySelectorAll('.category-item').forEach(el => {
const categoryName = el.querySelector('span').textContent.trim().toLowerCase();
if (categoryName.includes(searchTerm)) {
el.style.display = '';
// Show parent categories
let parent = el.parentElement;
while (parent && !parent.matches('#categories-container')) {
if (parent.hasAttribute('data-category-expand')) {
parent.classList.remove('hidden');
const parentId = parent.getAttribute('data-category-expand');
const parentCategory = document.querySelector(`[data-category-id="${parentId}"]`);
if (parentCategory) {
const chevron = parentCategory.querySelector('.fa-chevron-right');
if (chevron) chevron.style.transform = 'rotate(90deg)';
}
}
parent = parent.parentElement;
}
} else {
el.style.display = 'none';
}
});
}
</script>
{% if current_user.is_authenticated %}
<!-- Script für eingeloggte Benutzer -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load user mindmaps
const mindmapsList = document.getElementById('user-mindmaps-list');
const loadingMindmaps = document.getElementById('loading-mindmaps');
fetch('/api/mindmap/user')
.then(response => {
if (!response.ok) {
throw new Error('Benutzer-Mindmaps konnten nicht geladen werden');
}
return response.json();
})
.then(data => {
if (!mindmapsList) return;
// Loading-Anzeige entfernen
if (loadingMindmaps) {
loadingMindmaps.remove();
}
if (data.length === 0) {
mindmapsList.innerHTML = '<div class="text-center text-gray-500 dark:text-gray-400 py-2">Keine Mindmaps gefunden</div>';
return;
}
// Mindmaps anzeigen
data.forEach(mindmap => {
const item = document.createElement('div');
item.className = 'user-mindmap-item p-3';
item.innerHTML = `
<div class="font-medium text-gray-800 dark:text-gray-200">${mindmap.name}</div>
<div class="text-xs text-gray-500 dark:text-gray-400 mb-2">${mindmap.description}</div>
<div class="text-xs text-gray-500 dark:text-gray-400 mb-2">${mindmap.description || ''}</div>
<a href="/my-mindmap/${mindmap.id}" class="text-purple-600 dark:text-purple-400 text-xs hover:underline">
<i class="fa-solid fa-arrow-right mr-1"></i> Öffnen
</a>
@@ -605,9 +836,17 @@
});
})
.catch(error => {
console.error('Error loading user mindmaps:', error);
console.error('Fehler beim Laden der Benutzer-Mindmaps:', error);
if (loadingMindmaps) {
loadingMindmaps.innerHTML = `
<div class="text-red-500 mb-2">
<i class="fa-solid fa-exclamation-circle"></i>
</div>
<p>Fehler beim Laden der Mindmaps</p>
`;
}
});
{% endif %}
});
</script>
{% endif %}
{% endblock %}

View File

@@ -32,7 +32,7 @@
position: relative;
}
.profile-avatar {
.avatar-container {
width: 180px;
height: 180px;
border-radius: 50%;
@@ -50,40 +50,48 @@
flex-shrink: 0;
}
.profile-avatar:hover {
.avatar-container:hover {
transform: scale(1.05);
border: 3px solid rgba(179, 143, 255, 0.5);
box-shadow: 0 12px 45px rgba(0, 0, 0, 0.3), 0 0 25px rgba(179, 143, 255, 0.35);
}
.profile-avatar img {
.avatar-container img {
width: 100%;
height: 100%;
object-fit: cover;
transition: filter 0.3s ease;
}
.profile-avatar:hover img {
.avatar-container:hover img {
filter: brightness(1.1);
}
.profile-avatar-placeholder {
font-size: 5rem;
color: rgba(255, 255, 255, 0.6);
.avatar-edit {
position: absolute;
bottom: 0;
right: 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.profile-avatar:hover .profile-avatar-placeholder {
color: rgba(255, 255, 255, 0.9);
transform: scale(1.1);
.avatar-edit:hover {
background: rgba(255, 255, 255, 0.3);
}
.profile-info {
.user-info {
flex: 1;
padding-top: 0.5rem;
}
.profile-name {
.user-info h1 {
font-size: 2.75rem;
font-weight: 800;
margin-bottom: 0.75rem;
@@ -96,33 +104,7 @@
line-height: 1.2;
}
.profile-username {
font-size: 1.35rem;
color: rgba(255, 255, 255, 0.85);
margin-bottom: 1.25rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.username-badge {
background: rgba(179, 143, 255, 0.2);
border: 1px solid rgba(179, 143, 255, 0.3);
color: #b38fff;
padding: 0.3rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
transition: all 0.3s ease;
}
.username-badge:hover {
background: rgba(179, 143, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2), 0 0 8px rgba(179, 143, 255, 0.3);
}
.profile-bio {
.user-bio {
font-size: 1.15rem;
line-height: 1.7;
color: rgba(255, 255, 255, 0.9);
@@ -131,7 +113,7 @@
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.profile-meta {
.user-meta {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
@@ -140,118 +122,22 @@
align-items: center;
}
.profile-meta-item {
.user-meta span {
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.25s ease;
}
.profile-meta-item:hover {
.user-meta span:hover {
color: rgba(255, 255, 255, 1);
transform: translateY(-2px);
}
.profile-meta-icon {
.user-meta i {
opacity: 0.8;
}
/* Verbesserte Statistik-Karten mit interaktiven Effekten */
.profile-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 1.75rem 1.25rem;
background: rgba(32, 36, 55, 0.7);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
text-align: center;
position: relative;
overflow: hidden;
}
.stat-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle at center, rgba(179, 143, 255, 0.15) 0%, transparent 70%);
opacity: 0;
transition: opacity 0.5s ease;
z-index: 0;
}
.stat-item:hover::before {
opacity: 1;
}
.stat-item:hover {
transform: translateY(-5px);
background: rgba(32, 36, 55, 0.8);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.25), 0 0 20px rgba(179, 143, 255, 0.2);
}
.stat-value {
font-size: 2.25rem;
font-weight: 800;
margin-bottom: 0.75rem;
background: linear-gradient(135deg, #b38fff, #58a9ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
.stat-item:hover .stat-value {
transform: scale(1.1);
text-shadow: 0 0 15px rgba(179, 143, 255, 0.5);
}
.stat-label {
font-size: 0.95rem;
color: rgba(255, 255, 255, 0.75);
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
.stat-item:hover .stat-label {
color: rgba(255, 255, 255, 0.95);
}
/* Stat-Icon für visuelle Verstärkung */
.stat-icon {
font-size: 1.5rem;
margin-bottom: 1rem;
color: rgba(179, 143, 255, 0.7);
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
.stat-item:hover .stat-icon {
transform: scale(1.2) translateY(-3px);
color: rgba(179, 143, 255, 1);
}
/* Benutzer-Aktionsbereich */
.profile-actions {
display: flex;
@@ -504,31 +390,24 @@
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
}
html.light .profile-avatar {
html.light .avatar-container {
background: rgba(255, 255, 255, 0.9);
border: 3px solid rgba(126, 63, 242, 0.3);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1), 0 0 15px rgba(126, 63, 242, 0.15);
}
html.light .profile-name {
html.light .user-info h1 {
background: linear-gradient(135deg, #7e3ff2, #3282f6);
text-shadow: none;
}
html.light .profile-username,
html.light .profile-bio,
html.light .user-bio,
html.light .activity-content {
color: #1a202c;
text-shadow: none;
}
html.light .username-badge {
background: rgba(126, 63, 242, 0.15);
border: 1px solid rgba(126, 63, 242, 0.3);
color: #7e3ff2;
}
html.light .profile-meta {
html.light .user-meta span {
color: #4a5568;
}
@@ -606,63 +485,22 @@
<div class="container mx-auto px-4 py-10">
<!-- Profil-Container -->
<div class="profile-container">
<!-- Profil-Header mit Benutzerinformationen -->
<!-- User Info Section -->
<div class="profile-header">
<!-- Profilbild -->
<div class="profile-avatar">
{% if user.profile_image %}
<img src="{{ user.profile_image }}" alt="{{ user.name }}" />
{% else %}
<div class="profile-avatar-placeholder">
<i class="fas fa-user"></i>
</div>
{% endif %}
<div class="avatar-container">
<img src="{{ user.avatar if user.avatar else url_for('static', filename='img/default-avatar.png') }}" alt="Profilbild" class="avatar">
<div class="avatar-edit">
<i class="fas fa-camera"></i>
</div>
</div>
<!-- Profilinformationen -->
<div class="profile-info">
<h1 class="profile-name">{{ user.name|default('Max Mustermann') }}</h1>
<div class="profile-username">
@{{ user.username|default('maxmustermann') }}
{% if user.verified %}
<span class="username-badge">
<i class="fas fa-check-circle mr-1"></i> Verifiziert
</span>
{% endif %}
</div>
<p class="profile-bio">
{{ user.bio|default('Willkommen auf meinem Profil! Ich bin daran interessiert, Wissen zu vernetzen und neue Verbindungen zwischen verschiedenen Themengebieten zu entdecken. Mein Ziel ist es, ein tieferes Verständnis für komplexe Zusammenhänge zu entwickeln.') }}
</p>
<!-- Meta-Informationen -->
<div class="profile-meta">
<div class="profile-meta-item">
<i class="fas fa-map-marker-alt profile-meta-icon"></i>
<span>{{ user.location|default('Berlin, Deutschland') }}</span>
</div>
<div class="profile-meta-item">
<i class="fas fa-calendar-alt profile-meta-icon"></i>
<span>Mitglied seit {{ user.joined_date|default('Januar 2023') }}</span>
</div>
<div class="profile-meta-item">
<i class="fas fa-globe profile-meta-icon"></i>
<span>{{ user.website|default('www.beispiel.de') }}</span>
</div>
</div>
<!-- Profil-Aktionen -->
<div class="profile-actions">
<button class="profile-action-btn primary">
<i class="fas fa-user-plus mr-1"></i> Folgen
</button>
<button class="profile-action-btn">
<i class="fas fa-comment mr-1"></i> Nachricht
</button>
<button class="profile-action-btn">
<i class="fas fa-share-alt mr-1"></i> Teilen
</button>
<div class="user-info">
<h1>{{ user.username }}</h1>
<p class="user-bio">{{ user.bio if user.bio else 'Keine Bio vorhanden. Klicke auf bearbeiten, um eine hinzuzufügen.' }}</p>
<div class="user-meta">
<span><i class="fas fa-map-marker-alt"></i> {{ user.location if user.location else 'Kein Standort angegeben' }}</span>
<span><i class="fas fa-calendar-alt"></i> Mitglied seit {{ user.created_at.strftime('%d.%m.%Y') }}</span>
</div>
<button class="edit-profile-btn">Profil bearbeiten</button>
</div>
</div>
@@ -673,7 +511,7 @@
<div class="stat-icon">
<i class="fas fa-lightbulb"></i>
</div>
<div class="stat-value">{{ user.thoughts_count|default('42') }}</div>
<div class="stat-value">{{ stats.thought_count if stats and stats.thought_count else 0 }}</div>
<div class="stat-label">Gedanken</div>
</div>
@@ -682,7 +520,7 @@
<div class="stat-icon">
<i class="fas fa-project-diagram"></i>
</div>
<div class="stat-value">{{ user.connections_count|default('128') }}</div>
<div class="stat-value">{{ stats.connections_count if stats and stats.connections_count else 0 }}</div>
<div class="stat-label">Verbindungen</div>
</div>
@@ -691,7 +529,7 @@
<div class="stat-icon">
<i class="fas fa-users"></i>
</div>
<div class="stat-value">{{ user.followers_count|default('567') }}</div>
<div class="stat-value">{{ stats.followers_count if stats and stats.followers_count else 0 }}</div>
<div class="stat-label">Follower</div>
</div>
@@ -700,7 +538,7 @@
<div class="stat-icon">
<i class="fas fa-comment-dots"></i>
</div>
<div class="stat-value">{{ user.contributions_count|default('89') }}</div>
<div class="stat-value">{{ stats.contributions_count if stats and stats.contributions_count else 0 }}</div>
<div class="stat-label">Beiträge</div>
</div>
@@ -709,7 +547,7 @@
<div class="stat-icon">
<i class="fas fa-star"></i>
</div>
<div class="stat-value">{{ user.rating|default('4.8') }}</div>
<div class="stat-value">{{ stats.rating if stats and stats.rating else '0.0' }}</div>
<div class="stat-label">Bewertung</div>
</div>
</div>
@@ -731,115 +569,124 @@
<!-- Aktivitäts-Tab (Standardmäßig angezeigt) -->
<div class="tab-content" id="activity-tab">
<div class="activity-feed">
<!-- Aktivität 1 -->
<div class="activity-card">
<div class="activity-header">
<div class="activity-title">Neuer Gedanke hinzugefügt</div>
<div class="activity-date">vor 2 Stunden</div>
</div>
<div class="activity-content">
<p>Ich habe einen neuen Gedanken zum Thema "Künstliche Intelligenz und Kreativität" hinzugefügt. Wie können KI-Tools uns dabei helfen, kreativer zu denken?</p>
</div>
<div class="activity-footer">
<div class="activity-reactions">
<button class="reaction-button">
<i class="fas fa-thumbs-up"></i> <span>24</span>
</button>
<button class="reaction-button">
<i class="fas fa-comment"></i> <span>8</span>
</button>
<button class="reaction-button">
<i class="fas fa-share"></i> <span>3</span>
</button>
</div>
<div class="activity-actions">
<button class="action-button">
Ansehen
</button>
{% if activities %}
{% for activity in activities %}
<div class="activity-card">
<div class="activity-header">
<div class="activity-title">{{ activity.title }}</div>
<div class="activity-date">{{ activity.date }}</div>
</div>
<div class="activity-content">
<p>{{ activity.content }}</p>
</div>
<div class="activity-footer">
<div class="activity-reactions">
<button class="reaction-button {% if activity.user_liked %}active{% endif %}">
<i class="fas fa-thumbs-up"></i> <span>{{ activity.likes }}</span>
</button>
<button class="reaction-button">
<i class="fas fa-comment"></i> <span>{{ activity.comments }}</span>
</button>
<button class="reaction-button">
<i class="fas fa-share"></i> <span>{{ activity.shares }}</span>
</button>
</div>
<div class="activity-actions">
<button class="action-button">
Ansehen
</button>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-12">
<i class="fas fa-history text-5xl text-gray-400 mb-4"></i>
<p class="text-gray-500">Noch keine Aktivitäten vorhanden</p>
</div>
</div>
<!-- Aktivität 2 -->
<div class="activity-card">
<div class="activity-header">
<div class="activity-title">Verbindung erstellt</div>
<div class="activity-date">vor 1 Tag</div>
</div>
<div class="activity-content">
<p>Ich habe eine neue Verbindung zwischen "Nachhaltige Entwicklung" und "Digitale Transformation" hergestellt. Es gibt interessante Überschneidungen in diesen Bereichen.</p>
</div>
<div class="activity-footer">
<div class="activity-reactions">
<button class="reaction-button active">
<i class="fas fa-thumbs-up"></i> <span>42</span>
</button>
<button class="reaction-button">
<i class="fas fa-comment"></i> <span>12</span>
</button>
<button class="reaction-button">
<i class="fas fa-share"></i> <span>7</span>
</button>
</div>
<div class="activity-actions">
<button class="action-button">
Ansehen
</button>
</div>
</div>
</div>
<!-- Aktivität 3 -->
<div class="activity-card">
<div class="activity-header">
<div class="activity-title">Sammlung erstellt</div>
<div class="activity-date">vor 3 Tagen</div>
</div>
<div class="activity-content">
<p>Ich habe eine neue Sammlung zum Thema "Zukunftstechnologien" erstellt. Diese Sammlung enthält Gedanken zu KI, Quantencomputing, Biotechnologie und mehr.</p>
</div>
<div class="activity-footer">
<div class="activity-reactions">
<button class="reaction-button">
<i class="fas fa-thumbs-up"></i> <span>17</span>
</button>
<button class="reaction-button">
<i class="fas fa-comment"></i> <span>4</span>
</button>
<button class="reaction-button">
<i class="fas fa-share"></i> <span>2</span>
</button>
</div>
<div class="activity-actions">
<button class="action-button">
Ansehen
</button>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Weitere Tab-Inhalte (werden per JavaScript angezeigt) -->
<div class="tab-content hidden" id="thoughts-tab">
<p class="text-center text-gray-400 py-12">
<i class="fas fa-spinner fa-spin text-3xl mb-4 block"></i>
Gedanken werden geladen...
</p>
<div id="thoughts-container">
{% if thoughts %}
{% for thought in thoughts %}
<div class="thought-item">
<h3>{{ thought.title }}</h3>
<p>{{ thought.content }}</p>
<div class="thought-meta">
<span>{{ thought.date }}</span>
<span>{{ thought.category }}</span>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-12">
<i class="fas fa-lightbulb text-5xl text-gray-400 mb-4"></i>
<p class="text-gray-500">Noch keine Gedanken erstellt</p>
<a href="{{ url_for('create_thought') }}" class="mt-4 inline-block px-4 py-2 bg-purple-600 text-white rounded-lg">Ersten Gedanken erstellen</a>
</div>
{% endif %}
</div>
</div>
<div class="tab-content hidden" id="collections-tab">
<p class="text-center text-gray-400 py-12">
<i class="fas fa-spinner fa-spin text-3xl mb-4 block"></i>
Sammlungen werden geladen...
</p>
<div id="collections-container">
{% if collections %}
{% for collection in collections %}
<div class="collection-item">
<h3>{{ collection.title }}</h3>
<p>{{ collection.description }}</p>
<div class="collection-meta">
<span>{{ collection.thoughts_count }} Gedanken</span>
<span>{{ collection.date }}</span>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-12">
<i class="fas fa-folder-open text-5xl text-gray-400 mb-4"></i>
<p class="text-gray-500">Noch keine Sammlungen erstellt</p>
<a href="{{ url_for('create_collection') }}" class="mt-4 inline-block px-4 py-2 bg-purple-600 text-white rounded-lg">Erste Sammlung erstellen</a>
</div>
{% endif %}
</div>
</div>
<div class="tab-content hidden" id="connections-tab">
<p class="text-center text-gray-400 py-12">
<i class="fas fa-spinner fa-spin text-3xl mb-4 block"></i>
Verbindungen werden geladen...
</p>
<div id="connections-container">
{% if connections %}
{% for connection in connections %}
<div class="connection-item">
<div class="connection-nodes">
<div class="connection-node">
<h4>{{ connection.source.title }}</h4>
<p>{{ connection.source.excerpt }}</p>
</div>
<div class="connection-type">
<i class="fas fa-arrow-right"></i>
<span>{{ connection.relation_type }}</span>
</div>
<div class="connection-node">
<h4>{{ connection.target.title }}</h4>
<p>{{ connection.target.excerpt }}</p>
</div>
</div>
<div class="connection-meta">
<span>{{ connection.date }}</span>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-12">
<i class="fas fa-project-diagram text-5xl text-gray-400 mb-4"></i>
<p class="text-gray-500">Noch keine Verbindungen erstellt</p>
<a href="{{ url_for('mindmap') }}" class="mt-4 inline-block px-4 py-2 bg-purple-600 text-white rounded-lg">Verbindungen in der Mindmap erstellen</a>
</div>
{% endif %}
</div>
</div>
<div class="tab-content hidden" id="settings-tab">
@@ -849,22 +696,22 @@
<div class="settings-card-body">
<div class="settings-group">
<label class="settings-label" for="name">Name</label>
<input type="text" id="name" class="settings-input" value="{{ user.name|default('Max Mustermann') }}" />
<input type="text" id="name" class="settings-input" value="{{ user.name }}" />
</div>
<div class="settings-group">
<label class="settings-label" for="bio">Über mich</label>
<textarea id="bio" class="settings-input" rows="4">{{ user.bio|default('Willkommen auf meinem Profil! Ich bin daran interessiert, Wissen zu vernetzen und neue Verbindungen zwischen verschiedenen Themengebieten zu entdecken.') }}</textarea>
<textarea id="bio" class="settings-input" rows="4">{{ user.bio }}</textarea>
</div>
<div class="settings-group">
<label class="settings-label" for="location">Standort</label>
<input type="text" id="location" class="settings-input" value="{{ user.location|default('Berlin, Deutschland') }}" />
<input type="text" id="location" class="settings-input" value="{{ user.location }}" />
</div>
<div class="settings-group">
<label class="settings-label" for="website">Website</label>
<input type="url" id="website" class="settings-input" value="{{ user.website|default('https://www.beispiel.de') }}" />
<input type="url" id="website" class="settings-input" value="{{ user.website }}" />
</div>
<button class="profile-action-btn primary mt-4">
@@ -878,7 +725,7 @@
<div class="settings-card-body">
<div class="settings-group">
<label class="settings-label" for="email">E-Mail-Adresse</label>
<input type="email" id="email" class="settings-input" value="{{ user.email|default('beispiel@email.com') }}" />
<input type="email" id="email" class="settings-input" value="{{ user.email }}" />
</div>
<div class="settings-group">
@@ -903,7 +750,7 @@
{% endblock %}
{% block extra_js %}
<script>
<script nonce="{{ csp_nonce }}">
document.addEventListener('DOMContentLoaded', function() {
// Profil-Tab-Funktionalität
const tabs = document.querySelectorAll('.profile-tab');