Compare commits
3 Commits
88f8e98df0
...
4a3092a4d2
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a3092a4d2 | |||
| 2d8cdc052f | |||
| 968515ce2b |
34
.cursor/rules/ai-integration.mdc
Normal file
34
.cursor/rules/ai-integration.mdc
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# KI-Integration
|
||||||
|
|
||||||
|
Die Anwendung integriert OpenAI für KI-Funktionalitäten:
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
- [app.py](mdc:app.py): OpenAI-Client-Initialisierung
|
||||||
|
- [requirements.txt](mdc:requirements.txt): OpenAI SDK als Abhängigkeit
|
||||||
|
|
||||||
|
## Endpunkte
|
||||||
|
- `/api/assistant`: Hauptendpunkt für KI-Anfragen
|
||||||
|
|
||||||
|
## Funktionalitäten
|
||||||
|
- Chatbot-Integration: Benutzer können mit einem KI-Assistenten kommunizieren
|
||||||
|
- Inhaltsanalyse: KI kann Gedanken und Konzepte analysieren
|
||||||
|
- Vorschläge: Kontextbezogene Vorschläge basierend auf dem Benutzerkontext
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
- Verwendet den OpenAI SDK für API-Aufrufe
|
||||||
|
- Kontextübergabe für konsistente Konversationen
|
||||||
|
- Streaming-Antworten für bessere Benutzererfahrung
|
||||||
|
|
||||||
|
## Konfigurationsparameter
|
||||||
|
- `OPENAI_API_KEY`: API-Schlüssel (in .env-Datei)
|
||||||
|
- Das System verwendet vorwiegend das Chat-Completion-API
|
||||||
|
|
||||||
|
## Sicherheitsmaßnahmen
|
||||||
|
- API-Schlüssel werden sicher über Umgebungsvariablen geladen
|
||||||
|
- Ratenbegrenzung und Fehlerbehandlung für API-Aufrufe
|
||||||
|
- Eingabevalidierung vor API-Anfragen
|
||||||
36
.cursor/rules/authentication.mdc
Normal file
36
.cursor/rules/authentication.mdc
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Authentifizierung und Benutzerrollen
|
||||||
|
|
||||||
|
Die Anwendung nutzt Flask-Login für das Authentifizierungssystem:
|
||||||
|
|
||||||
|
## Hauptkomponenten
|
||||||
|
- [LoginManager](mdc:app.py): Konfiguration im app.py
|
||||||
|
- [User Model](mdc:models.py): Die User-Klasse implementiert UserMixin für Flask-Login
|
||||||
|
- Passwort-Hashing: Verwendet Werkzeug Security für sichere Passwort-Speicherung
|
||||||
|
|
||||||
|
## Authentifizierungsrouten
|
||||||
|
- `/login`: Benutzeranmeldung (GET/POST)
|
||||||
|
- `/register`: Benutzerregistrierung (GET/POST)
|
||||||
|
- `/logout`: Benutzerabmeldung
|
||||||
|
|
||||||
|
## Benutzerrollen
|
||||||
|
- Reguläre Benutzer: Grundlegende Funktionen
|
||||||
|
- Administratoren (`is_admin=True`): Erweiterte Privilegien
|
||||||
|
|
||||||
|
## Zugriffskontrollen
|
||||||
|
- `@login_required`: Decorator für routenspezifischen Authentifizierungsschutz
|
||||||
|
- `@admin_required`: Benutzerdefinierter Decorator für Admin-Zugriffskontrolle
|
||||||
|
|
||||||
|
## Sitzungsverwaltung
|
||||||
|
- Tracking von Anmeldezeit (`last_login`)
|
||||||
|
- Langlebige Sitzungen für Präferenzen (z.B. Dark Mode)
|
||||||
|
- Angepasste Flash-Nachrichten
|
||||||
|
|
||||||
|
## Profilmanagement
|
||||||
|
- `/settings`: Benutzereinstellungen aktualisieren
|
||||||
|
- Passwortänderung
|
||||||
|
- Profildetails (Biografie, Avatar, etc.)
|
||||||
31
.cursor/rules/configuration.mdc
Normal file
31
.cursor/rules/configuration.mdc
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Konfiguration und Umgebungsvariablen
|
||||||
|
|
||||||
|
Die Anwendung verwendet Umgebungsvariablen für die Konfiguration:
|
||||||
|
|
||||||
|
## Konfigurationsdateien
|
||||||
|
- [.env](mdc:.env): Haupt-Umgebungsvariablen (nicht in Git)
|
||||||
|
- [example.env](mdc:example.env): Beispiel-Konfiguration als Vorlage
|
||||||
|
|
||||||
|
## Wichtige Konfigurationsparameter
|
||||||
|
- `SECRET_KEY`: Geheimer Schlüssel für Flask-Sitzungen
|
||||||
|
- `SQLALCHEMY_DATABASE_URI`: Datenbankverbindung
|
||||||
|
- `OPENAI_API_KEY`: API-Schlüssel für OpenAI-Integration
|
||||||
|
|
||||||
|
## Anwendungsinitialisierung
|
||||||
|
- [run.py](mdc:run.py): Lädt Umgebungsvariablen und startet die Anwendung
|
||||||
|
- [app.py](mdc:app.py): Konfiguriert Flask mit den geladenen Umgebungsvariablen
|
||||||
|
- [init_db.py](mdc:init_db.py): Initialisiert die Datenbank mit Beispieldaten
|
||||||
|
|
||||||
|
## Datenbank-Konfiguration
|
||||||
|
- SQLite-Datenbank im `/database`-Verzeichnis
|
||||||
|
- Automatische Erstellung der Datenbankstruktur bei Anwendungsstart
|
||||||
|
- Beispieldaten werden mit `init_database()` erstellt
|
||||||
|
|
||||||
|
## Ausführung der Anwendung
|
||||||
|
- Entwicklungsserver: `python run.py`
|
||||||
|
- In Produktion: Nutzung von Gunicorn (siehe requirements.txt)
|
||||||
31
.cursor/rules/data-models.mdc
Normal file
31
.cursor/rules/data-models.mdc
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Datenmodelle
|
||||||
|
|
||||||
|
Die Anwendung verwendet SQLAlchemy als ORM mit folgenden Hauptmodellen:
|
||||||
|
|
||||||
|
## Benutzer und Authentifizierung
|
||||||
|
- [User](mdc:models.py): Benutzermodell mit Authentifizierung und Profildaten
|
||||||
|
|
||||||
|
## Mind-Mapping und Wissensorganisation
|
||||||
|
- [Category](mdc:models.py): Wissenschaftliche Kategorien zur Organisation der Mindmap
|
||||||
|
- [MindMapNode](mdc:models.py): Knoten in der öffentlichen Mindmap
|
||||||
|
- [UserMindmap](mdc:models.py): Benutzerspezifische Mindmaps
|
||||||
|
- [UserMindmapNode](mdc:models.py): Speichert Positionen von Knoten in Benutzer-Mindmaps
|
||||||
|
- [MindmapNote](mdc:models.py): Private Notizen zu Mindmap-Elementen
|
||||||
|
|
||||||
|
## Gedanken und Inhalte
|
||||||
|
- [Thought](mdc:models.py): Gedanken und Konzepte, die in Mindmaps verknüpft werden
|
||||||
|
- [ThoughtRelation](mdc:models.py): Verknüpfungen zwischen verschiedenen Gedanken
|
||||||
|
- [ThoughtRating](mdc:models.py): Bewertungen von Gedanken durch Benutzer
|
||||||
|
- [Comment](mdc:models.py): Kommentare zu Gedanken
|
||||||
|
|
||||||
|
## Hauptbeziehungen
|
||||||
|
- Benutzer → Gedanken: 1-zu-n (Autor)
|
||||||
|
- Benutzer → MindMaps: 1-zu-n
|
||||||
|
- Gedanken ↔ MindMapNodes: n-zu-m
|
||||||
|
- Kategorien → MindMapNodes: 1-zu-n
|
||||||
|
- Gedanken ↔ Gedanken: über ThoughtRelation (gerichtete Beziehungen)
|
||||||
32
.cursor/rules/development-workflow.mdc
Normal file
32
.cursor/rules/development-workflow.mdc
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Entwicklungs-Workflow
|
||||||
|
|
||||||
|
## Grundlegende Entwicklungsschritte
|
||||||
|
1. Umgebung einrichten: Python 3.11 und Abhängigkeiten installieren
|
||||||
|
2. `.env`-Datei basierend auf `example.env` erstellen
|
||||||
|
3. Datenbank initialisieren: `python init_db.py`
|
||||||
|
4. Entwicklungsserver starten: `python run.py`
|
||||||
|
|
||||||
|
## Datenbankentwicklung
|
||||||
|
- Models in [models.py](mdc:models.py) definieren
|
||||||
|
- Migrationen bei Schemaänderungen durchführen
|
||||||
|
- Testdaten über [init_db.py](mdc:init_db.py) bereitstellen
|
||||||
|
|
||||||
|
## Anwendungsentwicklung
|
||||||
|
- Neue Routen in [app.py](mdc:app.py) hinzufügen
|
||||||
|
- Frontend-Templates in `/templates` erstellen/anpassen
|
||||||
|
- API-Endpoints für AJAX/Frontend-Integration implementieren
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Tests mit pytest schreiben (siehe requirements.txt)
|
||||||
|
- Flask-Testumgebung für Integrationstest verwenden
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
- Immer auf Datenbankmodelle zurückgreifen (kein Raw-SQL)
|
||||||
|
- API-Endpunkte mit Authentifizierung schützen
|
||||||
|
- Flash-Nachrichten für Benutzerrückmeldungen verwenden
|
||||||
|
- Code-Dokumentation in deutscher Sprache halten
|
||||||
41
.cursor/rules/frontend-structure.mdc
Normal file
41
.cursor/rules/frontend-structure.mdc
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Frontend-Struktur
|
||||||
|
|
||||||
|
Die Anwendung verwendet ein Flask-Jinja2-Template-System mit JavaScript-Erweiterungen:
|
||||||
|
|
||||||
|
## Template-Struktur
|
||||||
|
- `/templates`: Hauptverzeichnis für Jinja2-Templates
|
||||||
|
- `/templates/errors`: Fehlerseiten (404, 500, etc.)
|
||||||
|
- Layout-Templates für einheitliches Design
|
||||||
|
|
||||||
|
## Frontend-Assets
|
||||||
|
- `/static/css`: CSS-Dateien (mit Tailwind)
|
||||||
|
- `/static/css/src`: Quell-CSS-Dateien
|
||||||
|
- `/static/js`: JavaScript-Dateien
|
||||||
|
- `/static/js/modules`: Modulare JS-Komponenten
|
||||||
|
- `/static/img`: Bilder und grafische Elemente
|
||||||
|
|
||||||
|
## JavaScript-Funktionalität
|
||||||
|
- API-Integration: Asynchrone Kommunikation mit Backend
|
||||||
|
- Mindmap-Visualisierung: Interaktive Darstellung von Konzepten
|
||||||
|
- Benutzeroberflächen-Interaktivität: Drag & Drop, Tooltips, Modals
|
||||||
|
|
||||||
|
## CSS-Framework
|
||||||
|
- Tailwind CSS für responsive Design-Elemente
|
||||||
|
- TAILWIND CDN verwenden, nicht manuell build!
|
||||||
|
|
||||||
|
## Responsive Design
|
||||||
|
- Mobile-first Ansatz für verschiedene Gerätetypen
|
||||||
|
- Anpassungsfähiges Layout für verschiedene Bildschirmgrößen
|
||||||
|
|
||||||
|
## Zugänglichkeit
|
||||||
|
- Semantisches HTML für bessere Zugänglichkeit
|
||||||
|
- ARIA-Attribute für Screenreader-Unterstützung
|
||||||
|
|
||||||
|
## Internationalisierung
|
||||||
|
- Deutsche Benutzeroberfläche als Standard
|
||||||
|
- Vorbereitet für mehrsprachige Unterstützung
|
||||||
27
.cursor/rules/project-structure.mdc
Normal file
27
.cursor/rules/project-structure.mdc
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Projekt-Struktur (Systades)
|
||||||
|
|
||||||
|
## Hauptkomponenten
|
||||||
|
Diese Python-Flask-Webanwendung implementiert ein Mind-Mapping und Gedanken-Management System:
|
||||||
|
|
||||||
|
- [app.py](mdc:app.py): Hauptanwendungsdatei mit allen Routen und Endpunkten
|
||||||
|
- [models.py](mdc:models.py): Datenbankmodelle und Beziehungen
|
||||||
|
- [run.py](mdc:run.py): Startpunkt der Anwendung
|
||||||
|
- [init_db.py](mdc:init_db.py): Initialisiert die Datenbank mit Beispieldaten
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
- `/database`: Enthält SQLite-Datenbank
|
||||||
|
- `/docs`: Dokumentation
|
||||||
|
- `/static`: Frontend-Ressourcen (CSS, JS, Bilder)
|
||||||
|
- `/templates`: Jinja2-Templates für die Webseiten
|
||||||
|
- `/utils`: Hilfsfunktionen und -klassen
|
||||||
|
|
||||||
|
## Hauptfunktionalität
|
||||||
|
- Mind-Mapping: Visualisierung von Wissen und Beziehungen
|
||||||
|
- Gedanken-Management: Erfassung und Organisation von Ideen und Konzepten
|
||||||
|
- Benutzer-Management: Registrierung, Login, Profile
|
||||||
|
- API-Endpunkte: RESTful-Schnittstellen für Frontend-Integration
|
||||||
43
.cursor/rules/routing.mdc
Normal file
43
.cursor/rules/routing.mdc
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# Routing und API-Endpunkte
|
||||||
|
|
||||||
|
## Hauptrouten (Webseiten)
|
||||||
|
- `/`: Startseite
|
||||||
|
- `/login`, `/register`, `/logout`: Authentifizierung
|
||||||
|
- `/mindmap`: Öffentliche Mindmap-Ansicht
|
||||||
|
- `/profile`: Benutzerprofil
|
||||||
|
- `/settings`: Benutzereinstellungen
|
||||||
|
- `/search`: Suchfunktion
|
||||||
|
- `/my_account`: Kontoübersicht
|
||||||
|
|
||||||
|
## API-Endpunkte
|
||||||
|
### Mindmap-Verwaltung
|
||||||
|
- `/api/mindmap`: Öffentliche Mindmap-Daten abrufen
|
||||||
|
- `/api/mindmap/public`: Öffentliche Mindmap abrufen
|
||||||
|
- `/api/mindmap/user/<id>`: Benutzer-Mindmap abrufen
|
||||||
|
- `/api/mindmap/<id>/add_node`: Knoten hinzufügen
|
||||||
|
- `/api/mindmap/<id>/remove_node/<node_id>`: Knoten entfernen
|
||||||
|
- `/api/mindmap/<id>/update_node_position`: Knotenposition aktualisieren
|
||||||
|
- `/api/mindmap/<id>/notes`: Notizen verwalten
|
||||||
|
|
||||||
|
### Gedanken und Inhalte
|
||||||
|
- `/api/thoughts`: Gedanken erstellen
|
||||||
|
- `/api/thoughts/<id>`: Gedanken abrufen, aktualisieren, löschen
|
||||||
|
- `/api/thoughts/<id>/bookmark`: Lesezeichen setzen/entfernen
|
||||||
|
- `/api/nodes/<id>/thoughts`: Gedanken zu einem Knoten abrufen/hinzufügen
|
||||||
|
|
||||||
|
### System und Benutzereinstellungen
|
||||||
|
- `/api/set_dark_mode`, `/api/get_dark_mode`: Erscheinungsbild-Einstellungen
|
||||||
|
- `/api/assistant`: KI-Assistent-Kommunikation
|
||||||
|
- `/api/categories`: Kategorien abrufen
|
||||||
|
- `/api/get_flash_messages`: Flash-Nachrichten für AJAX-Anfragen
|
||||||
|
|
||||||
|
## Fehlerbehandlung
|
||||||
|
- 404: Page Not Found
|
||||||
|
- 403: Forbidden
|
||||||
|
- 500: Internal Server Error
|
||||||
|
- 429: Too Many Requests
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
SECRET_KEY=dein-geheimer-schluessel-hier
|
SECRET_KEY=dein-geheimer-schluessel-hier
|
||||||
|
|
||||||
# OpenAI API
|
# OpenAI API
|
||||||
OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier
|
OPENAI_API_KEY=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
|
||||||
|
|
||||||
# Datenbank
|
# Datenbank
|
||||||
# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden
|
# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden
|
||||||
64
COMMON_ERRORS.md
Normal file
64
COMMON_ERRORS.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# ABSOLUTE DON'TS:
|
||||||
|
- Verwendung von npm anstelle der Tailwind CDN
|
||||||
|
- Implementierung von Content Security Policy (CSP) - UNTER KEINEN UMSTÄNDEN!
|
||||||
|
- Implementierung von Cross-Site Request Forgery (CSRF) Schutz
|
||||||
|
- Implementierung von Security Headers
|
||||||
|
|
||||||
|
# HÄUFIGE FEHLER:
|
||||||
|
- Verwendung der falschen Datenbank (die korrekte ist: database/systades.db)
|
||||||
|
- Falsche Pfadangaben bei statischen Dateien
|
||||||
|
- Vergessen der deutschen Spracheinstellungen in Templates
|
||||||
|
- Nicht beachten der vorhandenen Projektstruktur
|
||||||
|
|
||||||
|
# Häufige Fehler und Lösungen
|
||||||
|
|
||||||
|
## Content Security Policy (CSP)
|
||||||
|
|
||||||
|
### Problem: Externe Ressourcen werden nicht geladen
|
||||||
|
**Fehler:** Externe Ressourcen wie CDNs werden nicht korrekt geladen.
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
1. Stellen Sie sicher, dass die URLs in den Templates korrekt sind:
|
||||||
|
```html
|
||||||
|
<link href="https://cdn.tailwindcss.com" rel="stylesheet">
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Überprüfen Sie die Netzwerkverbindung und ob die CDN-Domains erreichbar sind.
|
||||||
|
|
||||||
|
3. Verwenden Sie lokale Ressourcen als Alternative:
|
||||||
|
```html
|
||||||
|
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
|
||||||
|
```
|
||||||
|
### Problem: Tailwind CSS CDN wird blockiert
|
||||||
|
**Fehler:** Tailwind CSS kann nicht von CDN geladen werden.
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
1. Verwenden Sie die lokale Version von Tailwind CSS:
|
||||||
|
```html
|
||||||
|
<link href="{{ url_for('static', filename='css/tailwind.min.css') }}" rel="stylesheet">
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Alternativ können Sie die CDN-Version direkt im Template einbinden:
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Stellen Sie sicher, dass die Datei `static/css/tailwind.min.css` existiert und aktuell ist.
|
||||||
|
|
||||||
|
## Datenbank-Fehler
|
||||||
|
|
||||||
|
### Problem: Datenbank existiert nicht
|
||||||
|
**Fehler:** SQLite-Datenbank kann nicht geöffnet werden.
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
1. Datenbank initialisieren: `python TOOLS.py db:rebuild`
|
||||||
|
2. Sicherstellen, dass das Datenbankverzeichnis existiert und Schreibrechte hat
|
||||||
|
|
||||||
|
## Authentifizierung
|
||||||
|
|
||||||
|
### Problem: Login funktioniert nicht
|
||||||
|
**Fehler:** Benutzer kann sich nicht einloggen.
|
||||||
|
|
||||||
|
**Lösung:**
|
||||||
|
1. Standard-Admin-Benutzer erstellen: `python TOOLS.py user:admin`
|
||||||
|
2. Passwort zurücksetzen: `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD`
|
||||||
10
Dockerfile
10
Dockerfile
@@ -1,10 +0,0 @@
|
|||||||
FROM python:3.9-slim-buster
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY requirements.txt requirements.txt
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
|
|
||||||
COPY website .
|
|
||||||
|
|
||||||
CMD ["python", "app.py"]
|
|
||||||
237
README.md
237
README.md
@@ -1,94 +1,161 @@
|
|||||||
# MindMap Wissensnetzwerk
|
# MindMapProjekt - Roadmap
|
||||||
|
|
||||||
Eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen mit integriertem ChatGPT-Assistenten.
|
## Projektübersicht
|
||||||
|
Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen. Das Projekt wird umfassend überarbeitet, um ein modernes, benutzerfreundliches Design und erweiterte Funktionalitäten zu bieten.
|
||||||
|
|
||||||
## Features
|
## Technischer Stack
|
||||||
|
- **Backend**: Python/Flask
|
||||||
|
- **Frontend**:
|
||||||
|
- Tailwind CSS (via CDN) für moderne UI
|
||||||
|
- SVG-Bibliotheken für Visualisierungen (D3.js)
|
||||||
|
- JavaScript/Alpine.js für interaktive Komponenten
|
||||||
|
- **Datenbank**: SQLite mit SQLAlchemy
|
||||||
|
- **KI-Integration**: OpenAI API für intelligente Assistenz
|
||||||
|
|
||||||
- Interaktive Mindmap zur Visualisierung von Wissensverbindungen
|
## Installation und Verwendung
|
||||||
- Gedanken mit verschiedenen Beziehungstypen verknüpfen
|
|
||||||
- Suchfunktion für Gedanken und Verbindungen
|
|
||||||
- Bewertungssystem für Gedanken
|
|
||||||
- Dark/Light Mode
|
|
||||||
- **Integrierter KI-Assistent** mit OpenAI GPT-Integration
|
|
||||||
|
|
||||||
## Installation
|
### Installation
|
||||||
|
1. Repository klonen
|
||||||
|
2. Virtuelle Umgebung erstellen: `python -m venv venv`
|
||||||
|
3. Virtuelle Umgebung aktivieren:
|
||||||
|
- Windows: `venv\Scripts\activate`
|
||||||
|
- Unix/MacOS: `source venv/bin/activate`
|
||||||
|
4. Abhängigkeiten installieren: `pip install -r requirements.txt`
|
||||||
|
5. Datenbank initialisieren: `python TOOLS.py db:rebuild`
|
||||||
|
6. Admin-Benutzer erstellen: `python TOOLS.py user:admin`
|
||||||
|
7. Server starten: `python TOOLS.py server:run`
|
||||||
|
|
||||||
### Einfache Installation
|
### Standardbenutzer
|
||||||
|
- **Admin-Benutzer**: Username: `admin` / Passwort: `admin`
|
||||||
|
- **Testbenutzer**: Username: `user` / Passwort: `user`
|
||||||
|
|
||||||
Führe im übergeordneten Verzeichnis folgendes aus:
|
### Verwaltungswerkzeuge mit TOOLS.py
|
||||||
|
Das Projekt enthält ein zentrales Verwaltungsskript `TOOLS.py`, das verschiedene Hilfsfunktionen bietet:
|
||||||
|
|
||||||
```
|
#### Datenbankverwaltung
|
||||||
python setup.py
|
- `python TOOLS.py db:fix` - Reparieren der Datenbankstruktur
|
||||||
|
- `python TOOLS.py db:rebuild` - Datenbank neu aufbauen (löscht alle Daten!)
|
||||||
|
- `python TOOLS.py db:test` - Datenbankverbindung und Modelle testen
|
||||||
|
- `python TOOLS.py db:stats` - Datenbankstatistiken anzeigen
|
||||||
|
|
||||||
|
#### Benutzerverwaltung
|
||||||
|
- `python TOOLS.py user:list` - Alle Benutzer anzeigen
|
||||||
|
- `python TOOLS.py user:create -u USERNAME -e EMAIL -p PASSWORD [-a]` - Neuen Benutzer erstellen
|
||||||
|
- `python TOOLS.py user:admin` - Admin-Benutzer erstellen (admin/admin)
|
||||||
|
- `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD` - Benutzerpasswort zurücksetzen
|
||||||
|
- `python TOOLS.py user:delete -u USERNAME` - Benutzer löschen
|
||||||
|
|
||||||
|
#### Serververwaltung
|
||||||
|
- `python TOOLS.py server:run [--host HOST] [--port PORT] [--no-debug]` - Entwicklungsserver starten
|
||||||
|
|
||||||
|
Für detaillierte Hilfe: `python TOOLS.py -h`
|
||||||
|
|
||||||
|
## Roadmap der Überarbeitung
|
||||||
|
|
||||||
|
### Phase 1: Grundlegende Infrastruktur ✅
|
||||||
|
- [x] Bestandsaufnahme des aktuellen Projekts
|
||||||
|
- [x] Erstellung der Roadmap
|
||||||
|
- [x] Aktualisierung der Abhängigkeiten
|
||||||
|
- [x] Integration von Tailwind CSS
|
||||||
|
- [x] Einrichtung der SVG-Bibliotheken (D3.js)
|
||||||
|
- [x] Favicon erstellen
|
||||||
|
- [x] Setup-Skript für einfache Installation
|
||||||
|
|
||||||
|
### Phase 2: Design-Überarbeitung 🔄
|
||||||
|
- [x] Implementierung des Dark Mode
|
||||||
|
- [x] Erstellung eines modernen, minimalistischen UI mit Tech-Ästhetik
|
||||||
|
- [x] Responsive Design für alle Geräte
|
||||||
|
- [ ] Gestaltung der Landing Page mit großer Typografie
|
||||||
|
|
||||||
|
### Phase 3: Mindmap-Funktionalitäten 🔄
|
||||||
|
- [x] Verbesserte Visualisierung mit SVG und D3.js
|
||||||
|
- [x] Implementierung der Mouseover-Funktion
|
||||||
|
- [x] Entwicklung der Suchfunktion für Knoten
|
||||||
|
- [ ] Tagging-System für Inhalte
|
||||||
|
- [ ] Quellenmanagement und -verlinkung
|
||||||
|
- [ ] Upload-Funktionalität an Knotenpunkten
|
||||||
|
|
||||||
|
### Phase 4: Kernseitenentwicklung
|
||||||
|
- [ ] Überarbeitung der Startseite mit neuen Features
|
||||||
|
- [ ] Entwicklung der "Wer sind wir?"-Seite
|
||||||
|
- [ ] Implementierung von Impressum und Datenschutzerklärung
|
||||||
|
- [ ] Erstellung der Kontaktseite mit FAQs
|
||||||
|
- [ ] Überarbeitung des Benutzerprofilbereichs
|
||||||
|
|
||||||
|
### Phase 5: Community-Features
|
||||||
|
- [ ] Entwicklung des Autorenbereichs
|
||||||
|
- [ ] Implementierung von Community-Bereichen für Themenbereiche
|
||||||
|
- [ ] Verbesserter Kommentarbereich
|
||||||
|
- [ ] Benutzerrechtemanagement
|
||||||
|
|
||||||
|
### Phase 6: KI-Integration
|
||||||
|
- [ ] Implementierung des Frage-Antwort-Systems
|
||||||
|
- [ ] KI-generierte Themeneinleitungen
|
||||||
|
- [ ] Intelligente Suchunterstützung
|
||||||
|
- [ ] Geführte Pfade durch Themenbereiche
|
||||||
|
- [ ] Vorgeschlagene Chat-Möglichkeiten
|
||||||
|
|
||||||
|
### Phase 7: Benutzerprofilfunktionen
|
||||||
|
- [ ] Speichern von Thematiken
|
||||||
|
- [ ] Persönliche Mindmap/Pinboard
|
||||||
|
- [ ] Beitragsmanagement
|
||||||
|
- [ ] Benutzerstatistiken und -aktivitäten
|
||||||
|
|
||||||
|
### Phase 8: Testing und Optimierung
|
||||||
|
- [ ] Umfassende Tests aller Funktionen
|
||||||
|
- [ ] Performance-Optimierung
|
||||||
|
- [ ] SEO-Implementierung
|
||||||
|
- [ ] Barrierefreiheit prüfen und verbessern
|
||||||
|
|
||||||
|
### Phase 9: Dokumentation und Einführung
|
||||||
|
- [ ] Erstellung von Benutzeranleitungen
|
||||||
|
- [ ] Entwicklerdokumentation
|
||||||
|
- [ ] Administratorenhandbuch
|
||||||
|
- [ ] Guided Tour für neue Benutzer
|
||||||
|
|
||||||
|
## Aktueller Status
|
||||||
|
- **Phase 1**: ✅ Abgeschlossen
|
||||||
|
- **Phase 2**: 🔄 In Bearbeitung (75% abgeschlossen)
|
||||||
|
- **Phase 3**: 🔄 In Bearbeitung (50% abgeschlossen)
|
||||||
|
|
||||||
|
## Aktuelle Fortschritte
|
||||||
|
- Grundlegende UI modernisiert mit Tailwind CSS und Dark Mode
|
||||||
|
- Neues Favicon für bessere visuelle Identität erstellt
|
||||||
|
- Setup-Prozess vereinfacht mit einem Shell-Skript
|
||||||
|
- Mindmap-Visualisierung komplett überarbeitet mit D3.js für eine interaktivere Erfahrung
|
||||||
|
- Responsive Design für optimale Darstellung auf allen Geräten
|
||||||
|
|
||||||
|
## Nächste Schritte
|
||||||
|
- Fertigstellung der Landing Page
|
||||||
|
- Erstellung der "Wer sind wir?"-Seite
|
||||||
|
- Implementierung des Tagging-Systems für Gedanken
|
||||||
|
- Verbesserung der Gedankenansicht im Mindmap-Bereich
|
||||||
|
|
||||||
|
## Content Security Policy (CSP)
|
||||||
|
|
||||||
|
Die Anwendung implementiert eine Content Security Policy, um die Sicherheit zu erhöhen und unerwünschte externe Ressourcen zu blockieren. CSP wird in `app.py` konfiguriert und schränkt ein, welche Ressourcen geladen werden dürfen.
|
||||||
|
|
||||||
|
### Aktualisierung (06.06.2024)
|
||||||
|
Die Anwendung verwendet nun die Tailwind CSS CDN für vereinfachte Entwicklung. Die CSP wurde entsprechend angepasst, um die Domain `cdn.tailwindcss.com` zu erlauben.
|
||||||
|
|
||||||
|
### Lokale und CDN-Ressourcen
|
||||||
|
|
||||||
|
Die Anwendung nutzt eine Mischung aus lokalen Ressourcen und CDNs:
|
||||||
|
- **CDN-Ressourcen**:
|
||||||
|
- Tailwind CSS (cdn.tailwindcss.com)
|
||||||
|
- **Lokale Ressourcen**:
|
||||||
|
- Alpine.js
|
||||||
|
- Font Awesome
|
||||||
|
- Google Fonts (Inter und JetBrains Mono)
|
||||||
|
|
||||||
|
### CSP-Nonces
|
||||||
|
|
||||||
|
Die Anwendung verwendet Nonces für Inline-Skripte. In den Templates wird `{{ csp_nonce }}` verwendet, um den Nonce-Wert einzufügen:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script nonce="{{ csp_nonce }}">
|
||||||
|
// JavaScript Code
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Dies erstellt eine virtuelle Umgebung, installiert alle Abhängigkeiten und erstellt die CSS-Dateien mit Tailwind.
|
*Zuletzt aktualisiert: 06.06.2024*
|
||||||
|
|
||||||
### Manuelle Installation
|
|
||||||
|
|
||||||
1. Repository klonen:
|
|
||||||
```
|
|
||||||
git clone <repository-url>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Python-Abhängigkeiten installieren:
|
|
||||||
```
|
|
||||||
cd website
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Environment-Variablen konfigurieren:
|
|
||||||
```
|
|
||||||
cp example.env .env
|
|
||||||
```
|
|
||||||
Bearbeite die `.env`-Datei und füge deinen OpenAI API-Schlüssel ein.
|
|
||||||
|
|
||||||
4. CSS mit Tailwind erstellen:
|
|
||||||
```
|
|
||||||
python build_css.py
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Datenbank initialisieren:
|
|
||||||
```
|
|
||||||
python init_db.py
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Anwendung starten:
|
|
||||||
```
|
|
||||||
python run.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Entwicklung
|
|
||||||
|
|
||||||
Für die Entwicklung mit automatischem CSS-Reload:
|
|
||||||
|
|
||||||
```
|
|
||||||
python dev.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Dieser Befehl startet sowohl den Flask-Server als auch den Tailwind CSS-Watcher, der CSS bei Änderungen automatisch neu generiert.
|
|
||||||
|
|
||||||
## Verwendung des KI-Assistenten
|
|
||||||
|
|
||||||
Der KI-Assistent ist über folgende Wege zugänglich:
|
|
||||||
|
|
||||||
1. **Schwebende Schaltfläche**: In der unteren rechten Ecke der Webseite ist eine Roboter-Schaltfläche, die den Assistenten öffnet.
|
|
||||||
2. **Navigation**: In der Hauptnavigation gibt es ebenfalls eine Schaltfläche mit Roboter-Symbol.
|
|
||||||
3. **Startseite**: Im "KI-Assistent"-Abschnitt auf der Startseite gibt es einen "KI-Chat starten"-Button.
|
|
||||||
|
|
||||||
Der Assistent kann bei folgenden Aufgaben helfen:
|
|
||||||
|
|
||||||
- Erklärung von Themen und Konzepten
|
|
||||||
- Suche nach Verbindungen zwischen Gedanken
|
|
||||||
- Beantwortung von Fragen zur Plattform
|
|
||||||
- Vorschläge für neue Gedankenverbindungen
|
|
||||||
|
|
||||||
## Technologie-Stack
|
|
||||||
|
|
||||||
- **Backend**: Flask, SQLAlchemy
|
|
||||||
- **Frontend**: HTML, CSS, JavaScript, Tailwind CSS (ohne npm), Alpine.js
|
|
||||||
- **KI**: OpenAI GPT API
|
|
||||||
- **Datenbank**: SQLite (Standard), kann auf andere Datenbanken umgestellt werden
|
|
||||||
|
|
||||||
## Konfiguration
|
|
||||||
|
|
||||||
Die Anwendung kann über Umgebungsvariablen konfiguriert werden. Siehe `example.env` für verfügbare Optionen.
|
|
||||||
@@ -66,6 +66,20 @@ Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorien
|
|||||||
- [ ] Caching-Strategien für bessere Performance
|
- [ ] Caching-Strategien für bessere Performance
|
||||||
- [ ] Verbesserte Fehlerbehandlung und Logging
|
- [ ] Verbesserte Fehlerbehandlung und Logging
|
||||||
|
|
||||||
|
## KI-Integration
|
||||||
|
|
||||||
|
### Aktuelle Implementation
|
||||||
|
- Integration von OpenAI mit dem gpt-4o-mini-Modell für den KI-Assistenten
|
||||||
|
- Datenbankzugriff für den KI-Assistenten, um direkt Informationen aus der Datenbank abzufragen
|
||||||
|
- Verbesserte Benutzeroberfläche für den KI-Assistenten mit kontextbezogenen Vorschlägen
|
||||||
|
|
||||||
|
### Zukünftige Verbesserungen
|
||||||
|
- Implementierung von Vektorsuche für präzisere Datenbank-Abfragen durch die KI
|
||||||
|
- Erweiterung der KI-Funktionalität für tiefere Analyse von Zusammenhängen zwischen Gedanken
|
||||||
|
- KI-gestützte Vorschläge für neue Verbindungen zwischen Gedanken basierend auf Inhaltsanalyse
|
||||||
|
- Finetuning des KI-Modells auf die spezifischen Anforderungen der Anwendung
|
||||||
|
- Erweiterung auf multimodale Fähigkeiten (Bild- und Textanalyse)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Implementierungsdetails
|
## Implementierungsdetails
|
||||||
@@ -100,3 +114,16 @@ Die implementierten API-Endpunkte umfassen:
|
|||||||
- `/api/mindmap/<id>/update_node_position` - Aktualisierung von Knotenpositionen
|
- `/api/mindmap/<id>/update_node_position` - Aktualisierung von Knotenpositionen
|
||||||
- `/api/mindmap/<id>/notes` - Verwaltung von Notizen
|
- `/api/mindmap/<id>/notes` - Verwaltung von Notizen
|
||||||
- `/api/nodes/<id>/thoughts` - Abrufen und Hinzufügen von Gedanken zu Knoten
|
- `/api/nodes/<id>/thoughts` - Abrufen und Hinzufügen von Gedanken zu Knoten
|
||||||
|
|
||||||
|
## Aktuelle Änderungen
|
||||||
|
- Tailwind CSS wurde auf CDN-Version aktualisiert (06.06.2024)
|
||||||
|
- Content Security Policy (CSP) für Tailwind CSS CDN konfiguriert
|
||||||
|
|
||||||
|
## Zukünftige Aufgaben
|
||||||
|
- Überprüfung der Kompatibilität der Tailwind CSS CDN-Version mit allen UI-Komponenten
|
||||||
|
- Optimierung der Ladezeiten für mobile Geräte
|
||||||
|
- Überarbeitung der Dark Mode Funktionalität mit neuer Tailwind Version
|
||||||
|
|
||||||
|
## Langfristige Ziele
|
||||||
|
- Migration zu einer statisch kompilierten Tailwind CSS Version für Produktivumgebungen
|
||||||
|
- Implementierung von Tailwind Plugins für erweiterte UI-Funktionen
|
||||||
BIN
__pycache__/app.cpython-311.pyc
Normal file
BIN
__pycache__/app.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/app.cpython-36.pyc
Normal file
BIN
__pycache__/app.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session
|
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session, g
|
||||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
@@ -41,7 +41,29 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|||||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
|
||||||
|
|
||||||
# OpenAI API-Konfiguration
|
# OpenAI API-Konfiguration
|
||||||
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
api_key = os.environ.get("OPENAI_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
print("WARNUNG: Kein OPENAI_API_KEY in Umgebungsvariablen gefunden. KI-Funktionalität wird nicht verfügbar sein.")
|
||||||
|
api_key = "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"
|
||||||
|
|
||||||
|
client = OpenAI(api_key=api_key)
|
||||||
|
|
||||||
|
# Dark Mode Einstellung in Session speichern
|
||||||
|
@app.before_request
|
||||||
|
def handle_dark_mode():
|
||||||
|
if 'dark_mode' not in session:
|
||||||
|
session['dark_mode'] = False # Standardmäßig Light Mode
|
||||||
|
|
||||||
|
# Context processor für Dark Mode
|
||||||
|
@app.context_processor
|
||||||
|
def inject_dark_mode():
|
||||||
|
return {'dark_mode': session.get('dark_mode', False)}
|
||||||
|
|
||||||
|
# Route zum Umschalten des Dark Mode
|
||||||
|
@app.route('/toggle-dark-mode', methods=['POST'])
|
||||||
|
def toggle_dark_mode():
|
||||||
|
session['dark_mode'] = not session.get('dark_mode', False)
|
||||||
|
return jsonify({'success': True, 'dark_mode': session['dark_mode']})
|
||||||
|
|
||||||
# Context processor für globale Template-Variablen
|
# Context processor für globale Template-Variablen
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
@@ -63,6 +85,214 @@ db.init_app(app)
|
|||||||
login_manager = LoginManager(app)
|
login_manager = LoginManager(app)
|
||||||
login_manager.login_view = 'login'
|
login_manager.login_view = 'login'
|
||||||
|
|
||||||
|
# Erst nach der App-Initialisierung die DB-Check-Funktionen importieren
|
||||||
|
from utils.db_check import check_db_connection, initialize_db_if_needed
|
||||||
|
|
||||||
|
def create_default_categories():
|
||||||
|
"""Erstellt die Standardkategorien für die Mindmap"""
|
||||||
|
# Hauptkategorien
|
||||||
|
main_categories = [
|
||||||
|
{
|
||||||
|
"name": "Philosophie",
|
||||||
|
"description": "Philosophisches Denken und Konzepte",
|
||||||
|
"color_code": "#9F7AEA",
|
||||||
|
"icon": "fa-brain",
|
||||||
|
"subcategories": [
|
||||||
|
{"name": "Ethik", "description": "Moralische Grundsätze", "icon": "fa-balance-scale"},
|
||||||
|
{"name": "Logik", "description": "Gesetze des Denkens", "icon": "fa-project-diagram"},
|
||||||
|
{"name": "Erkenntnistheorie", "description": "Natur des Wissens", "icon": "fa-lightbulb"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Wissenschaft",
|
||||||
|
"description": "Wissenschaftliche Disziplinen und Forschung",
|
||||||
|
"color_code": "#48BB78",
|
||||||
|
"icon": "fa-flask",
|
||||||
|
"subcategories": [
|
||||||
|
{"name": "Physik", "description": "Gesetze der Materie und Energie", "icon": "fa-atom"},
|
||||||
|
{"name": "Biologie", "description": "Wissenschaft des Lebens", "icon": "fa-dna"},
|
||||||
|
{"name": "Mathematik", "description": "Abstrakte Strukturen", "icon": "fa-calculator"},
|
||||||
|
{"name": "Informatik", "description": "Wissenschaft der Datenverarbeitung", "icon": "fa-laptop-code"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Technologie",
|
||||||
|
"description": "Technologische Entwicklungen und Anwendungen",
|
||||||
|
"color_code": "#ED8936",
|
||||||
|
"icon": "fa-microchip",
|
||||||
|
"subcategories": [
|
||||||
|
{"name": "Künstliche Intelligenz", "description": "Intelligente Maschinen", "icon": "fa-robot"},
|
||||||
|
{"name": "Programmierung", "description": "Softwareentwicklung", "icon": "fa-code"},
|
||||||
|
{"name": "Elektronik", "description": "Elektronische Systeme", "icon": "fa-memory"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Künste",
|
||||||
|
"description": "Kunstformen und kulturelle Ausdrucksweisen",
|
||||||
|
"color_code": "#ED64A6",
|
||||||
|
"icon": "fa-palette",
|
||||||
|
"subcategories": [
|
||||||
|
{"name": "Literatur", "description": "Schriftliche Werke", "icon": "fa-book"},
|
||||||
|
{"name": "Musik", "description": "Klangkunst", "icon": "fa-music"},
|
||||||
|
{"name": "Bildende Kunst", "description": "Visuelle Kunstformen", "icon": "fa-paint-brush"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Psychologie",
|
||||||
|
"description": "Menschliches Verhalten und Geist",
|
||||||
|
"color_code": "#4299E1",
|
||||||
|
"icon": "fa-comments",
|
||||||
|
"subcategories": [
|
||||||
|
{"name": "Kognition", "description": "Denken und Wahrnehmen", "icon": "fa-brain"},
|
||||||
|
{"name": "Emotionen", "description": "Gefühlswelt", "icon": "fa-heart"},
|
||||||
|
{"name": "Persönlichkeit", "description": "Charaktereigenschaften", "icon": "fa-user"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Kategorien erstellen
|
||||||
|
for main_cat_data in main_categories:
|
||||||
|
# Prüfen, ob die Kategorie bereits existiert
|
||||||
|
existing_cat = Category.query.filter_by(name=main_cat_data["name"]).first()
|
||||||
|
if existing_cat:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Hauptkategorie erstellen
|
||||||
|
main_category = Category(
|
||||||
|
name=main_cat_data["name"],
|
||||||
|
description=main_cat_data["description"],
|
||||||
|
color_code=main_cat_data["color_code"],
|
||||||
|
icon=main_cat_data["icon"]
|
||||||
|
)
|
||||||
|
db.session.add(main_category)
|
||||||
|
db.session.flush() # Um die ID zu generieren
|
||||||
|
|
||||||
|
# Unterkategorien erstellen
|
||||||
|
for sub_cat_data in main_cat_data.get("subcategories", []):
|
||||||
|
sub_category = Category(
|
||||||
|
name=sub_cat_data["name"],
|
||||||
|
description=sub_cat_data["description"],
|
||||||
|
color_code=main_cat_data["color_code"],
|
||||||
|
icon=sub_cat_data.get("icon", main_cat_data["icon"]),
|
||||||
|
parent_id=main_category.id
|
||||||
|
)
|
||||||
|
db.session.add(sub_category)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
print("Standard-Kategorien wurden erstellt!")
|
||||||
|
|
||||||
|
def initialize_database():
|
||||||
|
"""Initialisiert die Datenbank mit Grunddaten, falls diese leer ist"""
|
||||||
|
try:
|
||||||
|
print("Initialisiere die Datenbank...")
|
||||||
|
|
||||||
|
# Erstelle alle Tabellen
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# Prüfe, ob bereits Benutzer existieren
|
||||||
|
if User.query.count() == 0:
|
||||||
|
print("Erstelle Admin-Benutzer...")
|
||||||
|
admin = User(
|
||||||
|
username="admin",
|
||||||
|
email="admin@example.com",
|
||||||
|
is_admin=True
|
||||||
|
)
|
||||||
|
admin.set_password("admin123") # In echter Umgebung ein sicheres Passwort verwenden!
|
||||||
|
db.session.add(admin)
|
||||||
|
|
||||||
|
# Prüfe, ob bereits Kategorien existieren
|
||||||
|
if Category.query.count() == 0:
|
||||||
|
print("Erstelle Standard-Kategorien...")
|
||||||
|
create_default_categories()
|
||||||
|
|
||||||
|
# Stelle sicher, dass die Standard-Knoten für die öffentliche Mindmap existieren
|
||||||
|
if MindMapNode.query.count() == 0:
|
||||||
|
print("Erstelle Standard-Knoten für die Mindmap...")
|
||||||
|
|
||||||
|
# Hauptknoten: Wissen
|
||||||
|
root_node = MindMapNode(
|
||||||
|
name="Wissen",
|
||||||
|
description="Zentrale Wissensbasis",
|
||||||
|
color_code="#4299E1",
|
||||||
|
is_public=True
|
||||||
|
)
|
||||||
|
db.session.add(root_node)
|
||||||
|
db.session.flush() # Um die ID zu generieren
|
||||||
|
|
||||||
|
# Verwandte Kategorien finden
|
||||||
|
philosophy = Category.query.filter_by(name="Philosophie").first()
|
||||||
|
science = Category.query.filter_by(name="Wissenschaft").first()
|
||||||
|
technology = Category.query.filter_by(name="Technologie").first()
|
||||||
|
arts = Category.query.filter_by(name="Künste").first()
|
||||||
|
|
||||||
|
# Erstelle Hauptthemenknoten
|
||||||
|
nodes = [
|
||||||
|
MindMapNode(
|
||||||
|
name="Philosophie",
|
||||||
|
description="Philosophisches Denken",
|
||||||
|
color_code="#9F7AEA",
|
||||||
|
category=philosophy,
|
||||||
|
is_public=True
|
||||||
|
),
|
||||||
|
MindMapNode(
|
||||||
|
name="Wissenschaft",
|
||||||
|
description="Wissenschaftliche Erkenntnisse",
|
||||||
|
color_code="#48BB78",
|
||||||
|
category=science,
|
||||||
|
is_public=True
|
||||||
|
),
|
||||||
|
MindMapNode(
|
||||||
|
name="Technologie",
|
||||||
|
description="Technologische Entwicklungen",
|
||||||
|
color_code="#ED8936",
|
||||||
|
category=technology,
|
||||||
|
is_public=True
|
||||||
|
),
|
||||||
|
MindMapNode(
|
||||||
|
name="Künste",
|
||||||
|
description="Künstlerische Ausdrucksformen",
|
||||||
|
color_code="#ED64A6",
|
||||||
|
category=arts,
|
||||||
|
is_public=True
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Füge Knoten zur Datenbank hinzu
|
||||||
|
for node in nodes:
|
||||||
|
db.session.add(node)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Nachdem wir die IDs haben, füge die Verbindungen hinzu
|
||||||
|
all_nodes = MindMapNode.query.all()
|
||||||
|
root = MindMapNode.query.filter_by(name="Wissen").first()
|
||||||
|
|
||||||
|
if root:
|
||||||
|
for node in all_nodes:
|
||||||
|
if node.id != root.id:
|
||||||
|
root.children.append(node)
|
||||||
|
|
||||||
|
# Speichere die Änderungen
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
print("Datenbankinitialisierung abgeschlossen.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler bei der Datenbankinitialisierung: {str(e)}")
|
||||||
|
db.session.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Instead of before_first_request, which is deprecated in newer Flask versions
|
||||||
|
# Use a function to initialize the database that will be called during app creation
|
||||||
|
def init_app_database(app_instance):
|
||||||
|
"""Initialisiert die Datenbank für die Flask-App"""
|
||||||
|
with app_instance.app_context():
|
||||||
|
# Überprüfe und initialisiere die Datenbank bei Bedarf
|
||||||
|
if not initialize_db_if_needed(db, initialize_database):
|
||||||
|
print("WARNUNG: Datenbankinitialisierung fehlgeschlagen. Einige Funktionen könnten eingeschränkt sein.")
|
||||||
|
|
||||||
|
# Call the function to initialize the database
|
||||||
|
init_app_database(app)
|
||||||
|
|
||||||
# Benutzerdefinierter Decorator für Admin-Zugriff
|
# Benutzerdefinierter Decorator für Admin-Zugriff
|
||||||
def admin_required(f):
|
def admin_required(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
@@ -145,14 +375,22 @@ def index():
|
|||||||
@app.route('/mindmap')
|
@app.route('/mindmap')
|
||||||
def mindmap():
|
def mindmap():
|
||||||
"""Zeigt die öffentliche Mindmap an."""
|
"""Zeigt die öffentliche Mindmap an."""
|
||||||
# Sicherstellen, dass wir Kategorien haben
|
try:
|
||||||
with app.app_context():
|
# Sicherstellen, dass wir Kategorien haben
|
||||||
if Category.query.count() == 0:
|
if Category.query.count() == 0:
|
||||||
create_default_categories()
|
create_default_categories()
|
||||||
|
|
||||||
# Hole alle Kategorien der obersten Ebene
|
# Hole alle Kategorien der obersten Ebene
|
||||||
categories = Category.query.filter_by(parent_id=None).all()
|
categories = Category.query.filter_by(parent_id=None).all()
|
||||||
return render_template('mindmap.html', categories=categories)
|
|
||||||
|
# Transformiere Kategorien in ein anzeigbares Format für die Vorlage
|
||||||
|
category_tree = [build_category_tree(cat) for cat in categories]
|
||||||
|
|
||||||
|
return render_template('mindmap.html', categories=category_tree)
|
||||||
|
except Exception as e:
|
||||||
|
# Bei Fehler leere Kategorienliste übergeben und Fehler protokollieren
|
||||||
|
print(f"Fehler beim Laden der Mindmap-Kategorien: {str(e)}")
|
||||||
|
return render_template('mindmap.html', categories=[], error=str(e))
|
||||||
|
|
||||||
# Route for user profile
|
# Route for user profile
|
||||||
@app.route('/profile')
|
@app.route('/profile')
|
||||||
@@ -160,16 +398,47 @@ def mindmap():
|
|||||||
def profile():
|
def profile():
|
||||||
# Lade Benutzer-Mindmaps
|
# Lade Benutzer-Mindmaps
|
||||||
user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
|
user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
|
||||||
|
|
||||||
# Lade Statistiken
|
# Lade Statistiken
|
||||||
thought_count = Thought.query.filter_by(user_id=current_user.id).count()
|
thought_count = Thought.query.filter_by(user_id=current_user.id).count()
|
||||||
bookmark_count = db.session.query(func.count()).select_from(user_thought_bookmark).filter(
|
bookmark_count = db.session.query(user_thought_bookmark).filter(
|
||||||
user_thought_bookmark.c.user_id == current_user.id
|
user_thought_bookmark.c.user_id == current_user.id).count()
|
||||||
).scalar()
|
|
||||||
|
# Berechne tatsächliche Werte für Benutzerstatistiken
|
||||||
|
contributions_count = Comment.query.filter_by(user_id=current_user.id).count()
|
||||||
|
|
||||||
|
# Berechne Verbindungen (Anzahl der Gedankenverknüpfungen)
|
||||||
|
connections_count = ThoughtRelation.query.filter(
|
||||||
|
(ThoughtRelation.source_id.in_(
|
||||||
|
db.session.query(Thought.id).filter_by(user_id=current_user.id)
|
||||||
|
)) |
|
||||||
|
(ThoughtRelation.target_id.in_(
|
||||||
|
db.session.query(Thought.id).filter_by(user_id=current_user.id)
|
||||||
|
))
|
||||||
|
).count()
|
||||||
|
|
||||||
|
# Berechne durchschnittliche Bewertung der Gedanken des Benutzers
|
||||||
|
avg_rating = db.session.query(func.avg(ThoughtRating.relevance_score)).join(
|
||||||
|
Thought, Thought.id == ThoughtRating.thought_id
|
||||||
|
).filter(Thought.user_id == current_user.id).scalar() or 0
|
||||||
|
|
||||||
|
# Hole die Anzahl der Follower (falls implementiert)
|
||||||
|
# In diesem Beispiel nehmen wir an, dass es keine Follower-Funktionalität gibt
|
||||||
|
followers_count = 0
|
||||||
|
|
||||||
|
# Hole den Standort des Benutzers aus der Datenbank, falls vorhanden
|
||||||
|
location = "Deutschland" # Standardwert
|
||||||
|
|
||||||
return render_template('profile.html',
|
return render_template('profile.html',
|
||||||
|
user=current_user,
|
||||||
user_mindmaps=user_mindmaps,
|
user_mindmaps=user_mindmaps,
|
||||||
thought_count=thought_count,
|
thought_count=thought_count,
|
||||||
bookmark_count=bookmark_count)
|
bookmark_count=bookmark_count,
|
||||||
|
connections_count=connections_count,
|
||||||
|
contributions_count=contributions_count,
|
||||||
|
followers_count=followers_count,
|
||||||
|
rating=round(avg_rating, 1),
|
||||||
|
location=location)
|
||||||
|
|
||||||
# Route für Benutzereinstellungen
|
# Route für Benutzereinstellungen
|
||||||
@app.route('/settings', methods=['GET', 'POST'])
|
@app.route('/settings', methods=['GET', 'POST'])
|
||||||
@@ -328,34 +597,45 @@ def get_public_mindmap():
|
|||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
def build_category_tree(category):
|
def build_category_tree(category):
|
||||||
"""Rekursive Funktion zum Aufbau der Kategoriestruktur."""
|
"""
|
||||||
nodes = []
|
Erstellt eine Baumstruktur für eine Kategorie mit all ihren Unterkategorien
|
||||||
# Hole alle Knoten in dieser Kategorie
|
und dazugehörigen Knoten
|
||||||
for node in category.nodes:
|
|
||||||
if node.is_public:
|
|
||||||
nodes.append({
|
|
||||||
'id': node.id,
|
|
||||||
'name': node.name,
|
|
||||||
'description': node.description,
|
|
||||||
'color_code': node.color_code,
|
|
||||||
'thought_count': len(node.thoughts)
|
|
||||||
})
|
|
||||||
|
|
||||||
# Rekursiv durch Unterkaterorien
|
Args:
|
||||||
children = []
|
category: Ein Category-Objekt
|
||||||
for child in category.children:
|
|
||||||
children.append(build_category_tree(child))
|
|
||||||
|
|
||||||
return {
|
Returns:
|
||||||
|
dict: Eine JSON-serialisierbare Darstellung der Kategoriestruktur
|
||||||
|
"""
|
||||||
|
# Kategorie-Basisinformationen
|
||||||
|
category_dict = {
|
||||||
'id': category.id,
|
'id': category.id,
|
||||||
'name': category.name,
|
'name': category.name,
|
||||||
'description': category.description,
|
'description': category.description,
|
||||||
'color_code': category.color_code,
|
'color_code': category.color_code,
|
||||||
'icon': category.icon,
|
'icon': category.icon,
|
||||||
'nodes': nodes,
|
'nodes': [],
|
||||||
'children': children
|
'children': []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Knoten zur Kategorie hinzufügen
|
||||||
|
if category.nodes:
|
||||||
|
for node in category.nodes:
|
||||||
|
category_dict['nodes'].append({
|
||||||
|
'id': node.id,
|
||||||
|
'name': node.name,
|
||||||
|
'description': node.description or '',
|
||||||
|
'color_code': node.color_code or '#9F7AEA',
|
||||||
|
'thought_count': len(node.thoughts) if hasattr(node, 'thoughts') else 0
|
||||||
|
})
|
||||||
|
|
||||||
|
# Rekursiv Unterkategorien hinzufügen
|
||||||
|
if category.children:
|
||||||
|
for child in category.children:
|
||||||
|
category_dict['children'].append(build_category_tree(child))
|
||||||
|
|
||||||
|
return category_dict
|
||||||
|
|
||||||
@app.route('/api/mindmap/user/<int:mindmap_id>')
|
@app.route('/api/mindmap/user/<int:mindmap_id>')
|
||||||
@login_required
|
@login_required
|
||||||
def get_user_mindmap(mindmap_id):
|
def get_user_mindmap(mindmap_id):
|
||||||
@@ -874,17 +1154,21 @@ def bookmark_thought(thought_id):
|
|||||||
|
|
||||||
@app.route('/api/categories')
|
@app.route('/api/categories')
|
||||||
def get_categories():
|
def get_categories():
|
||||||
"""Liefert alle verfügbaren Kategorien."""
|
"""API-Endpunkt, der alle Kategorien als hierarchische Struktur zurückgibt"""
|
||||||
categories = Category.query.all()
|
try:
|
||||||
|
# Hole alle Kategorien der obersten Ebene
|
||||||
|
categories = Category.query.filter_by(parent_id=None).all()
|
||||||
|
|
||||||
return jsonify([{
|
# Transformiere zu einer Baumstruktur
|
||||||
'id': category.id,
|
category_tree = [build_category_tree(cat) for cat in categories]
|
||||||
'name': category.name,
|
|
||||||
'description': category.description,
|
return jsonify(category_tree)
|
||||||
'color_code': category.color_code,
|
except Exception as e:
|
||||||
'icon': category.icon,
|
print(f"Fehler beim Abrufen der Kategorien: {str(e)}")
|
||||||
'parent_id': category.parent_id
|
return jsonify({
|
||||||
} for category in categories])
|
'success': False,
|
||||||
|
'error': 'Kategorien konnten nicht geladen werden'
|
||||||
|
}), 500
|
||||||
|
|
||||||
@app.route('/api/set_dark_mode', methods=['POST'])
|
@app.route('/api/set_dark_mode', methods=['POST'])
|
||||||
def set_dark_mode():
|
def set_dark_mode():
|
||||||
@@ -930,7 +1214,7 @@ def too_many_requests(e):
|
|||||||
# OpenAI-Integration für KI-Assistenz
|
# OpenAI-Integration für KI-Assistenz
|
||||||
@app.route('/api/assistant', methods=['POST'])
|
@app.route('/api/assistant', methods=['POST'])
|
||||||
def chat_with_assistant():
|
def chat_with_assistant():
|
||||||
"""Chatbot-API mit OpenAI Integration."""
|
"""Chatbot-API mit OpenAI Integration und Datenbankzugriff."""
|
||||||
data = request.json
|
data = request.json
|
||||||
|
|
||||||
# Prüfen, ob wir ein einzelnes Prompt oder ein messages-Array haben
|
# Prüfen, ob wir ein einzelnes Prompt oder ein messages-Array haben
|
||||||
@@ -943,9 +1227,9 @@ def chat_with_assistant():
|
|||||||
|
|
||||||
# Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht
|
# Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht
|
||||||
system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'),
|
system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'),
|
||||||
"Du bist ein hilfreicher Assistent, der Menschen dabei hilft, "
|
"Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. "
|
||||||
"Wissen zu organisieren und zu verknüpfen. Liefere informative, "
|
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. "
|
||||||
"sachliche und gut strukturierte Antworten.")
|
"Antworte informativ, sachlich und gut strukturiert auf Deutsch.")
|
||||||
|
|
||||||
# Formatiere Nachrichten für OpenAI API
|
# Formatiere Nachrichten für OpenAI API
|
||||||
api_messages = [{"role": "system", "content": system_message}]
|
api_messages = [{"role": "system", "content": system_message}]
|
||||||
@@ -966,9 +1250,9 @@ def chat_with_assistant():
|
|||||||
|
|
||||||
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
|
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
|
||||||
system_message = (
|
system_message = (
|
||||||
"Du bist ein hilfreicher Assistent, der Menschen dabei hilft, "
|
"Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. "
|
||||||
"Wissen zu organisieren und zu verknüpfen. Liefere informative, "
|
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. "
|
||||||
"sachliche und gut strukturierte Antworten."
|
"Antworte informativ, sachlich und gut strukturiert auf Deutsch."
|
||||||
)
|
)
|
||||||
|
|
||||||
if context:
|
if context:
|
||||||
@@ -979,14 +1263,41 @@ def chat_with_assistant():
|
|||||||
{"role": "user", "content": prompt}
|
{"role": "user", "content": prompt}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Extrahiere die letzte Benutzernachricht für Datenbankabfragen
|
||||||
|
user_message = next((msg['content'] for msg in reversed(api_messages) if msg['role'] == 'user'), '')
|
||||||
|
|
||||||
|
# Prüfen, ob die Anfrage nach Datenbankinformationen sucht
|
||||||
|
db_context = check_database_query(user_message)
|
||||||
|
|
||||||
|
if db_context:
|
||||||
|
# Erweitere den Kontext mit Datenbankinformationen
|
||||||
|
api_messages.append({
|
||||||
|
"role": "system",
|
||||||
|
"content": f"Hier sind relevante Informationen aus der Datenbank:\n\n{db_context}"
|
||||||
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Überprüfen ob OpenAI API-Key konfiguriert ist
|
||||||
|
if not client.api_key or client.api_key.startswith("sk-dummy"):
|
||||||
|
print("Warnung: OpenAI API-Key ist nicht oder nur als Dummy konfiguriert!")
|
||||||
|
return jsonify({
|
||||||
|
'error': 'Der OpenAI API-Key ist nicht korrekt konfiguriert. Bitte konfigurieren Sie die Umgebungsvariable OPENAI_API_KEY.'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
# API-Aufruf mit Timeout
|
||||||
|
import time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model="gpt-4o-mini",
|
model="gpt-4o-mini",
|
||||||
messages=api_messages,
|
messages=api_messages,
|
||||||
max_tokens=300,
|
max_tokens=600, # Erhöht für längere, detailliertere Antworten
|
||||||
temperature=0.7
|
temperature=0.7,
|
||||||
|
timeout=20 # 20 Sekunden Timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print(f"OpenAI API-Antwortzeit: {time.time() - start_time:.2f} Sekunden")
|
||||||
|
|
||||||
answer = response.choices[0].message.content
|
answer = response.choices[0].message.content
|
||||||
|
|
||||||
# Für das neue Format erwarten wir response statt answer
|
# Für das neue Format erwarten wir response statt answer
|
||||||
@@ -995,134 +1306,77 @@ def chat_with_assistant():
|
|||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
print(f"Fehler bei der OpenAI-Anfrage: {str(e)}")
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}'
|
'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}'
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
# App-Kontext-Funktion für Initialisierung der Datenbank
|
def check_database_query(user_message):
|
||||||
def create_default_categories():
|
"""
|
||||||
"""Erstellt die Standard-Kategorien und wissenschaftlichen Bereiche."""
|
Überprüft, ob die Benutzeranfrage nach Datenbankinformationen sucht und extrahiert
|
||||||
categories = [
|
relevante Daten aus der Datenbank.
|
||||||
{
|
"""
|
||||||
'name': 'Naturwissenschaften',
|
context = []
|
||||||
'description': 'Empirische Untersuchung und Erklärung natürlicher Phänomene',
|
|
||||||
'color_code': '#4CAF50',
|
|
||||||
'icon': 'flask',
|
|
||||||
'children': [
|
|
||||||
{
|
|
||||||
'name': 'Physik',
|
|
||||||
'description': 'Studium der Materie, Energie und deren Wechselwirkungen',
|
|
||||||
'color_code': '#81C784',
|
|
||||||
'icon': 'atom'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Biologie',
|
|
||||||
'description': 'Wissenschaft des Lebens und lebender Organismen',
|
|
||||||
'color_code': '#66BB6A',
|
|
||||||
'icon': 'leaf'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Chemie',
|
|
||||||
'description': 'Wissenschaft der Materie, ihrer Eigenschaften und Reaktionen',
|
|
||||||
'color_code': '#A5D6A7',
|
|
||||||
'icon': 'vial'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Sozialwissenschaften',
|
|
||||||
'description': 'Untersuchung von Gesellschaft und menschlichem Verhalten',
|
|
||||||
'color_code': '#2196F3',
|
|
||||||
'icon': 'users',
|
|
||||||
'children': [
|
|
||||||
{
|
|
||||||
'name': 'Psychologie',
|
|
||||||
'description': 'Wissenschaftliches Studium des Geistes und Verhaltens',
|
|
||||||
'color_code': '#64B5F6',
|
|
||||||
'icon': 'brain'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Soziologie',
|
|
||||||
'description': 'Studium sozialer Beziehungen und Institutionen',
|
|
||||||
'color_code': '#42A5F5',
|
|
||||||
'icon': 'network-wired'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Geisteswissenschaften',
|
|
||||||
'description': 'Studium menschlicher Kultur und Kreativität',
|
|
||||||
'color_code': '#9C27B0',
|
|
||||||
'icon': 'book',
|
|
||||||
'children': [
|
|
||||||
{
|
|
||||||
'name': 'Philosophie',
|
|
||||||
'description': 'Untersuchung grundlegender Fragen über Existenz, Wissen und Ethik',
|
|
||||||
'color_code': '#BA68C8',
|
|
||||||
'icon': 'lightbulb'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Geschichte',
|
|
||||||
'description': 'Studium der Vergangenheit und ihres Einflusses auf die Gegenwart',
|
|
||||||
'color_code': '#AB47BC',
|
|
||||||
'icon': 'landmark'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Literatur',
|
|
||||||
'description': 'Studium literarischer Werke und ihrer Bedeutung',
|
|
||||||
'color_code': '#CE93D8',
|
|
||||||
'icon': 'feather'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Technologie',
|
|
||||||
'description': 'Anwendung wissenschaftlicher Erkenntnisse für praktische Zwecke',
|
|
||||||
'color_code': '#FF9800',
|
|
||||||
'icon': 'microchip',
|
|
||||||
'children': [
|
|
||||||
{
|
|
||||||
'name': 'Informatik',
|
|
||||||
'description': 'Studium von Computern und Berechnungssystemen',
|
|
||||||
'color_code': '#FFB74D',
|
|
||||||
'icon': 'laptop-code'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'Künstliche Intelligenz',
|
|
||||||
'description': 'Entwicklung intelligenter Maschinen und Software',
|
|
||||||
'color_code': '#FFA726',
|
|
||||||
'icon': 'robot'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
# Kategorien in die Datenbank einfügen
|
# Prüfen auf verschiedene Datenbankabfragemuster
|
||||||
for category_data in categories:
|
if any(keyword in user_message.lower() for keyword in ['gedanken', 'thought', 'beitrag', 'inhalt']):
|
||||||
children_data = category_data.pop('children', [])
|
# Suche nach relevanten Gedanken
|
||||||
category = Category(**category_data)
|
thoughts = Thought.query.filter(
|
||||||
db.session.add(category)
|
db.or_(
|
||||||
db.session.flush() # Um die ID zu generieren
|
Thought.title.ilike(f'%{word}%') for word in user_message.split()
|
||||||
|
if len(word) > 3 # Nur längere Wörter zur Suche verwenden
|
||||||
|
)
|
||||||
|
).limit(5).all()
|
||||||
|
|
||||||
# Unterkategorien hinzufügen
|
if thoughts:
|
||||||
for child_data in children_data:
|
context.append("Relevante Gedanken:")
|
||||||
child = Category(**child_data, parent_id=category.id)
|
for thought in thoughts:
|
||||||
db.session.add(child)
|
context.append(f"- Titel: {thought.title}")
|
||||||
|
context.append(f" Zusammenfassung: {thought.abstract if thought.abstract else 'Keine Zusammenfassung verfügbar'}")
|
||||||
|
context.append(f" Keywords: {thought.keywords if thought.keywords else 'Keine Keywords verfügbar'}")
|
||||||
|
context.append("")
|
||||||
|
|
||||||
db.session.commit()
|
if any(keyword in user_message.lower() for keyword in ['kategorie', 'category', 'themengebiet', 'bereich']):
|
||||||
print("Standard-Kategorien wurden erstellt!")
|
# Suche nach Kategorien
|
||||||
|
categories = Category.query.filter(
|
||||||
|
db.or_(
|
||||||
|
Category.name.ilike(f'%{word}%') for word in user_message.split()
|
||||||
|
if len(word) > 3
|
||||||
|
)
|
||||||
|
).limit(5).all()
|
||||||
|
|
||||||
def initialize_database():
|
if categories:
|
||||||
"""Initialisiert die Datenbank, falls sie noch nicht existiert."""
|
context.append("Relevante Kategorien:")
|
||||||
db.create_all()
|
for category in categories:
|
||||||
|
context.append(f"- Name: {category.name}")
|
||||||
|
context.append(f" Beschreibung: {category.description}")
|
||||||
|
context.append("")
|
||||||
|
|
||||||
# Überprüfe, ob bereits Kategorien existieren
|
if any(keyword in user_message.lower() for keyword in ['mindmap', 'karte', 'visualisierung']):
|
||||||
if Category.query.count() == 0:
|
# Suche nach öffentlichen Mindmaps
|
||||||
create_default_categories()
|
mindmap_nodes = MindMapNode.query.filter(
|
||||||
|
db.and_(
|
||||||
|
MindMapNode.is_public == True,
|
||||||
|
db.or_(
|
||||||
|
MindMapNode.name.ilike(f'%{word}%') for word in user_message.split()
|
||||||
|
if len(word) > 3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).limit(5).all()
|
||||||
|
|
||||||
# Führe die Datenbankinitialisierung beim Starten der App aus
|
if mindmap_nodes:
|
||||||
with app.app_context():
|
context.append("Relevante Mindmap-Knoten:")
|
||||||
initialize_database()
|
for node in mindmap_nodes:
|
||||||
|
context.append(f"- Name: {node.name}")
|
||||||
|
context.append(f" Beschreibung: {node.description if node.description else 'Keine Beschreibung verfügbar'}")
|
||||||
|
if node.category:
|
||||||
|
context.append(f" Kategorie: {node.category.name}")
|
||||||
|
context.append("")
|
||||||
|
|
||||||
|
return "\n".join(context) if context else ""
|
||||||
|
|
||||||
@app.route('/search')
|
@app.route('/search')
|
||||||
def search_thoughts_page():
|
def search_thoughts_page():
|
||||||
@@ -1179,3 +1433,50 @@ if __name__ == '__main__':
|
|||||||
# Make sure tables exist
|
# Make sure tables exist
|
||||||
db.create_all()
|
db.create_all()
|
||||||
app.run(host="0.0.0.0", debug=True)
|
app.run(host="0.0.0.0", debug=True)
|
||||||
|
|
||||||
|
@app.route('/api/refresh-mindmap')
|
||||||
|
def refresh_mindmap():
|
||||||
|
"""
|
||||||
|
API-Endpunkt zum Neuladen der Mindmap-Daten,
|
||||||
|
wenn die Datenbank-Verbindung vorübergehend fehlgeschlagen ist
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Stelle sicher, dass wir Kategorien haben
|
||||||
|
if Category.query.count() == 0:
|
||||||
|
create_default_categories()
|
||||||
|
|
||||||
|
# Hole alle Kategorien und Knoten
|
||||||
|
categories = Category.query.filter_by(parent_id=None).all()
|
||||||
|
category_tree = [build_category_tree(cat) for cat in categories]
|
||||||
|
|
||||||
|
# Hole alle Mindmap-Knoten
|
||||||
|
nodes = MindMapNode.query.all()
|
||||||
|
node_data = []
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
node_obj = {
|
||||||
|
'id': node.id,
|
||||||
|
'name': node.name,
|
||||||
|
'description': node.description or '',
|
||||||
|
'color_code': node.color_code or '#9F7AEA',
|
||||||
|
'thought_count': len(node.thoughts),
|
||||||
|
'category_id': node.category_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verbindungen hinzufügen
|
||||||
|
node_obj['connections'] = [{'target': child.id} for child in node.children]
|
||||||
|
|
||||||
|
node_data.append(node_obj)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'categories': category_tree,
|
||||||
|
'nodes': node_data
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Neuladen der Mindmap: {str(e)}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Datenbankverbindung konnte nicht hergestellt werden'
|
||||||
|
}), 500
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
@echo off
|
|
||||||
echo Copying network image to website/static/network-bg.jpg...
|
|
||||||
|
|
||||||
if not exist "website\static" (
|
|
||||||
echo Error: website/static directory does not exist.
|
|
||||||
echo Make sure you are running this script from the main project directory.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%~1"=="" (
|
|
||||||
echo Usage: copy-network-image.bat [path_to_image]
|
|
||||||
echo Example: copy-network-image.bat d2efd014-1325-471f-b9a7-90d025eb81d6.png
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
if not exist "%~1" (
|
|
||||||
echo Error: The specified image file "%~1" does not exist.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
copy /Y "%~1" "website\static\network-bg.jpg" > nul
|
|
||||||
|
|
||||||
if %errorlevel% equ 0 (
|
|
||||||
echo Success! The image has been copied to website/static/network-bg.jpg
|
|
||||||
echo Please restart the Flask server to see the changes.
|
|
||||||
) else (
|
|
||||||
echo Error: Failed to copy the image.
|
|
||||||
)
|
|
||||||
|
|
||||||
pause
|
|
||||||
Binary file not shown.
86
deploy.py
86
deploy.py
@@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Deploy the website on a server"""
|
|
||||||
print("Deploying the website on the server...")
|
|
||||||
|
|
||||||
# Get the directory where deploy.py is located (project root)
|
|
||||||
project_root = Path(__file__).resolve().parent
|
|
||||||
website_dir = project_root / "website"
|
|
||||||
|
|
||||||
# Check if virtual environment exists, create if not
|
|
||||||
venv_dir = project_root / "venv"
|
|
||||||
if not venv_dir.exists():
|
|
||||||
print("Creating virtual environment...")
|
|
||||||
subprocess.run([sys.executable, "-m", "venv", str(venv_dir)], check=True)
|
|
||||||
|
|
||||||
# Determine Python and pip paths based on OS
|
|
||||||
if os.name == 'nt': # Windows
|
|
||||||
python = venv_dir / "Scripts" / "python"
|
|
||||||
pip = venv_dir / "Scripts" / "pip"
|
|
||||||
else: # Unix-like
|
|
||||||
python = venv_dir / "bin" / "python"
|
|
||||||
pip = venv_dir / "bin" / "pip"
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
print("Installing dependencies...")
|
|
||||||
subprocess.run([str(pip), "install", "-r", str(website_dir / "requirements.txt")], check=True)
|
|
||||||
subprocess.run([str(pip), "install", "gunicorn"], check=True)
|
|
||||||
|
|
||||||
# Build CSS
|
|
||||||
print("Building CSS with Tailwind...")
|
|
||||||
subprocess.run([str(python), str(website_dir / "build_css.py")], check=True)
|
|
||||||
|
|
||||||
# Create a systemd service file
|
|
||||||
service_file = """[Unit]
|
|
||||||
Description=MindMap Wissensnetzwerk
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=www-data
|
|
||||||
WorkingDirectory={website_dir}
|
|
||||||
Environment="PATH={venv_bin}"
|
|
||||||
ExecStart={gunicorn} --workers 3 --bind 0.0.0.0:5000 --log-level info 'run:app'
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
""".format(
|
|
||||||
website_dir=website_dir,
|
|
||||||
venv_bin=venv_dir / "bin",
|
|
||||||
gunicorn=venv_dir / "bin" / "gunicorn"
|
|
||||||
)
|
|
||||||
|
|
||||||
service_path = project_root / "mindmap.service"
|
|
||||||
with open(service_path, 'w') as f:
|
|
||||||
f.write(service_file)
|
|
||||||
|
|
||||||
print(f"""
|
|
||||||
Deployment files created!
|
|
||||||
|
|
||||||
To install the service on a Linux server:
|
|
||||||
1. Copy the systemd service file:
|
|
||||||
sudo cp {service_path} /etc/systemd/system/
|
|
||||||
|
|
||||||
2. Reload systemd:
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
|
|
||||||
3. Enable and start the service:
|
|
||||||
sudo systemctl enable mindmap.service
|
|
||||||
sudo systemctl start mindmap.service
|
|
||||||
|
|
||||||
4. Check service status:
|
|
||||||
sudo systemctl status mindmap.service
|
|
||||||
|
|
||||||
Alternatively, you can run the application with gunicorn manually:
|
|
||||||
cd {website_dir}
|
|
||||||
{venv_dir}/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 'run:app'
|
|
||||||
""")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
60
deploy.sh
60
deploy.sh
@@ -1,60 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Farben für Ausgaben
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
echo -e "${GREEN}==== MindMap Projekt Server Deployment ====${NC}"
|
|
||||||
|
|
||||||
# Python-Umgebung erstellen
|
|
||||||
echo -e "${BLUE}Erstelle Python-Umgebung...${NC}"
|
|
||||||
python3 -m venv venv
|
|
||||||
source venv/bin/activate
|
|
||||||
|
|
||||||
# Python-Abhängigkeiten installieren
|
|
||||||
echo -e "${BLUE}Installiere Python-Abhängigkeiten...${NC}"
|
|
||||||
pip install -r website/requirements.txt
|
|
||||||
pip install gunicorn
|
|
||||||
|
|
||||||
# Tailwind CSS kompilieren
|
|
||||||
echo -e "${BLUE}Kompiliere Tailwind CSS...${NC}"
|
|
||||||
cd website
|
|
||||||
python build_css.py
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Datenbank initialisieren, falls noch nicht vorhanden
|
|
||||||
echo -e "${BLUE}Initialisiere die Datenbank, falls nötig...${NC}"
|
|
||||||
cd website
|
|
||||||
python init_db.py
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Systemd Service erstellen
|
|
||||||
echo -e "${BLUE}Erstelle Systemd Service...${NC}"
|
|
||||||
SERVICE_FILE="[Unit]
|
|
||||||
Description=MindMap Wissensnetzwerk
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=$(whoami)
|
|
||||||
WorkingDirectory=$(pwd)/website
|
|
||||||
Environment=\"PATH=$(pwd)/venv/bin\"
|
|
||||||
ExecStart=$(pwd)/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 --log-level info 'run:app'
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target"
|
|
||||||
|
|
||||||
echo "$SERVICE_FILE" > mindmap.service
|
|
||||||
|
|
||||||
echo -e "${GREEN}==== Deployment abgeschlossen ====${NC}"
|
|
||||||
echo -e "${BLUE}Um den Service zu installieren, führe folgende Befehle aus:${NC}"
|
|
||||||
echo -e "sudo cp mindmap.service /etc/systemd/system/"
|
|
||||||
echo -e "sudo systemctl daemon-reload"
|
|
||||||
echo -e "sudo systemctl enable mindmap.service"
|
|
||||||
echo -e "sudo systemctl start mindmap.service"
|
|
||||||
echo -e ""
|
|
||||||
echo -e "${BLUE}Alternativ kannst du den Server manuell starten:${NC}"
|
|
||||||
echo -e "cd website"
|
|
||||||
echo -e "../venv/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 'run:app'"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
version: "3.9"
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "5000:5000"
|
|
||||||
restart: always
|
|
||||||
@@ -5,7 +5,7 @@ email-validator
|
|||||||
python-dotenv
|
python-dotenv
|
||||||
werkzeug==2.2.3
|
werkzeug==2.2.3
|
||||||
flask-sqlalchemy==3.0.5
|
flask-sqlalchemy==3.0.5
|
||||||
openai==1.3.0
|
openai==1.15.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
flask-cors==4.0.0
|
flask-cors==4.0.0
|
||||||
gunicorn==21.2.0
|
gunicorn==21.2.0
|
||||||
|
|||||||
53
setup.py
53
setup.py
@@ -1,53 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Set up the project from the parent directory"""
|
|
||||||
print("Setting up the project...")
|
|
||||||
|
|
||||||
# Get the directory where setup.py is located (project root)
|
|
||||||
project_root = Path(__file__).resolve().parent
|
|
||||||
website_dir = project_root / "website"
|
|
||||||
|
|
||||||
# Check if virtual environment exists, create if not
|
|
||||||
venv_dir = project_root / "venv"
|
|
||||||
if not venv_dir.exists():
|
|
||||||
print("Creating virtual environment...")
|
|
||||||
subprocess.run([sys.executable, "-m", "venv", str(venv_dir)], check=True)
|
|
||||||
|
|
||||||
# Determine pip path based on OS
|
|
||||||
if os.name == 'nt': # Windows
|
|
||||||
pip = venv_dir / "Scripts" / "pip"
|
|
||||||
else: # Unix-like
|
|
||||||
pip = venv_dir / "bin" / "pip"
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
print("Installing dependencies...")
|
|
||||||
subprocess.run([str(pip), "install", "-r", str(website_dir / "requirements.txt")], check=True)
|
|
||||||
|
|
||||||
# Build CSS
|
|
||||||
print("Building CSS with Tailwind...")
|
|
||||||
if os.name == 'nt': # Windows
|
|
||||||
python = venv_dir / "Scripts" / "python"
|
|
||||||
else: # Unix-like
|
|
||||||
python = venv_dir / "bin" / "python"
|
|
||||||
|
|
||||||
subprocess.run([str(python), str(website_dir / "build_css.py")], check=True)
|
|
||||||
|
|
||||||
print("""
|
|
||||||
Setup completed successfully!
|
|
||||||
|
|
||||||
To run the development server:
|
|
||||||
1. Activate the virtual environment:
|
|
||||||
- Windows: .\\venv\\Scripts\\activate
|
|
||||||
- Unix/MacOS: source venv/bin/activate
|
|
||||||
2. Run the Flask application:
|
|
||||||
- cd website
|
|
||||||
- python run.py
|
|
||||||
""")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
27
static/css/all.min.css
vendored
Normal file
27
static/css/all.min.css
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Font Awesome 6.4.0
|
||||||
|
*
|
||||||
|
* This is a placeholder file. For production, you should:
|
||||||
|
* 1. Download Font Awesome from https://fontawesome.com/download
|
||||||
|
* 2. Extract the downloaded package
|
||||||
|
* 3. Copy the 'css/all.min.css' file to this location
|
||||||
|
* 4. Copy the 'webfonts' folder to '/static/webfonts/'
|
||||||
|
*
|
||||||
|
* Alternatively, you can install via npm and copy the files:
|
||||||
|
* npm install @fortawesome/fontawesome-free
|
||||||
|
* cp -r node_modules/@fortawesome/fontawesome-free/css/all.min.css static/css/
|
||||||
|
* cp -r node_modules/@fortawesome/fontawesome-free/webfonts/ static/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Placeholder styles for common Font Awesome icons */
|
||||||
|
.fa, .fas, .far, .fab {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning message */
|
||||||
|
body::before {
|
||||||
|
content: "Font Awesome CSS placeholder. Please replace with the actual file.";
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
203
static/css/assistant.css
Normal file
203
static/css/assistant.css
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/* ChatGPT Assistent Styles - Verbesserte Version */
|
||||||
|
#chatgpt-assistant {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assistant-chat {
|
||||||
|
transition: max-height 0.4s cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||||
|
opacity 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.25);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: calc(100vw - 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#assistant-toggle {
|
||||||
|
transition: transform 0.3s ease, background-color 0.2s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assistant-toggle:hover {
|
||||||
|
transform: scale(1.1) rotate(10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#assistant-history {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assistant-history::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assistant-history::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#assistant-history::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(156, 163, 175, 0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #assistant-history::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(156, 163, 175, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbesserte Message-Bubbles mit Schatten und Animation */
|
||||||
|
#assistant-history .flex > div {
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||||
|
animation: messageAppear 0.3s ease-out forwards;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes messageAppear {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verzögerte Animation für Messages */
|
||||||
|
#assistant-history .flex:nth-child(1) > div { animation-delay: 0.05s; }
|
||||||
|
#assistant-history .flex:nth-child(2) > div { animation-delay: 0.1s; }
|
||||||
|
#assistant-history .flex:nth-child(3) > div { animation-delay: 0.15s; }
|
||||||
|
#assistant-history .flex:nth-child(4) > div { animation-delay: 0.2s; }
|
||||||
|
#assistant-history .flex:nth-child(5) > div { animation-delay: 0.25s; }
|
||||||
|
|
||||||
|
/* Vorschläge styling */
|
||||||
|
#assistant-suggestions {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-pill {
|
||||||
|
animation: pillAppear 0.4s ease forwards;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pillAppear {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling für verschiedene Verzögerungen bei Vorschlägen */
|
||||||
|
#assistant-suggestions button:nth-child(1) { animation-delay: 0.1s; }
|
||||||
|
#assistant-suggestions button:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
#assistant-suggestions button:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
|
||||||
|
/* Mach Platz für Notifications, damit sie nicht mit dem Assistenten überlappen */
|
||||||
|
.notification-area {
|
||||||
|
bottom: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbesserte Glassmorphism-Effekt */
|
||||||
|
.glass-morphism {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .glass-morphism {
|
||||||
|
background: rgba(15, 23, 42, 0.35);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbesserte Farbpalette für Dark Theme */
|
||||||
|
.dark {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(10, 15, 25, var(--tw-bg-opacity)) !important;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .bg-dark-900 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(10, 15, 25, var(--tw-bg-opacity)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .bg-dark-800 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(15, 23, 42, var(--tw-bg-opacity)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .bg-dark-700 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(23, 33, 64, var(--tw-bg-opacity)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typing Indicator Animation Styles */
|
||||||
|
.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: bounce 1.4s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(1) { animation-delay: 0s; }
|
||||||
|
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 80%, 100% { transform: translateY(0); }
|
||||||
|
40% { transform: translateY(-8px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chat Input Fokus-Effekt */
|
||||||
|
#assistant-chat input:focus {
|
||||||
|
border-color: var(--primary-500, #3B82F6);
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #assistant-chat input:focus {
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbesserte Responsive Layouts */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
#assistant-chat {
|
||||||
|
width: calc(100vw - 2rem) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chatgpt-assistant {
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer immer unten */
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
16
static/css/tailwind.min.css
vendored
Normal file
16
static/css/tailwind.min.css
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Tailwind CSS v3.4.16
|
||||||
|
*
|
||||||
|
* This is a placeholder file. For production, you should:
|
||||||
|
* 1. Install Tailwind CSS as a PostCSS plugin: https://tailwindcss.com/docs/installation
|
||||||
|
* 2. Run the Tailwind CLI to compile this file with your custom configuration
|
||||||
|
* 3. Replace this file with the compiled CSS
|
||||||
|
*
|
||||||
|
* The actual file should be generated using:
|
||||||
|
* npx tailwindcss -i ./src/input.css -o ./static/css/tailwind.min.css --minify
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Base Tailwind imports */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
@@ -450,6 +450,76 @@ class D3Extensions {
|
|||||||
// Pulsanimation starten
|
// Pulsanimation starten
|
||||||
pulse();
|
pulse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verarbeitet Daten aus der Datenbank für die Mindmap-Visualisierung
|
||||||
|
* @param {Array} databaseNodes - Knotendaten aus der Datenbank
|
||||||
|
* @param {Array} links - Verbindungsdaten oder null für automatische Extraktion
|
||||||
|
* @returns {Object} Aufbereitete Daten für D3.js
|
||||||
|
*/
|
||||||
|
static processDbNodesForVisualization(databaseNodes, links = null) {
|
||||||
|
// Überprüfe, ob Daten vorhanden sind
|
||||||
|
if (!databaseNodes || databaseNodes.length === 0) {
|
||||||
|
console.warn('Keine Knotendaten zum Verarbeiten vorhanden');
|
||||||
|
return { nodes: [], links: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Knoten mit D3-Kompatiblem Format erstellen
|
||||||
|
const nodes = databaseNodes.map(node => {
|
||||||
|
// Farbgenerierung, falls keine vorhanden
|
||||||
|
const nodeColor = node.color_code ||
|
||||||
|
node.color ||
|
||||||
|
D3Extensions.stringToColor(node.name || 'default');
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
name: node.name,
|
||||||
|
description: node.description || '',
|
||||||
|
thought_count: node.thought_count || 0,
|
||||||
|
color: nodeColor,
|
||||||
|
// Zusätzliche Attribute
|
||||||
|
category_id: node.category_id,
|
||||||
|
is_public: node.is_public !== undefined ? node.is_public : true,
|
||||||
|
// Position, falls vorhanden
|
||||||
|
x: node.x_position,
|
||||||
|
y: node.y_position,
|
||||||
|
// Größe, falls vorhanden
|
||||||
|
scale: node.scale || 1.0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verbindungen verarbeiten
|
||||||
|
let processedLinks = [];
|
||||||
|
|
||||||
|
if (links && Array.isArray(links)) {
|
||||||
|
// Verwende übergebene Verbindungen
|
||||||
|
processedLinks = links.map(link => {
|
||||||
|
return {
|
||||||
|
source: link.source,
|
||||||
|
target: link.target,
|
||||||
|
// Zusätzliche Attribute
|
||||||
|
type: link.type || 'default',
|
||||||
|
strength: link.strength || 1
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Extrahiere Verbindungen aus den Knoten
|
||||||
|
databaseNodes.forEach(node => {
|
||||||
|
if (node.connections && Array.isArray(node.connections)) {
|
||||||
|
node.connections.forEach(conn => {
|
||||||
|
processedLinks.push({
|
||||||
|
source: node.id,
|
||||||
|
target: conn.target,
|
||||||
|
type: conn.type || 'default',
|
||||||
|
strength: conn.strength || 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { nodes, links: processedLinks };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Globale Verfügbarkeit sicherstellen
|
// Globale Verfügbarkeit sicherstellen
|
||||||
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
35
static/fonts/inter.css
Normal file
35
static/fonts/inter.css
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/* Inter font - Local version */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: url('../fonts/inter-light.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/inter-regular.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url('../fonts/inter-medium.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url('../fonts/inter-semibold.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url('../fonts/inter-bold.woff2') format('woff2');
|
||||||
|
}
|
||||||
21
static/fonts/jetbrains-mono.css
Normal file
21
static/fonts/jetbrains-mono.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/* JetBrains Mono font - Local version */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('../fonts/jetbrainsmono-regular.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url('../fonts/jetbrainsmono-medium.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url('../fonts/jetbrainsmono-bold.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
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.');
|
||||||
@@ -227,3 +227,6 @@ const MindMap = {
|
|||||||
|
|
||||||
// Globale Export für andere Module
|
// Globale Export für andere Module
|
||||||
window.MindMap = MindMap;
|
window.MindMap = MindMap;
|
||||||
|
|
||||||
|
// Export als Modul
|
||||||
|
export default MindMap;
|
||||||
572
static/js/modules/chatgpt-assistant.js
Normal file
572
static/js/modules/chatgpt-assistant.js
Normal file
@@ -0,0 +1,572 @@
|
|||||||
|
/**
|
||||||
|
* ChatGPT Assistent Modul
|
||||||
|
* Verwaltet die Interaktion mit der OpenAI API und die Benutzeroberfläche des Assistenten
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ChatGPTAssistant {
|
||||||
|
constructor() {
|
||||||
|
this.messages = [];
|
||||||
|
this.isOpen = false;
|
||||||
|
this.isLoading = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialisiert den Assistenten und fügt die UI zum DOM hinzu
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
// Assistent-Container erstellen
|
||||||
|
this.createAssistantUI();
|
||||||
|
|
||||||
|
// Event-Listener hinzufügen
|
||||||
|
this.setupEventListeners();
|
||||||
|
|
||||||
|
// Ersten Willkommensnachricht anzeigen
|
||||||
|
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!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt die UI-Elemente für den Assistenten
|
||||||
|
*/
|
||||||
|
createAssistantUI() {
|
||||||
|
// Hauptcontainer erstellen
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
this.container.id = 'chatgpt-assistant';
|
||||||
|
this.container.className = 'fixed bottom-4 right-4 z-50 flex flex-col';
|
||||||
|
|
||||||
|
// Button zum Öffnen/Schließen des Assistenten
|
||||||
|
const toggleButton = document.createElement('button');
|
||||||
|
toggleButton.id = 'assistant-toggle';
|
||||||
|
toggleButton.className = 'ml-auto bg-primary-600 hover:bg-primary-700 text-white rounded-full p-3 shadow-lg transition-all duration-300 mb-2';
|
||||||
|
toggleButton.innerHTML = '<i class="fas fa-robot text-xl"></i>';
|
||||||
|
|
||||||
|
// 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 md:w-96 max-h-0 opacity-0';
|
||||||
|
|
||||||
|
// Chat-Header
|
||||||
|
const header = document.createElement('div');
|
||||||
|
header.className = 'bg-primary-600 text-white p-3 flex items-center justify-between';
|
||||||
|
header.innerHTML = `
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="fas fa-robot mr-2"></i>
|
||||||
|
<span>KI-Assistent (4o-mini)</span>
|
||||||
|
</div>
|
||||||
|
<button id="assistant-close" class="text-white hover:text-gray-200">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Chat-Verlauf
|
||||||
|
this.chatHistory = document.createElement('div');
|
||||||
|
this.chatHistory.id = 'assistant-history';
|
||||||
|
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');
|
||||||
|
inputContainer.className = 'border-t border-gray-200 dark:border-dark-600 p-3 flex items-center';
|
||||||
|
|
||||||
|
this.inputField = document.createElement('input');
|
||||||
|
this.inputField.type = 'text';
|
||||||
|
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');
|
||||||
|
sendButton.id = 'assistant-send';
|
||||||
|
sendButton.className = 'bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded-r-lg';
|
||||||
|
sendButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
||||||
|
|
||||||
|
// Elemente zusammenfügen
|
||||||
|
inputContainer.appendChild(this.inputField);
|
||||||
|
inputContainer.appendChild(sendButton);
|
||||||
|
|
||||||
|
chatContainer.appendChild(header);
|
||||||
|
chatContainer.appendChild(this.chatHistory);
|
||||||
|
chatContainer.appendChild(this.suggestionArea);
|
||||||
|
chatContainer.appendChild(inputContainer);
|
||||||
|
|
||||||
|
this.container.appendChild(toggleButton);
|
||||||
|
this.container.appendChild(chatContainer);
|
||||||
|
|
||||||
|
// Zum DOM hinzufügen
|
||||||
|
document.body.appendChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Richtet Event-Listener für die Benutzeroberfläche ein
|
||||||
|
*/
|
||||||
|
setupEventListeners() {
|
||||||
|
// Toggle-Button
|
||||||
|
const toggleButton = document.getElementById('assistant-toggle');
|
||||||
|
if (toggleButton) {
|
||||||
|
toggleButton.addEventListener('click', () => this.toggleAssistant());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schließen-Button
|
||||||
|
const closeButton = document.getElementById('assistant-close');
|
||||||
|
if (closeButton) {
|
||||||
|
closeButton.addEventListener('click', () => this.toggleAssistant(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Senden-Button
|
||||||
|
const sendButton = document.getElementById('assistant-send');
|
||||||
|
if (sendButton) {
|
||||||
|
sendButton.addEventListener('click', () => this.sendMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter-Taste im Eingabefeld
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Öffnet oder schließt den Assistenten
|
||||||
|
* @param {boolean} state - Optional: erzwingt einen bestimmten Zustand
|
||||||
|
*/
|
||||||
|
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-[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-[32rem]', 'opacity-100');
|
||||||
|
chatContainer.classList.add('max-h-0', 'opacity-0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt eine Nachricht zum Chat-Verlauf hinzu
|
||||||
|
* @param {string} sender - 'user' oder 'assistant'
|
||||||
|
* @param {string} text - Nachrichtentext
|
||||||
|
*/
|
||||||
|
addMessage(sender, text) {
|
||||||
|
// Nachricht zum Verlauf hinzufügen
|
||||||
|
this.messages.push({ role: sender, content: text });
|
||||||
|
|
||||||
|
// DOM-Element erstellen
|
||||||
|
const messageEl = document.createElement('div');
|
||||||
|
messageEl.className = `flex ${sender === 'user' ? 'justify-end' : 'justify-start'}`;
|
||||||
|
|
||||||
|
const bubble = document.createElement('div');
|
||||||
|
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%]';
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Eingabefeld zurücksetzen
|
||||||
|
this.inputField.value = '';
|
||||||
|
|
||||||
|
// Ladeindikator anzeigen
|
||||||
|
this.isLoading = true;
|
||||||
|
this.showLoadingIndicator();
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Sende Anfrage an KI-Assistent API...');
|
||||||
|
// Anfrage an den Server senden
|
||||||
|
const response = await fetch('/api/assistant', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
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(`Serverfehler: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Antwort erhalten:', data);
|
||||||
|
|
||||||
|
// Antwort anzeigen
|
||||||
|
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, falls noch vorhanden
|
||||||
|
this.removeLoadingIndicator();
|
||||||
|
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
const bubble = document.createElement('div');
|
||||||
|
bubble.className = 'bg-gray-100 dark:bg-dark-700 text-gray-800 dark:text-white rounded-lg py-2 px-3';
|
||||||
|
bubble.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
||||||
|
|
||||||
|
loadingEl.appendChild(bubble);
|
||||||
|
this.chatHistory.appendChild(loadingEl);
|
||||||
|
|
||||||
|
// Scroll zum Ende des Verlaufs
|
||||||
|
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt den Ladeindikator aus dem Chat
|
||||||
|
*/
|
||||||
|
removeLoadingIndicator() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exportiere die Klasse für die Verwendung in anderen Modulen
|
||||||
|
export default ChatGPTAssistant;
|
||||||
@@ -19,6 +19,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
* Initialisiert die Mindmap-Seite
|
* Initialisiert die Mindmap-Seite
|
||||||
*/
|
*/
|
||||||
function initMindmapPage() {
|
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 mindmapContainer = document.getElementById('mindmap-container');
|
||||||
const thoughtsContainer = document.getElementById('thoughts-container');
|
const thoughtsContainer = document.getElementById('thoughts-container');
|
||||||
|
|
||||||
@@ -26,6 +30,7 @@ function initMindmapPage() {
|
|||||||
console.error('Mindmap-Container nicht gefunden!');
|
console.error('Mindmap-Container nicht gefunden!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('Mindmap-Container gefunden:', mindmapContainer);
|
||||||
|
|
||||||
// Prüfe, ob D3.js geladen ist
|
// Prüfe, ob D3.js geladen ist
|
||||||
if (typeof d3 === 'undefined') {
|
if (typeof d3 === 'undefined') {
|
||||||
@@ -41,17 +46,47 @@ function initMindmapPage() {
|
|||||||
return;
|
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
|
// Erstelle die Mindmap-Visualisierung
|
||||||
const mindmap = new MindMapVisualization('#mindmap-container', {
|
try {
|
||||||
height: 600,
|
console.log('Versuche, MindMapVisualization zu erstellen...');
|
||||||
onNodeClick: handleNodeClick
|
const mindmap = new MindMapVisualization('#mindmap-container', {
|
||||||
});
|
height: 600,
|
||||||
|
onNodeClick: handleNodeClick
|
||||||
|
});
|
||||||
|
|
||||||
// Globale Referenz für die Zoom-Buttons erstellen
|
// Globale Referenz für die Zoom-Buttons erstellen
|
||||||
window.mindmapInstance = mindmap;
|
window.mindmapInstance = mindmap;
|
||||||
|
|
||||||
// Lade die Mindmap-Daten
|
// Lade die Mindmap-Daten
|
||||||
mindmap.loadData();
|
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
|
// Suchfunktion für die Mindmap
|
||||||
const searchInput = document.getElementById('mindmap-search');
|
const searchInput = document.getElementById('mindmap-search');
|
||||||
@@ -163,7 +163,7 @@ class MindMapVisualization {
|
|||||||
// API-Aufruf mit kürzerem Timeout im Hintergrund durchführen
|
// API-Aufruf mit kürzerem Timeout im Hintergrund durchführen
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
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', {
|
const response = await fetch('/api/mindmap', {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
@@ -175,8 +175,55 @@ class MindMapVisualization {
|
|||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.warn(`HTTP Fehler: ${response.status}, verwende Standarddaten`);
|
console.warn(`HTTP Fehler: ${response.status}, versuche erneute Verbindung`);
|
||||||
return; // Keep using default data
|
|
||||||
|
// 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();
|
const data = await response.json();
|
||||||
@@ -66,18 +66,18 @@ class NeuralNetworkBackground {
|
|||||||
flowColor: '#c4b5fd'
|
flowColor: '#c4b5fd'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Config - Updated to be more flowing and subtle
|
// Config - Updated to be more subtle, efficient, and elegant
|
||||||
this.config = {
|
this.config = {
|
||||||
nodeCount: 100, // Slightly fewer nodes for cleaner look
|
nodeCount: 70, // Reduced node count for better performance
|
||||||
nodeSize: 0.8, // Smaller nodes
|
nodeSize: 0.6, // Smaller nodes for subtlety
|
||||||
nodeVariation: 0.5, // Less variation for uniformity
|
nodeVariation: 0.3, // Less variation for uniformity
|
||||||
connectionDistance: 200, // Longer connections for better flow
|
connectionDistance: 150, // Shorter connections for cleaner look
|
||||||
connectionOpacity: 0.2, // More subtle connections
|
connectionOpacity: 0.15, // More subtle connections
|
||||||
animationSpeed: 0.08, // Much slower movement
|
animationSpeed: 0.04, // Slower, more elegant movement
|
||||||
pulseSpeed: 0.004, // Slower pulse
|
pulseSpeed: 0.002, // Very slow pulse for subtlety
|
||||||
flowSpeed: 0.6, // Speed of flow animations
|
flowSpeed: 0.4, // Slower flow animations
|
||||||
flowDensity: 0.001, // How often new flows start (lower = less frequent)
|
flowDensity: 0.0008, // Less frequent flow animations
|
||||||
flowLength: 0.2 // Length of the flow (percentage of the connection)
|
flowLength: 0.15 // Shorter flow animations
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
@@ -713,7 +713,7 @@ class NeuralNetworkBackground {
|
|||||||
};
|
};
|
||||||
p2 = {
|
p2 = {
|
||||||
x: fromNode.x + (toNode.x - fromNode.x) * endProgress,
|
x: fromNode.x + (toNode.x - fromNode.x) * endProgress,
|
||||||
y: fromNode.y + (toNode.y - fromNode.y) * endProgress
|
y: fromNode.y + (toNode.y - fromNode.y) * startProgress
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
p1 = {
|
p1 = {
|
||||||
6
static/three.min.js
vendored
Normal file
6
static/three.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -14,9 +14,9 @@
|
|||||||
<meta name="keywords" content="systades, wissen, visualisierung, lernen, gedanken, theorie">
|
<meta name="keywords" content="systades, wissen, visualisierung, lernen, gedanken, theorie">
|
||||||
<meta name="author" content="Systades-Team">
|
<meta name="author" content="Systades-Team">
|
||||||
|
|
||||||
<!-- Tailwind CSS über CDN -->
|
<!-- Tailwind CSS - CDN Version -->
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com" nonce="{{ csp_nonce }}"></script>
|
||||||
<script>
|
<script nonce="{{ csp_nonce }}">
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme: {
|
theme: {
|
||||||
@@ -63,13 +63,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Fonts -->
|
<!-- Local Font Files -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link href="{{ url_for('static', filename='fonts/inter.css') }}" rel="stylesheet">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link href="{{ url_for('static', filename='fonts/jetbrains-mono.css') }}" rel="stylesheet">
|
||||||
<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">
|
|
||||||
|
|
||||||
<!-- Icons -->
|
<!-- Icons - Self-hosted Font Awesome -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link href="{{ url_for('static', filename='css/all.min.css') }}" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Assistent CSS -->
|
<!-- Assistent CSS -->
|
||||||
<link href="{{ url_for('static', filename='css/assistant.css') }}" rel="stylesheet">
|
<link href="{{ url_for('static', filename='css/assistant.css') }}" rel="stylesheet">
|
||||||
@@ -80,23 +79,23 @@
|
|||||||
<!-- Base-Styles ausgelagert in eigene Datei -->
|
<!-- Base-Styles ausgelagert in eigene Datei -->
|
||||||
<link href="{{ url_for('static', filename='css/base-styles.css') }}" rel="stylesheet">
|
<link href="{{ url_for('static', filename='css/base-styles.css') }}" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Alpine.js -->
|
<!-- Alpine.js - Self-hosted -->
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js"></script>
|
<script src="{{ url_for('static', filename='js/alpine.min.js') }}" defer nonce="{{ csp_nonce }}"></script>
|
||||||
|
|
||||||
<!-- Neural Network Background CSS -->
|
<!-- Neural Network Background CSS -->
|
||||||
<link href="{{ url_for('static', filename='css/neural-network-background.css') }}" rel="stylesheet">
|
<link href="{{ url_for('static', filename='css/neural-network-background.css') }}" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Neural Network Background Script -->
|
<!-- 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) -->
|
<!-- Hauptmodul laden (als ES6 Modul) -->
|
||||||
<script type="module">
|
<script type="module" nonce="{{ csp_nonce }}">
|
||||||
import MindMap from "{{ url_for('static', filename='js/main.js') }}";
|
import MindMap from "{{ url_for('static', filename='js/main.js') }}";
|
||||||
// Alpine.js-Integration
|
// Alpine.js-Integration
|
||||||
document.addEventListener('alpine:init', () => {
|
document.addEventListener('alpine:init', () => {
|
||||||
Alpine.data('layout', () => ({
|
Alpine.data('layout', () => ({
|
||||||
darkMode: true, // Default to dark mode
|
darkMode: true, // Default to dark mode
|
||||||
mobileMenuOpen: false,
|
mobileMenuOpen: false, // Mobile Menü standardmäßig geschlossen
|
||||||
userMenuOpen: false,
|
userMenuOpen: false,
|
||||||
showSettingsModal: 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)
|
// MindMap global verfügbar machen (für Alpine.js und andere nicht-Module Skripte)
|
||||||
window.MindMap = MindMap;
|
window.MindMap = MindMap;
|
||||||
</script>
|
</script>
|
||||||
@@ -214,11 +221,31 @@
|
|||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
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>
|
</style>
|
||||||
</head>
|
</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 -->
|
<!-- 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 -->
|
<!-- Hauptnavigation -->
|
||||||
<nav class="sticky top-0 left-0 right-0 z-50 transition-all duration-300 py-4 px-5 border-b glass-morphism"
|
<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'">
|
x-bind:class="darkMode ? 'glass-navbar-dark border-white/10' : 'glass-navbar-light border-gray-200/50'">
|
||||||
@@ -389,6 +416,7 @@
|
|||||||
|
|
||||||
<!-- Mobile Menü -->
|
<!-- Mobile Menü -->
|
||||||
<div x-show="mobileMenuOpen"
|
<div x-show="mobileMenuOpen"
|
||||||
|
x-cloak
|
||||||
x-transition:enter="transition ease-out duration-200"
|
x-transition:enter="transition ease-out duration-200"
|
||||||
x-transition:enter-start="opacity-0 -translate-y-4"
|
x-transition:enter-start="opacity-0 -translate-y-4"
|
||||||
x-transition:enter-end="opacity-100 translate-y-0"
|
x-transition:enter-end="opacity-100 translate-y-0"
|
||||||
@@ -569,7 +597,7 @@
|
|||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
|
|
||||||
<!-- KI-Chat Initialisierung -->
|
<!-- KI-Chat Initialisierung -->
|
||||||
<script type="module">
|
<script type="module" nonce="{{ csp_nonce }}">
|
||||||
// Importiere und initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
|
// Importiere und initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
|
||||||
// dass er auf jeder Seite verfügbar ist, selbst wenn MindMap nicht geladen ist
|
// 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') }}";
|
import ChatGPTAssistant from "{{ url_for('static', filename='js/modules/chatgpt-assistant.js') }}";
|
||||||
33
templates/errors/403.html
Normal file
33
templates/errors/403.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}403 - Zugriff verweigert{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<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">
|
||||||
|
<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 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 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 %}
|
||||||
33
templates/errors/404.html
Normal file
33
templates/errors/404.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}404 - Seite nicht gefunden{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<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">
|
||||||
|
<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 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 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 %}
|
||||||
33
templates/errors/429.html
Normal file
33
templates/errors/429.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}429 - Zu viele Anfragen{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<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">
|
||||||
|
<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 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 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 %}
|
||||||
33
templates/errors/500.html
Normal file
33
templates/errors/500.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}500 - Serverfehler{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<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">
|
||||||
|
<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 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 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 %}
|
||||||
@@ -253,85 +253,99 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chat Interface Preview -->
|
<!-- Chat Interface Preview -->
|
||||||
<div class="max-w-3xl mx-auto embedded-chat">
|
<div class="max-w-3xl mx-auto">
|
||||||
<!-- Chat Header -->
|
<div class="embedded-chat" id="demo-chat">
|
||||||
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
<!-- Chat Header -->
|
||||||
<div class="flex items-center">
|
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||||
<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">
|
<div class="flex items-center">
|
||||||
<i class="fa-solid fa-robot text-sm"></i>
|
<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>
|
</div>
|
||||||
<span class="font-medium text-gray-800 dark:text-gray-200">Systades Assistent</span>
|
<div>
|
||||||
</div>
|
<button id="open-real-assistant" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||||
<div>
|
<i class="fa-solid fa-expand"></i>
|
||||||
<button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
</button>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- User Message -->
|
<!-- Chat Messages -->
|
||||||
<div class="mb-4 flex justify-end">
|
<div id="embedded-chat-messages" class="border-b border-gray-200 dark:border-gray-700">
|
||||||
<div class="bg-purple-100 dark:bg-purple-900/30 rounded-lg p-3 max-w-[80%]">
|
<!-- Assistant Message -->
|
||||||
<p class="text-gray-800 dark:text-gray-200">
|
<div class="mb-4 flex">
|
||||||
Kann ich mit deiner Hilfe eine Mindmap zum Thema Künstliche Intelligenz erstellen?
|
<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">
|
||||||
</p>
|
<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>
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Assistant Typing -->
|
<!-- User Message -->
|
||||||
<div class="flex items-center">
|
<div class="mb-4 flex justify-end">
|
||||||
<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">
|
<div class="bg-purple-100 dark:bg-purple-900/30 rounded-lg p-3 max-w-[80%]">
|
||||||
<i class="fa-solid fa-robot text-sm"></i>
|
<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>
|
||||||
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3">
|
|
||||||
<div class="typing-dots">
|
<!-- Assistant Response -->
|
||||||
<span></span>
|
<div class="mb-4 flex" id="demo-ai-response">
|
||||||
<span></span>
|
<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">
|
||||||
<span></span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Chat Input -->
|
<!-- Chat Input -->
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input type="text" placeholder="Stelle eine Frage..." class="mystical-input flex-grow" disabled>
|
<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>
|
<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>
|
<i class="fa-solid fa-paper-plane"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Quick Queries -->
|
<!-- Quick Queries -->
|
||||||
<div class="mt-3 flex flex-wrap gap-2">
|
<div class="mt-3 flex flex-wrap gap-2">
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400 mr-1">Beispiele:</span>
|
<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 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 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 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 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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Try it Button -->
|
<!-- Try it Button -->
|
||||||
<div class="text-center mt-10">
|
<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">
|
class="mystical-button mystical-button-primary inline-flex items-center">
|
||||||
<i class="fa-solid fa-robot mr-2"></i>
|
<i class="fa-solid fa-robot mr-2"></i>
|
||||||
KI-Assistenten ausprobieren
|
KI-Assistenten ausprobieren
|
||||||
@@ -432,33 +446,75 @@
|
|||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
// Simulate assistant typing and response
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
setTimeout(() => {
|
// Expand-Button mit dem echten Assistenten verknüpfen
|
||||||
const chatMessages = document.getElementById('embedded-chat-messages');
|
const openRealAssistantBtn = document.getElementById('open-real-assistant');
|
||||||
const typingIndicator = chatMessages.querySelector('.flex:last-child');
|
if (openRealAssistantBtn) {
|
||||||
|
openRealAssistantBtn.addEventListener('click', function() {
|
||||||
if (typingIndicator) {
|
if (window.MindMap && window.MindMap.assistant) {
|
||||||
// Create assistant response
|
window.MindMap.assistant.toggleAssistant(true);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}, 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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
852
templates/mindmap.html
Normal file
852
templates/mindmap.html
Normal file
@@ -0,0 +1,852 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Mindmap{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
/* Full page background */
|
||||||
|
html, body {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mindmap Container */
|
||||||
|
#mindmap-container {
|
||||||
|
position: relative;
|
||||||
|
min-height: calc(100vh - 160px);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Control Panel */
|
||||||
|
.control-panel {
|
||||||
|
position: fixed;
|
||||||
|
top: 100px;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 10;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .control-panel {
|
||||||
|
background-color: rgba(17, 24, 39, 0.8);
|
||||||
|
border-color: rgba(109, 40, 217, 0.3);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-panel {
|
||||||
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
|
border-color: rgba(139, 92, 246, 0.2);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Control Panel Toggle */
|
||||||
|
.panel-toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
z-index: 2;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .panel-toggle {
|
||||||
|
background-color: rgba(109, 40, 217, 0.2);
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-toggle {
|
||||||
|
background-color: rgba(139, 92, 246, 0.1);
|
||||||
|
color: rgba(30, 41, 59, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-toggle:hover {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category Tree */
|
||||||
|
.category-tree {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .category-item:hover {
|
||||||
|
background-color: rgba(109, 40, 217, 0.1);
|
||||||
|
border-left-color: rgba(139, 92, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item:hover {
|
||||||
|
background-color: rgba(139, 92, 246, 0.05);
|
||||||
|
border-left-color: rgba(139, 92, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Node List */
|
||||||
|
.node-list {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-item {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .node-item {
|
||||||
|
background-color: rgba(31, 41, 55, 0.5);
|
||||||
|
border: 1px solid rgba(55, 65, 81, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-item {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .node-item:hover {
|
||||||
|
background-color: rgba(55, 65, 81, 0.7);
|
||||||
|
border-color: rgba(139, 92, 246, 0.5);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-item:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-color: rgba(139, 92, 246, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Node Counter Badge */
|
||||||
|
.node-counter {
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .node-counter {
|
||||||
|
background-color: rgba(109, 40, 217, 0.3);
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-counter {
|
||||||
|
background-color: rgba(139, 92, 246, 0.1);
|
||||||
|
color: rgba(109, 40, 217, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Canvas area */
|
||||||
|
#mindmap-canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip */
|
||||||
|
.tooltip-container {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .tooltip-container {
|
||||||
|
background-color: rgba(17, 24, 39, 0.9);
|
||||||
|
border: 1px solid rgba(109, 40, 217, 0.3);
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-container {
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||||
|
color: rgba(30, 41, 59, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search input */
|
||||||
|
.search-input {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .search-input {
|
||||||
|
background-color: rgba(31, 41, 55, 0.7);
|
||||||
|
border: 1px solid rgba(55, 65, 81, 0.5);
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||||
|
color: rgba(30, 41, 59, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .search-input:focus {
|
||||||
|
border-color: rgba(139, 92, 246, 0.5);
|
||||||
|
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
border-color: rgba(139, 92, 246, 0.3);
|
||||||
|
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode toggle */
|
||||||
|
.mode-toggle {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mode-toggle {
|
||||||
|
background-color: rgba(31, 41, 55, 0.5);
|
||||||
|
border: 1px solid rgba(55, 65, 81, 0.5);
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-toggle {
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.8);
|
||||||
|
color: rgba(30, 41, 59, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .mode-toggle.active {
|
||||||
|
background-color: rgba(109, 40, 217, 0.2);
|
||||||
|
border-color: rgba(139, 92, 246, 0.4);
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mode-toggle.active {
|
||||||
|
background-color: rgba(139, 92, 246, 0.1);
|
||||||
|
border-color: rgba(139, 92, 246, 0.3);
|
||||||
|
color: rgba(109, 40, 217, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User Mindmaps */
|
||||||
|
.user-mindmap-section {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 10;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .user-mindmap-section {
|
||||||
|
background-color: rgba(17, 24, 39, 0.85);
|
||||||
|
border: 1px solid rgba(109, 40, 217, 0.3);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-mindmap-section {
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid rgba(139, 92, 246, 0.2);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User Mindmap List */
|
||||||
|
.user-mindmap-item {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .user-mindmap-item {
|
||||||
|
background-color: rgba(31, 41, 55, 0.5);
|
||||||
|
border: 1px solid rgba(55, 65, 81, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-mindmap-item {
|
||||||
|
background-color: rgba(255, 255, 255, 0.7);
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .user-mindmap-item:hover {
|
||||||
|
background-color: rgba(55, 65, 81, 0.7);
|
||||||
|
border-color: rgba(139, 92, 246, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-mindmap-item:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-color: rgba(139, 92, 246, 0.3);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zoom Controls */
|
||||||
|
.zoom-controls {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .zoom-controls {
|
||||||
|
background-color: rgba(17, 24, 39, 0.7);
|
||||||
|
border: 1px solid rgba(55, 65, 81, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-controls {
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .zoom-btn {
|
||||||
|
background-color: rgba(31, 41, 55, 0.7);
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn {
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
color: rgba(30, 41, 59, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .zoom-btn:hover {
|
||||||
|
background-color: rgba(139, 92, 246, 0.3);
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn:hover {
|
||||||
|
background-color: rgba(139, 92, 246, 0.1);
|
||||||
|
color: rgba(109, 40, 217, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading spinner */
|
||||||
|
.spinner {
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 3px solid;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .spinner {
|
||||||
|
border-top-color: rgba(139, 92, 246, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border-top-color: rgba(109, 40, 217, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Mindmap Container -->
|
||||||
|
<div id="mindmap-container">
|
||||||
|
<!-- Main Canvas -->
|
||||||
|
<div id="mindmap-canvas"></div>
|
||||||
|
|
||||||
|
<!-- Control Panel -->
|
||||||
|
<div class="control-panel p-4 w-64" id="control-panel" x-data="{ isExpanded: true }">
|
||||||
|
<div class="panel-toggle" @click="isExpanded = !isExpanded">
|
||||||
|
<i class="fa-solid" :class="isExpanded ? 'fa-chevron-left' : 'fa-chevron-right'"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="isExpanded">
|
||||||
|
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Wissensbereiche</h2>
|
||||||
|
|
||||||
|
<!-- Search Box -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<input type="text" id="category-search" class="search-input" placeholder="Bereich suchen...">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category Tree -->
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- View Mode Toggle -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">Ansicht</h3>
|
||||||
|
<div class="flex justify-between gap-2">
|
||||||
|
<button id="view-all" class="mode-toggle text-sm flex-1 active">
|
||||||
|
<i class="fa-solid fa-diagram-project mr-1"></i> Alles
|
||||||
|
</button>
|
||||||
|
<button id="view-focused" class="mode-toggle text-sm flex-1">
|
||||||
|
<i class="fa-solid fa-bullseye mr-1"></i> Fokus
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User Mindmaps Section -->
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<div class="user-mindmap-section p-4 w-64" x-data="{ isExpanded: true }">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Meine Mindmaps</h2>
|
||||||
|
<button @click="isExpanded = !isExpanded" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
|
||||||
|
<i class="fa-solid" :class="isExpanded ? 'fa-chevron-down' : 'fa-chevron-up'"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-show="isExpanded">
|
||||||
|
<!-- 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 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 -->
|
||||||
|
<a href="{{ url_for('create_mindmap') }}" class="mystical-button mystical-button-primary w-full text-center text-sm">
|
||||||
|
<i class="fa-solid fa-plus mr-1"></i> Neue Mindmap erstellen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Zoom Controls -->
|
||||||
|
<div class="zoom-controls">
|
||||||
|
<button id="zoom-in" class="zoom-btn">
|
||||||
|
<i class="fa-solid fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button id="zoom-out" class="zoom-btn">
|
||||||
|
<i class="fa-solid fa-minus"></i>
|
||||||
|
</button>
|
||||||
|
<button id="reset-view" class="zoom-btn">
|
||||||
|
<i class="fa-solid fa-home"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Node Tooltip -->
|
||||||
|
<div id="node-tooltip" class="tooltip-container rounded-lg p-4 shadow-lg"></div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<!-- D3.js for Mindmap Visualization -->
|
||||||
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom D3 Extensions -->
|
||||||
|
<script src="{{ url_for('static', filename='d3-extensions.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');
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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>
|
||||||
|
<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>
|
||||||
|
`;
|
||||||
|
mindmapsList.appendChild(item);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-avatar {
|
.avatar-container {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -50,40 +50,48 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-avatar:hover {
|
.avatar-container:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
border: 3px solid rgba(179, 143, 255, 0.5);
|
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);
|
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%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
transition: filter 0.3s ease;
|
transition: filter 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-avatar:hover img {
|
.avatar-container:hover img {
|
||||||
filter: brightness(1.1);
|
filter: brightness(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-avatar-placeholder {
|
.avatar-edit {
|
||||||
font-size: 5rem;
|
position: absolute;
|
||||||
color: rgba(255, 255, 255, 0.6);
|
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;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-avatar:hover .profile-avatar-placeholder {
|
.avatar-edit:hover {
|
||||||
color: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-info {
|
.user-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-name {
|
.user-info h1 {
|
||||||
font-size: 2.75rem;
|
font-size: 2.75rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
@@ -96,33 +104,7 @@
|
|||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-username {
|
.user-bio {
|
||||||
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 {
|
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
@@ -131,7 +113,7 @@
|
|||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-meta {
|
.user-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
@@ -140,118 +122,22 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-meta-item {
|
.user-meta span {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
transition: all 0.25s ease;
|
transition: all 0.25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-meta-item:hover {
|
.user-meta span:hover {
|
||||||
color: rgba(255, 255, 255, 1);
|
color: rgba(255, 255, 255, 1);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-meta-icon {
|
.user-meta i {
|
||||||
opacity: 0.8;
|
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 */
|
/* Benutzer-Aktionsbereich */
|
||||||
.profile-actions {
|
.profile-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -504,31 +390,24 @@
|
|||||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
|
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);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
border: 3px solid rgba(126, 63, 242, 0.3);
|
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);
|
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);
|
background: linear-gradient(135deg, #7e3ff2, #3282f6);
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.light .profile-username,
|
html.light .user-bio,
|
||||||
html.light .profile-bio,
|
|
||||||
html.light .activity-content {
|
html.light .activity-content {
|
||||||
color: #1a202c;
|
color: #1a202c;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.light .username-badge {
|
html.light .user-meta span {
|
||||||
background: rgba(126, 63, 242, 0.15);
|
|
||||||
border: 1px solid rgba(126, 63, 242, 0.3);
|
|
||||||
color: #7e3ff2;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.light .profile-meta {
|
|
||||||
color: #4a5568;
|
color: #4a5568;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,63 +485,22 @@
|
|||||||
<div class="container mx-auto px-4 py-10">
|
<div class="container mx-auto px-4 py-10">
|
||||||
<!-- Profil-Container -->
|
<!-- Profil-Container -->
|
||||||
<div class="profile-container">
|
<div class="profile-container">
|
||||||
<!-- Profil-Header mit Benutzerinformationen -->
|
<!-- User Info Section -->
|
||||||
<div class="profile-header">
|
<div class="profile-header">
|
||||||
<!-- Profilbild -->
|
<div class="avatar-container">
|
||||||
<div class="profile-avatar">
|
<img src="{{ user.avatar if user.avatar else url_for('static', filename='img/default-avatar.png') }}" alt="Profilbild" class="avatar">
|
||||||
{% if user.profile_image %}
|
<div class="avatar-edit">
|
||||||
<img src="{{ user.profile_image }}" alt="{{ user.name }}" />
|
<i class="fas fa-camera"></i>
|
||||||
{% else %}
|
</div>
|
||||||
<div class="profile-avatar-placeholder">
|
|
||||||
<i class="fas fa-user"></i>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="user-info">
|
||||||
<!-- Profilinformationen -->
|
<h1>{{ user.username }}</h1>
|
||||||
<div class="profile-info">
|
<p class="user-bio">{{ user.bio if user.bio else 'Keine Bio vorhanden. Klicke auf bearbeiten, um eine hinzuzufügen.' }}</p>
|
||||||
<h1 class="profile-name">{{ user.name|default('Max Mustermann') }}</h1>
|
<div class="user-meta">
|
||||||
<div class="profile-username">
|
<span><i class="fas fa-map-marker-alt"></i> {{ user.location if user.location else 'Kein Standort angegeben' }}</span>
|
||||||
@{{ user.username|default('maxmustermann') }}
|
<span><i class="fas fa-calendar-alt"></i> Mitglied seit {{ user.created_at.strftime('%d.%m.%Y') }}</span>
|
||||||
{% 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>
|
</div>
|
||||||
|
<button class="edit-profile-btn">Profil bearbeiten</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -673,7 +511,7 @@
|
|||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-lightbulb"></i>
|
<i class="fas fa-lightbulb"></i>
|
||||||
</div>
|
</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 class="stat-label">Gedanken</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -682,7 +520,7 @@
|
|||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-project-diagram"></i>
|
<i class="fas fa-project-diagram"></i>
|
||||||
</div>
|
</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 class="stat-label">Verbindungen</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -691,7 +529,7 @@
|
|||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-users"></i>
|
<i class="fas fa-users"></i>
|
||||||
</div>
|
</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 class="stat-label">Follower</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -700,7 +538,7 @@
|
|||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-comment-dots"></i>
|
<i class="fas fa-comment-dots"></i>
|
||||||
</div>
|
</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 class="stat-label">Beiträge</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -709,7 +547,7 @@
|
|||||||
<div class="stat-icon">
|
<div class="stat-icon">
|
||||||
<i class="fas fa-star"></i>
|
<i class="fas fa-star"></i>
|
||||||
</div>
|
</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 class="stat-label">Bewertung</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -731,115 +569,124 @@
|
|||||||
<!-- Aktivitäts-Tab (Standardmäßig angezeigt) -->
|
<!-- Aktivitäts-Tab (Standardmäßig angezeigt) -->
|
||||||
<div class="tab-content" id="activity-tab">
|
<div class="tab-content" id="activity-tab">
|
||||||
<div class="activity-feed">
|
<div class="activity-feed">
|
||||||
<!-- Aktivität 1 -->
|
{% if activities %}
|
||||||
<div class="activity-card">
|
{% for activity in activities %}
|
||||||
<div class="activity-header">
|
<div class="activity-card">
|
||||||
<div class="activity-title">Neuer Gedanke hinzugefügt</div>
|
<div class="activity-header">
|
||||||
<div class="activity-date">vor 2 Stunden</div>
|
<div class="activity-title">{{ activity.title }}</div>
|
||||||
</div>
|
<div class="activity-date">{{ activity.date }}</div>
|
||||||
<div class="activity-content">
|
</div>
|
||||||
<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 class="activity-content">
|
||||||
</div>
|
<p>{{ activity.content }}</p>
|
||||||
<div class="activity-footer">
|
</div>
|
||||||
<div class="activity-reactions">
|
<div class="activity-footer">
|
||||||
<button class="reaction-button">
|
<div class="activity-reactions">
|
||||||
<i class="fas fa-thumbs-up"></i> <span>24</span>
|
<button class="reaction-button {% if activity.user_liked %}active{% endif %}">
|
||||||
</button>
|
<i class="fas fa-thumbs-up"></i> <span>{{ activity.likes }}</span>
|
||||||
<button class="reaction-button">
|
</button>
|
||||||
<i class="fas fa-comment"></i> <span>8</span>
|
<button class="reaction-button">
|
||||||
</button>
|
<i class="fas fa-comment"></i> <span>{{ activity.comments }}</span>
|
||||||
<button class="reaction-button">
|
</button>
|
||||||
<i class="fas fa-share"></i> <span>3</span>
|
<button class="reaction-button">
|
||||||
</button>
|
<i class="fas fa-share"></i> <span>{{ activity.shares }}</span>
|
||||||
</div>
|
</button>
|
||||||
<div class="activity-actions">
|
</div>
|
||||||
<button class="action-button">
|
<div class="activity-actions">
|
||||||
Ansehen
|
<button class="action-button">
|
||||||
</button>
|
Ansehen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
</div>
|
{% endif %}
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Weitere Tab-Inhalte (werden per JavaScript angezeigt) -->
|
<!-- Weitere Tab-Inhalte (werden per JavaScript angezeigt) -->
|
||||||
<div class="tab-content hidden" id="thoughts-tab">
|
<div class="tab-content hidden" id="thoughts-tab">
|
||||||
<p class="text-center text-gray-400 py-12">
|
<div id="thoughts-container">
|
||||||
<i class="fas fa-spinner fa-spin text-3xl mb-4 block"></i>
|
{% if thoughts %}
|
||||||
Gedanken werden geladen...
|
{% for thought in thoughts %}
|
||||||
</p>
|
<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>
|
||||||
|
|
||||||
<div class="tab-content hidden" id="collections-tab">
|
<div class="tab-content hidden" id="collections-tab">
|
||||||
<p class="text-center text-gray-400 py-12">
|
<div id="collections-container">
|
||||||
<i class="fas fa-spinner fa-spin text-3xl mb-4 block"></i>
|
{% if collections %}
|
||||||
Sammlungen werden geladen...
|
{% for collection in collections %}
|
||||||
</p>
|
<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>
|
||||||
|
|
||||||
<div class="tab-content hidden" id="connections-tab">
|
<div class="tab-content hidden" id="connections-tab">
|
||||||
<p class="text-center text-gray-400 py-12">
|
<div id="connections-container">
|
||||||
<i class="fas fa-spinner fa-spin text-3xl mb-4 block"></i>
|
{% if connections %}
|
||||||
Verbindungen werden geladen...
|
{% for connection in connections %}
|
||||||
</p>
|
<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>
|
||||||
|
|
||||||
<div class="tab-content hidden" id="settings-tab">
|
<div class="tab-content hidden" id="settings-tab">
|
||||||
@@ -849,22 +696,22 @@
|
|||||||
<div class="settings-card-body">
|
<div class="settings-card-body">
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<label class="settings-label" for="name">Name</label>
|
<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>
|
||||||
|
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<label class="settings-label" for="bio">Über mich</label>
|
<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>
|
||||||
|
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<label class="settings-label" for="location">Standort</label>
|
<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>
|
||||||
|
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<label class="settings-label" for="website">Website</label>
|
<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>
|
</div>
|
||||||
|
|
||||||
<button class="profile-action-btn primary mt-4">
|
<button class="profile-action-btn primary mt-4">
|
||||||
@@ -878,7 +725,7 @@
|
|||||||
<div class="settings-card-body">
|
<div class="settings-card-body">
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<label class="settings-label" for="email">E-Mail-Adresse</label>
|
<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>
|
||||||
|
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
@@ -903,7 +750,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script nonce="{{ csp_nonce }}">
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Profil-Tab-Funktionalität
|
// Profil-Tab-Funktionalität
|
||||||
const tabs = document.querySelectorAll('.profile-tab');
|
const tabs = document.querySelectorAll('.profile-tab');
|
||||||
@@ -7,14 +7,14 @@ This package contains various utilities for database management,
|
|||||||
user management, and server administration.
|
user management, and server administration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .db_fix import fix_database_schema
|
# Import utilities that don't depend on app.py first
|
||||||
from .db_rebuild import rebuild_database
|
from .db_check import check_db_connection, initialize_db_if_needed
|
||||||
from .db_test import test_database_connection, test_models, print_database_stats, run_all_tests
|
|
||||||
from .user_manager import list_users, create_user, reset_password, delete_user, create_admin_user
|
|
||||||
from .server import run_development_server
|
|
||||||
|
|
||||||
|
# Define the list of all available utilities
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Database utilities
|
# Database utilities
|
||||||
|
'check_db_connection',
|
||||||
|
'initialize_db_if_needed',
|
||||||
'fix_database_schema',
|
'fix_database_schema',
|
||||||
'rebuild_database',
|
'rebuild_database',
|
||||||
'test_database_connection',
|
'test_database_connection',
|
||||||
@@ -32,3 +32,10 @@ __all__ = [
|
|||||||
# Server management
|
# Server management
|
||||||
'run_development_server',
|
'run_development_server',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Import remaining modules that might depend on app
|
||||||
|
from .db_fix import fix_database_schema
|
||||||
|
from .db_rebuild import rebuild_database
|
||||||
|
from .db_test import test_database_connection, test_models, print_database_stats, run_all_tests
|
||||||
|
from .user_manager import list_users, create_user, reset_password, delete_user, create_admin_user
|
||||||
|
from .server import run_development_server
|
||||||
BIN
utils/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
utils/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/db_check.cpython-311.pyc
Normal file
BIN
utils/__pycache__/db_check.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
utils/__pycache__/db_rebuild.cpython-311.pyc
Normal file
BIN
utils/__pycache__/db_rebuild.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
79
utils/db_check.py
Normal file
79
utils/db_check.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy import text
|
||||||
|
import time
|
||||||
|
|
||||||
|
def check_db_connection(db):
|
||||||
|
"""
|
||||||
|
Überprüft die Datenbankverbindung und versucht ggf. die Verbindung wiederherzustellen
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: SQLAlchemy-Instanz
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True, wenn die Verbindung erfolgreich ist, sonst False
|
||||||
|
"""
|
||||||
|
max_retries = 3
|
||||||
|
retry_count = 0
|
||||||
|
|
||||||
|
while retry_count < max_retries:
|
||||||
|
try:
|
||||||
|
# Führe eine einfache Abfrage durch, um die Verbindung zu testen
|
||||||
|
with current_app.app_context():
|
||||||
|
db.session.execute(text('SELECT 1'))
|
||||||
|
return True
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
retry_count += 1
|
||||||
|
print(f"DB-Verbindungsfehler (Versuch {retry_count}/{max_retries}): {str(e)}")
|
||||||
|
|
||||||
|
if retry_count < max_retries:
|
||||||
|
# Warte vor dem nächsten Versuch
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Versuche die Verbindung zurückzusetzen
|
||||||
|
try:
|
||||||
|
db.session.rollback()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def initialize_db_if_needed(db, initialize_function=None):
|
||||||
|
"""
|
||||||
|
Initialisiert die Datenbank, falls erforderlich
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: SQLAlchemy-Instanz
|
||||||
|
initialize_function: Funktion, die aufgerufen wird, um die Datenbank zu initialisieren
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True, wenn die Datenbank bereit ist, sonst False
|
||||||
|
"""
|
||||||
|
# Prüfe die Verbindung
|
||||||
|
if not check_db_connection(db):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Prüfe, ob die Tabellen existieren
|
||||||
|
try:
|
||||||
|
with current_app.app_context():
|
||||||
|
# Führe eine Testabfrage auf einer Tabelle durch
|
||||||
|
db.session.execute(text('SELECT COUNT(*) FROM user'))
|
||||||
|
except SQLAlchemyError:
|
||||||
|
# Tabellen existieren nicht, erstelle sie
|
||||||
|
try:
|
||||||
|
with current_app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# Rufe die Initialisierungsfunktion auf, falls vorhanden
|
||||||
|
if initialize_function and callable(initialize_function):
|
||||||
|
initialize_function()
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler bei DB-Initialisierung: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -11,12 +11,20 @@ import importlib.util
|
|||||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
sys.path.insert(0, parent_dir)
|
sys.path.insert(0, parent_dir)
|
||||||
|
|
||||||
from app import app, db_path, create_default_categories
|
# Import models directly to avoid circular import
|
||||||
from models import db, User, Category
|
from models import db, User, Category
|
||||||
|
|
||||||
def rebuild_database():
|
def rebuild_database(app_instance=None):
|
||||||
"""Completely rebuilds the database by dropping and recreating all tables."""
|
"""Completely rebuilds the database by dropping and recreating all tables."""
|
||||||
with app.app_context():
|
if app_instance is None:
|
||||||
|
# Only import app if it's not provided as a parameter
|
||||||
|
from app import app as app_instance
|
||||||
|
from app import db_path
|
||||||
|
else:
|
||||||
|
# Get db_path from app_instance config
|
||||||
|
db_path = app_instance.config['SQLALCHEMY_DATABASE_URI'].replace('sqlite:///', '')
|
||||||
|
|
||||||
|
with app_instance.app_context():
|
||||||
print(f"Database path: {db_path}")
|
print(f"Database path: {db_path}")
|
||||||
|
|
||||||
# Back up existing database if it exists
|
# Back up existing database if it exists
|
||||||
@@ -68,7 +76,9 @@ def rebuild_database():
|
|||||||
|
|
||||||
# Create default categories
|
# Create default categories
|
||||||
print("Creating default categories...")
|
print("Creating default categories...")
|
||||||
create_default_categories()
|
# Instead of directly importing create_default_categories, call it through app_instance
|
||||||
|
create_default_categories_func = getattr(sys.modules['app'], 'create_default_categories')
|
||||||
|
create_default_categories_func()
|
||||||
|
|
||||||
print("Database rebuild completed successfully!")
|
print("Database rebuild completed successfully!")
|
||||||
return True
|
return True
|
||||||
225
utils/download_resources.py
Executable file
225
utils/download_resources.py
Executable file
@@ -0,0 +1,225 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Dieses Skript lädt externe Ressourcen wie Font Awesome, Tailwind CSS und Alpine.js herunter
|
||||||
|
und installiert sie lokal, um die Abhängigkeit von externen CDNs zu vermeiden und
|
||||||
|
die Content Security Policy zu erfüllen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import zipfile
|
||||||
|
import io
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# URLs für die Ressourcen
|
||||||
|
RESOURCES = {
|
||||||
|
'alpine.js': 'https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js',
|
||||||
|
'font_awesome': 'https://use.fontawesome.com/releases/v6.4.0/fontawesome-free-6.4.0-web.zip',
|
||||||
|
'inter_font_300': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2',
|
||||||
|
'inter_font_400': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2',
|
||||||
|
'inter_font_500': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2',
|
||||||
|
'inter_font_600': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2',
|
||||||
|
'inter_font_700': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2',
|
||||||
|
'jetbrains_font_400': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2',
|
||||||
|
'jetbrains_font_500': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2',
|
||||||
|
'jetbrains_font_700': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Zielverzeichnisse
|
||||||
|
DIRECTORIES = {
|
||||||
|
'js': os.path.join(BASE_DIR, 'static', 'js'),
|
||||||
|
'css': os.path.join(BASE_DIR, 'static', 'css'),
|
||||||
|
'fonts': os.path.join(BASE_DIR, 'static', 'fonts'),
|
||||||
|
'webfonts': os.path.join(BASE_DIR, 'static', 'webfonts'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def download_file(url, filepath):
|
||||||
|
"""Lädt eine Datei von einer URL herunter und speichert sie lokal."""
|
||||||
|
response = requests.get(url, stream=True)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
f.write(chunk)
|
||||||
|
print(f"✅ Heruntergeladen: {os.path.basename(filepath)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Fehler beim Herunterladen von {url}: {response.status_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def extract_zip(zip_data, extract_to):
|
||||||
|
"""Extrahiert eine ZIP-Datei in das angegebene Verzeichnis."""
|
||||||
|
with zipfile.ZipFile(io.BytesIO(zip_data)) as zip_ref:
|
||||||
|
zip_ref.extractall(extract_to)
|
||||||
|
|
||||||
|
def setup_directories():
|
||||||
|
"""Erstellt die benötigten Verzeichnisse, falls sie nicht existieren."""
|
||||||
|
for directory in DIRECTORIES.values():
|
||||||
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
print(f"📁 Verzeichnis erstellt/überprüft: {directory}")
|
||||||
|
|
||||||
|
def download_alpine():
|
||||||
|
"""Lädt Alpine.js herunter."""
|
||||||
|
url = RESOURCES['alpine.js']
|
||||||
|
filepath = os.path.join(DIRECTORIES['js'], 'alpine.min.js')
|
||||||
|
download_file(url, filepath)
|
||||||
|
|
||||||
|
def download_font_awesome():
|
||||||
|
"""Lädt Font Awesome herunter und extrahiert die Dateien."""
|
||||||
|
url = RESOURCES['font_awesome']
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
# Temporäres Verzeichnis für die Extraktion
|
||||||
|
temp_dir = os.path.join(BASE_DIR, 'temp_fontawesome')
|
||||||
|
os.makedirs(temp_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# ZIP-Datei extrahieren
|
||||||
|
extract_zip(response.content, temp_dir)
|
||||||
|
|
||||||
|
# CSS-Datei kopieren
|
||||||
|
fa_dir = os.path.join(temp_dir, 'fontawesome-free-6.4.0-web')
|
||||||
|
css_source = os.path.join(fa_dir, 'css', 'all.min.css')
|
||||||
|
css_dest = os.path.join(DIRECTORIES['css'], 'all.min.css')
|
||||||
|
shutil.copyfile(css_source, css_dest)
|
||||||
|
print(f"✅ Font Awesome CSS kopiert nach {css_dest}")
|
||||||
|
|
||||||
|
# Webfonts-Verzeichnis kopieren
|
||||||
|
webfonts_source = os.path.join(fa_dir, 'webfonts')
|
||||||
|
shutil.rmtree(DIRECTORIES['webfonts'], ignore_errors=True)
|
||||||
|
shutil.copytree(webfonts_source, DIRECTORIES['webfonts'])
|
||||||
|
print(f"✅ Font Awesome Webfonts kopiert nach {DIRECTORIES['webfonts']}")
|
||||||
|
|
||||||
|
# Temporäres Verzeichnis löschen
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Fehler beim Herunterladen von Font Awesome: {response.status_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def download_google_fonts():
|
||||||
|
"""Lädt die Google Fonts (Inter und JetBrains Mono) herunter."""
|
||||||
|
font_files = {
|
||||||
|
'inter-light.woff2': RESOURCES['inter_font_300'],
|
||||||
|
'inter-regular.woff2': RESOURCES['inter_font_400'],
|
||||||
|
'inter-medium.woff2': RESOURCES['inter_font_500'],
|
||||||
|
'inter-semibold.woff2': RESOURCES['inter_font_600'],
|
||||||
|
'inter-bold.woff2': RESOURCES['inter_font_700'],
|
||||||
|
'jetbrainsmono-regular.woff2': RESOURCES['jetbrains_font_400'],
|
||||||
|
'jetbrainsmono-medium.woff2': RESOURCES['jetbrains_font_500'],
|
||||||
|
'jetbrainsmono-bold.woff2': RESOURCES['jetbrains_font_700'],
|
||||||
|
}
|
||||||
|
|
||||||
|
for filename, url in font_files.items():
|
||||||
|
filepath = os.path.join(DIRECTORIES['fonts'], filename)
|
||||||
|
download_file(url, filepath)
|
||||||
|
|
||||||
|
def setup_tailwind():
|
||||||
|
"""Richtet Tailwind CSS ein."""
|
||||||
|
# Tailwind-Konfiguration erstellen, falls sie nicht existiert
|
||||||
|
tailwind_config = os.path.join(BASE_DIR, 'tailwind.config.js')
|
||||||
|
if not os.path.exists(tailwind_config):
|
||||||
|
with open(tailwind_config, 'w') as f:
|
||||||
|
f.write("""/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
darkMode: 'class',
|
||||||
|
content: [
|
||||||
|
"./templates/**/*.html",
|
||||||
|
"./static/**/*.js",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'sans-serif'],
|
||||||
|
mono: ['JetBrains Mono', 'ui-monospace', 'monospace']
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
50: '#f5f3ff',
|
||||||
|
100: '#ede9fe',
|
||||||
|
200: '#ddd6fe',
|
||||||
|
300: '#c4b5fd',
|
||||||
|
400: '#a78bfa',
|
||||||
|
500: '#8b5cf6',
|
||||||
|
600: '#7c3aed',
|
||||||
|
700: '#6d28d9',
|
||||||
|
800: '#5b21b6',
|
||||||
|
900: '#4c1d95'
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
50: '#ecfdf5',
|
||||||
|
100: '#d1fae5',
|
||||||
|
200: '#a7f3d0',
|
||||||
|
300: '#6ee7b7',
|
||||||
|
400: '#34d399',
|
||||||
|
500: '#10b981',
|
||||||
|
600: '#059669',
|
||||||
|
700: '#047857',
|
||||||
|
800: '#065f46',
|
||||||
|
900: '#064e3b'
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
500: '#374151',
|
||||||
|
600: '#1f2937',
|
||||||
|
700: '#111827',
|
||||||
|
800: '#0e1220',
|
||||||
|
900: '#0a0e19'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
print(f"✅ Tailwind-Konfiguration erstellt: {tailwind_config}")
|
||||||
|
|
||||||
|
# Input-CSS-Datei erstellen
|
||||||
|
input_css_dir = os.path.join(DIRECTORIES['css'], 'src')
|
||||||
|
os.makedirs(input_css_dir, exist_ok=True)
|
||||||
|
|
||||||
|
input_css = os.path.join(input_css_dir, 'input.css')
|
||||||
|
if not os.path.exists(input_css):
|
||||||
|
with open(input_css, 'w') as f:
|
||||||
|
f.write("""@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
""")
|
||||||
|
print(f"✅ Tailwind Input-CSS erstellt: {input_css}")
|
||||||
|
|
||||||
|
# Hinweis zur Kompilierung anzeigen
|
||||||
|
print("\n📋 Um Tailwind CSS zu kompilieren, führe folgenden Befehl aus:")
|
||||||
|
print("npm install -D tailwindcss")
|
||||||
|
print(f"npx tailwindcss -i {input_css} -o {os.path.join(DIRECTORIES['css'], 'tailwind.min.css')} --minify")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Hauptfunktion: Lädt alle benötigten Ressourcen herunter."""
|
||||||
|
print("🚀 Starte den Download externer Ressourcen...")
|
||||||
|
setup_directories()
|
||||||
|
|
||||||
|
# Alpine.js herunterladen
|
||||||
|
print("\n📦 Lade Alpine.js herunter...")
|
||||||
|
download_alpine()
|
||||||
|
|
||||||
|
# Font Awesome herunterladen
|
||||||
|
print("\n📦 Lade Font Awesome herunter...")
|
||||||
|
download_font_awesome()
|
||||||
|
|
||||||
|
# Google Fonts herunterladen
|
||||||
|
print("\n📦 Lade Google Fonts herunter...")
|
||||||
|
download_google_fonts()
|
||||||
|
|
||||||
|
# Tailwind CSS einrichten
|
||||||
|
print("\n📦 Richte Tailwind CSS ein...")
|
||||||
|
setup_tailwind()
|
||||||
|
|
||||||
|
print("\n✅ Alle Ressourcen wurden erfolgreich heruntergeladen und eingerichtet!")
|
||||||
|
print("🔒 Die Webseite sollte nun ohne externe CDNs funktionieren und die Content Security Policy erfüllen.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# MindMapProjekt - Roadmap
|
|
||||||
|
|
||||||
## Projektübersicht
|
|
||||||
Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen. Das Projekt wird umfassend überarbeitet, um ein modernes, benutzerfreundliches Design und erweiterte Funktionalitäten zu bieten.
|
|
||||||
|
|
||||||
## Technischer Stack
|
|
||||||
- **Backend**: Python/Flask
|
|
||||||
- **Frontend**:
|
|
||||||
- Tailwind CSS für moderne UI
|
|
||||||
- SVG-Bibliotheken für Visualisierungen (D3.js)
|
|
||||||
- JavaScript/Alpine.js für interaktive Komponenten
|
|
||||||
- **Datenbank**: SQLite mit SQLAlchemy
|
|
||||||
- **KI-Integration**: OpenAI API für intelligente Assistenz
|
|
||||||
|
|
||||||
## Installation und Verwendung
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
1. Repository klonen
|
|
||||||
2. Virtuelle Umgebung erstellen: `python -m venv venv`
|
|
||||||
3. Virtuelle Umgebung aktivieren:
|
|
||||||
- Windows: `venv\Scripts\activate`
|
|
||||||
- Unix/MacOS: `source venv/bin/activate`
|
|
||||||
4. Abhängigkeiten installieren: `pip install -r requirements.txt`
|
|
||||||
5. Datenbank initialisieren: `python TOOLS.py db:rebuild`
|
|
||||||
6. Admin-Benutzer erstellen: `python TOOLS.py user:admin`
|
|
||||||
7. Server starten: `python TOOLS.py server:run`
|
|
||||||
|
|
||||||
### Standardbenutzer
|
|
||||||
- **Admin-Benutzer**: Username: `admin` / Passwort: `admin`
|
|
||||||
- **Testbenutzer**: Username: `user` / Passwort: `user`
|
|
||||||
|
|
||||||
### Verwaltungswerkzeuge mit TOOLS.py
|
|
||||||
Das Projekt enthält ein zentrales Verwaltungsskript `TOOLS.py`, das verschiedene Hilfsfunktionen bietet:
|
|
||||||
|
|
||||||
#### Datenbankverwaltung
|
|
||||||
- `python TOOLS.py db:fix` - Reparieren der Datenbankstruktur
|
|
||||||
- `python TOOLS.py db:rebuild` - Datenbank neu aufbauen (löscht alle Daten!)
|
|
||||||
- `python TOOLS.py db:test` - Datenbankverbindung und Modelle testen
|
|
||||||
- `python TOOLS.py db:stats` - Datenbankstatistiken anzeigen
|
|
||||||
|
|
||||||
#### Benutzerverwaltung
|
|
||||||
- `python TOOLS.py user:list` - Alle Benutzer anzeigen
|
|
||||||
- `python TOOLS.py user:create -u USERNAME -e EMAIL -p PASSWORD [-a]` - Neuen Benutzer erstellen
|
|
||||||
- `python TOOLS.py user:admin` - Admin-Benutzer erstellen (admin/admin)
|
|
||||||
- `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD` - Benutzerpasswort zurücksetzen
|
|
||||||
- `python TOOLS.py user:delete -u USERNAME` - Benutzer löschen
|
|
||||||
|
|
||||||
#### Serververwaltung
|
|
||||||
- `python TOOLS.py server:run [--host HOST] [--port PORT] [--no-debug]` - Entwicklungsserver starten
|
|
||||||
|
|
||||||
Für detaillierte Hilfe: `python TOOLS.py -h`
|
|
||||||
|
|
||||||
## Roadmap der Überarbeitung
|
|
||||||
|
|
||||||
### Phase 1: Grundlegende Infrastruktur ✅
|
|
||||||
- [x] Bestandsaufnahme des aktuellen Projekts
|
|
||||||
- [x] Erstellung der Roadmap
|
|
||||||
- [x] Aktualisierung der Abhängigkeiten
|
|
||||||
- [x] Integration von Tailwind CSS
|
|
||||||
- [x] Einrichtung der SVG-Bibliotheken (D3.js)
|
|
||||||
- [x] Favicon erstellen
|
|
||||||
- [x] Setup-Skript für einfache Installation
|
|
||||||
|
|
||||||
### Phase 2: Design-Überarbeitung 🔄
|
|
||||||
- [x] Implementierung des Dark Mode
|
|
||||||
- [x] Erstellung eines modernen, minimalistischen UI mit Tech-Ästhetik
|
|
||||||
- [x] Responsive Design für alle Geräte
|
|
||||||
- [ ] Gestaltung der Landing Page mit großer Typografie
|
|
||||||
|
|
||||||
### Phase 3: Mindmap-Funktionalitäten 🔄
|
|
||||||
- [x] Verbesserte Visualisierung mit SVG und D3.js
|
|
||||||
- [x] Implementierung der Mouseover-Funktion
|
|
||||||
- [x] Entwicklung der Suchfunktion für Knoten
|
|
||||||
- [ ] Tagging-System für Inhalte
|
|
||||||
- [ ] Quellenmanagement und -verlinkung
|
|
||||||
- [ ] Upload-Funktionalität an Knotenpunkten
|
|
||||||
|
|
||||||
### Phase 4: Kernseitenentwicklung
|
|
||||||
- [ ] Überarbeitung der Startseite mit neuen Features
|
|
||||||
- [ ] Entwicklung der "Wer sind wir?"-Seite
|
|
||||||
- [ ] Implementierung von Impressum und Datenschutzerklärung
|
|
||||||
- [ ] Erstellung der Kontaktseite mit FAQs
|
|
||||||
- [ ] Überarbeitung des Benutzerprofilbereichs
|
|
||||||
|
|
||||||
### Phase 5: Community-Features
|
|
||||||
- [ ] Entwicklung des Autorenbereichs
|
|
||||||
- [ ] Implementierung von Community-Bereichen für Themenbereiche
|
|
||||||
- [ ] Verbesserter Kommentarbereich
|
|
||||||
- [ ] Benutzerrechtemanagement
|
|
||||||
|
|
||||||
### Phase 6: KI-Integration
|
|
||||||
- [ ] Implementierung des Frage-Antwort-Systems
|
|
||||||
- [ ] KI-generierte Themeneinleitungen
|
|
||||||
- [ ] Intelligente Suchunterstützung
|
|
||||||
- [ ] Geführte Pfade durch Themenbereiche
|
|
||||||
- [ ] Vorgeschlagene Chat-Möglichkeiten
|
|
||||||
|
|
||||||
### Phase 7: Benutzerprofilfunktionen
|
|
||||||
- [ ] Speichern von Thematiken
|
|
||||||
- [ ] Persönliche Mindmap/Pinboard
|
|
||||||
- [ ] Beitragsmanagement
|
|
||||||
- [ ] Benutzerstatistiken und -aktivitäten
|
|
||||||
|
|
||||||
### Phase 8: Testing und Optimierung
|
|
||||||
- [ ] Umfassende Tests aller Funktionen
|
|
||||||
- [ ] Performance-Optimierung
|
|
||||||
- [ ] SEO-Implementierung
|
|
||||||
- [ ] Barrierefreiheit prüfen und verbessern
|
|
||||||
|
|
||||||
### Phase 9: Dokumentation und Einführung
|
|
||||||
- [ ] Erstellung von Benutzeranleitungen
|
|
||||||
- [ ] Entwicklerdokumentation
|
|
||||||
- [ ] Administratorenhandbuch
|
|
||||||
- [ ] Guided Tour für neue Benutzer
|
|
||||||
|
|
||||||
## Aktueller Status
|
|
||||||
- **Phase 1**: ✅ Abgeschlossen
|
|
||||||
- **Phase 2**: 🔄 In Bearbeitung (75% abgeschlossen)
|
|
||||||
- **Phase 3**: 🔄 In Bearbeitung (50% abgeschlossen)
|
|
||||||
|
|
||||||
## Aktuelle Fortschritte
|
|
||||||
- Grundlegende UI modernisiert mit Tailwind CSS und Dark Mode
|
|
||||||
- Neues Favicon für bessere visuelle Identität erstellt
|
|
||||||
- Setup-Prozess vereinfacht mit einem Shell-Skript
|
|
||||||
- Mindmap-Visualisierung komplett überarbeitet mit D3.js für eine interaktivere Erfahrung
|
|
||||||
- Responsive Design für optimale Darstellung auf allen Geräten
|
|
||||||
|
|
||||||
## Nächste Schritte
|
|
||||||
- Fertigstellung der Landing Page
|
|
||||||
- Erstellung der "Wer sind wir?"-Seite
|
|
||||||
- Implementierung des Tagging-Systems für Gedanken
|
|
||||||
- Verbesserung der Gedankenansicht im Mindmap-Bereich
|
|
||||||
|
|
||||||
*Zuletzt aktualisiert: 01.06.2024*
|
|
||||||
Binary file not shown.
@@ -1,4 +0,0 @@
|
|||||||
# Netscape HTTP Cookie File
|
|
||||||
# https://curl.se/docs/http-cookies.html
|
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
|
||||||
|
|
||||||
Binary file not shown.
@@ -1,14 +0,0 @@
|
|||||||
flask==2.2.5
|
|
||||||
flask-login==0.6.2
|
|
||||||
flask-wtf
|
|
||||||
email-validator
|
|
||||||
python-dotenv
|
|
||||||
werkzeug==2.2.3
|
|
||||||
flask-sqlalchemy==3.0.5
|
|
||||||
openai==1.3.0
|
|
||||||
requests==2.31.0
|
|
||||||
flask-cors==4.0.0
|
|
||||||
gunicorn==21.2.0
|
|
||||||
#pillow==10.0.1
|
|
||||||
pytest==7.4.0
|
|
||||||
pytest-flask==1.2.0
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from init_db import init_database
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Lade .env-Datei explizit
|
|
||||||
env_path = Path(__file__).parent / ".env"
|
|
||||||
if env_path.exists():
|
|
||||||
print(f"Lade Umgebungsvariablen aus {env_path}")
|
|
||||||
load_dotenv(dotenv_path=env_path, override=True, force=True)
|
|
||||||
else:
|
|
||||||
print("Warnung: .env-Datei nicht gefunden!")
|
|
||||||
|
|
||||||
# Check if CSS file exists, build it if it doesn't
|
|
||||||
css_file = Path(__file__).parent / "static" / "css" / "main.css"
|
|
||||||
if not css_file.exists():
|
|
||||||
print("CSS file not found. Building with Tailwind...")
|
|
||||||
try:
|
|
||||||
from build_css import build_css
|
|
||||||
build_css()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Warning: Failed to build CSS: {e}")
|
|
||||||
print("You may need to run 'python build_css.py' manually.")
|
|
||||||
|
|
||||||
# Initialize the database first
|
|
||||||
init_database()
|
|
||||||
|
|
||||||
# Run the Flask application
|
|
||||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Farben für Ausgaben
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[0;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
echo -e "${GREEN}==== MindMap Projekt Setup ====${NC}"
|
|
||||||
|
|
||||||
# Verzeichnis erstellen, wenn nicht vorhanden
|
|
||||||
echo -e "${BLUE}Überprüfe Verzeichnisstruktur...${NC}"
|
|
||||||
mkdir -p static/css/src
|
|
||||||
mkdir -p static/img
|
|
||||||
mkdir -p static/js
|
|
||||||
mkdir -p bin
|
|
||||||
|
|
||||||
# Überprüfe, ob .env-Datei existiert und erstelle sie, wenn nicht
|
|
||||||
echo -e "${BLUE}Überprüfe .env-Datei...${NC}"
|
|
||||||
if [ ! -f ".env" ]; then
|
|
||||||
echo -e "${YELLOW}Keine .env-Datei gefunden. Erstelle neue .env-Datei aus example.env...${NC}"
|
|
||||||
cp example.env .env
|
|
||||||
echo -e "${YELLOW}Bitte bearbeiten Sie die .env-Datei und setzen Sie die erforderlichen Werte.${NC}"
|
|
||||||
echo -e "${YELLOW}Insbesondere müssen Sie einen gültigen API-Schlüssel für OpenAI setzen.${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${GREEN}.env-Datei existiert bereits.${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Python-Abhängigkeiten installieren
|
|
||||||
echo -e "${BLUE}Installiere Python-Abhängigkeiten...${NC}"
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Zusätzliche Abhängigkeiten für Favicon-Erstellung
|
|
||||||
echo -e "${BLUE}Installiere Abhängigkeiten für Favicon-Erstellung...${NC}"
|
|
||||||
pip install pillow cairosvg
|
|
||||||
|
|
||||||
# Tailwind CSS mit Python-Skript kompilieren
|
|
||||||
echo -e "${BLUE}Kompiliere Tailwind CSS...${NC}"
|
|
||||||
python build_css.py
|
|
||||||
|
|
||||||
# Favicon generieren
|
|
||||||
echo -e "${BLUE}Generiere Favicon...${NC}"
|
|
||||||
python static/img/favicon-gen.py
|
|
||||||
|
|
||||||
# Erstelle die Datenbank
|
|
||||||
echo -e "${BLUE}Initialisiere die Datenbank...${NC}"
|
|
||||||
python init_db.py
|
|
||||||
|
|
||||||
echo -e "${GREEN}==== Setup abgeschlossen ====${NC}"
|
|
||||||
echo -e "${GREEN}Starte die Anwendung mit: python run.py${NC}"
|
|
||||||
echo -e "${GREEN}Für Entwicklung mit CSS-Autoreload: python dev.py${NC}"
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
/* ChatGPT Assistent Styles */
|
|
||||||
#chatgpt-assistant {
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#assistant-chat {
|
|
||||||
transition: max-height 0.3s ease, opacity 0.3s ease;
|
|
||||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#assistant-toggle {
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
#assistant-toggle:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#assistant-history {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#assistant-history::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#assistant-history::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#assistant-history::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(156, 163, 175, 0.5);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark #assistant-history::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(156, 163, 175, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mach Platz für Notifications, damit sie nicht mit dem Assistenten überlappen */
|
|
||||||
.notification-area {
|
|
||||||
bottom: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verbesserter Glassmorphism-Effekt */
|
|
||||||
.glass-morphism {
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
-webkit-backdrop-filter: blur(10px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .glass-morphism {
|
|
||||||
background: rgba(15, 23, 42, 0.3);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dunkleres Dark Theme */
|
|
||||||
.dark {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgba(10, 15, 25, var(--tw-bg-opacity)) !important;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .bg-dark-900 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgba(10, 15, 25, var(--tw-bg-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .bg-dark-800 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgba(15, 23, 42, var(--tw-bg-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .bg-dark-700 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgba(23, 33, 64, var(--tw-bg-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer immer unten */
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
/**
|
|
||||||
* ChatGPT Assistent Modul
|
|
||||||
* Verwaltet die Interaktion mit der OpenAI API und die Benutzeroberfläche des Assistenten
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ChatGPTAssistant {
|
|
||||||
constructor() {
|
|
||||||
this.messages = [];
|
|
||||||
this.isOpen = false;
|
|
||||||
this.isLoading = false;
|
|
||||||
this.container = null;
|
|
||||||
this.chatHistory = null;
|
|
||||||
this.inputField = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialisiert den Assistenten und fügt die UI zum DOM hinzu
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
// Assistent-Container erstellen
|
|
||||||
this.createAssistantUI();
|
|
||||||
|
|
||||||
// Event-Listener hinzufügen
|
|
||||||
this.setupEventListeners();
|
|
||||||
|
|
||||||
// Ersten Willkommensnachricht anzeigen
|
|
||||||
this.addMessage("assistant", "Frage den KI-Assistenten");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Erstellt die UI-Elemente für den Assistenten
|
|
||||||
*/
|
|
||||||
createAssistantUI() {
|
|
||||||
// Hauptcontainer erstellen
|
|
||||||
this.container = document.createElement('div');
|
|
||||||
this.container.id = 'chatgpt-assistant';
|
|
||||||
this.container.className = 'fixed bottom-4 right-4 z-50 flex flex-col';
|
|
||||||
|
|
||||||
// Button zum Öffnen/Schließen des Assistenten
|
|
||||||
const toggleButton = document.createElement('button');
|
|
||||||
toggleButton.id = 'assistant-toggle';
|
|
||||||
toggleButton.className = 'ml-auto bg-primary-600 hover:bg-primary-700 text-white rounded-full p-3 shadow-lg transition-all duration-300 mb-2';
|
|
||||||
toggleButton.innerHTML = '<i class="fas fa-robot text-xl"></i>';
|
|
||||||
|
|
||||||
// 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';
|
|
||||||
|
|
||||||
// Chat-Header
|
|
||||||
const header = document.createElement('div');
|
|
||||||
header.className = 'bg-primary-600 text-white p-3 flex items-center justify-between';
|
|
||||||
header.innerHTML = `
|
|
||||||
<div class="flex items-center">
|
|
||||||
<i class="fas fa-robot mr-2"></i>
|
|
||||||
<span>KI-Assistent</span>
|
|
||||||
</div>
|
|
||||||
<button id="assistant-close" class="text-white hover:text-gray-200">
|
|
||||||
<i class="fas fa-times"></i>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 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';
|
|
||||||
|
|
||||||
// Chat-Eingabe
|
|
||||||
const inputContainer = document.createElement('div');
|
|
||||||
inputContainer.className = 'border-t border-gray-200 dark:border-dark-600 p-3 flex items-center';
|
|
||||||
|
|
||||||
this.inputField = document.createElement('input');
|
|
||||||
this.inputField.type = 'text';
|
|
||||||
this.inputField.placeholder = 'Frage den KI-Assistenten';
|
|
||||||
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');
|
|
||||||
sendButton.id = 'assistant-send';
|
|
||||||
sendButton.className = 'bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded-r-lg';
|
|
||||||
sendButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
|
|
||||||
|
|
||||||
// Elemente zusammenfügen
|
|
||||||
inputContainer.appendChild(this.inputField);
|
|
||||||
inputContainer.appendChild(sendButton);
|
|
||||||
|
|
||||||
chatContainer.appendChild(header);
|
|
||||||
chatContainer.appendChild(this.chatHistory);
|
|
||||||
chatContainer.appendChild(inputContainer);
|
|
||||||
|
|
||||||
this.container.appendChild(toggleButton);
|
|
||||||
this.container.appendChild(chatContainer);
|
|
||||||
|
|
||||||
// Zum DOM hinzufügen
|
|
||||||
document.body.appendChild(this.container);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Richtet Event-Listener für die Benutzeroberfläche ein
|
|
||||||
*/
|
|
||||||
setupEventListeners() {
|
|
||||||
// Toggle-Button
|
|
||||||
const toggleButton = document.getElementById('assistant-toggle');
|
|
||||||
toggleButton.addEventListener('click', () => this.toggleAssistant());
|
|
||||||
|
|
||||||
// Schließen-Button
|
|
||||||
const closeButton = document.getElementById('assistant-close');
|
|
||||||
closeButton.addEventListener('click', () => this.toggleAssistant(false));
|
|
||||||
|
|
||||||
// Senden-Button
|
|
||||||
const sendButton = document.getElementById('assistant-send');
|
|
||||||
sendButton.addEventListener('click', () => this.sendMessage());
|
|
||||||
|
|
||||||
// Enter-Taste im Eingabefeld
|
|
||||||
this.inputField.addEventListener('keyup', (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
this.sendMessage();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Öffnet oder schließt den Assistenten
|
|
||||||
* @param {boolean} state - Optional: erzwingt einen bestimmten Zustand
|
|
||||||
*/
|
|
||||||
toggleAssistant(state = null) {
|
|
||||||
const chatContainer = document.getElementById('assistant-chat');
|
|
||||||
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();
|
|
||||||
} else {
|
|
||||||
chatContainer.classList.remove('max-h-96', 'opacity-100');
|
|
||||||
chatContainer.classList.add('max-h-0', 'opacity-0');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fügt eine Nachricht zum Chat-Verlauf hinzu
|
|
||||||
* @param {string} sender - 'user' oder 'assistant'
|
|
||||||
* @param {string} text - Nachrichtentext
|
|
||||||
*/
|
|
||||||
addMessage(sender, text) {
|
|
||||||
// Nachricht zum Verlauf hinzufügen
|
|
||||||
this.messages.push({ role: sender, content: text });
|
|
||||||
|
|
||||||
// DOM-Element erstellen
|
|
||||||
const messageEl = document.createElement('div');
|
|
||||||
messageEl.className = `flex ${sender === 'user' ? 'justify-end' : 'justify-start'}`;
|
|
||||||
|
|
||||||
const bubble = document.createElement('div');
|
|
||||||
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;
|
|
||||||
|
|
||||||
messageEl.appendChild(bubble);
|
|
||||||
this.chatHistory.appendChild(messageEl);
|
|
||||||
|
|
||||||
// Scroll zum Ende des Verlaufs
|
|
||||||
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sendet die Benutzernachricht an den Server und zeigt die Antwort an
|
|
||||||
*/
|
|
||||||
async sendMessage() {
|
|
||||||
const userInput = this.inputField.value.trim();
|
|
||||||
if (!userInput || this.isLoading) return;
|
|
||||||
|
|
||||||
// Benutzernachricht anzeigen
|
|
||||||
this.addMessage('user', userInput);
|
|
||||||
|
|
||||||
// Eingabefeld zurücksetzen
|
|
||||||
this.inputField.value = '';
|
|
||||||
|
|
||||||
// Ladeindikator anzeigen
|
|
||||||
this.isLoading = true;
|
|
||||||
this.showLoadingIndicator();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Anfrage an den Server senden
|
|
||||||
const response = await fetch('/api/assistant', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
messages: this.messages
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Netzwerkfehler oder Serverproblem');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Ladeindikator entfernen
|
|
||||||
this.removeLoadingIndicator();
|
|
||||||
|
|
||||||
// Antwort anzeigen
|
|
||||||
this.addMessage('assistant', data.response);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler bei der Kommunikation mit dem Assistenten:', error);
|
|
||||||
|
|
||||||
// Ladeindikator entfernen
|
|
||||||
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.');
|
|
||||||
} finally {
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zeigt einen Ladeindikator im Chat an
|
|
||||||
*/
|
|
||||||
showLoadingIndicator() {
|
|
||||||
const loadingEl = document.createElement('div');
|
|
||||||
loadingEl.id = 'assistant-loading';
|
|
||||||
loadingEl.className = 'flex justify-start';
|
|
||||||
|
|
||||||
const bubble = document.createElement('div');
|
|
||||||
bubble.className = 'bg-gray-100 dark:bg-dark-700 text-gray-800 dark:text-white rounded-lg py-2 px-3';
|
|
||||||
bubble.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
|
||||||
|
|
||||||
loadingEl.appendChild(bubble);
|
|
||||||
this.chatHistory.appendChild(loadingEl);
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exportiere die Klasse für die Verwendung in anderen Modulen
|
|
||||||
export default ChatGPTAssistant;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user