Compare commits

..

49 Commits

Author SHA1 Message Date
a7bb9563b3 hintergrund Fixes 2025-04-29 20:49:43 +01:00
4e0c470663 Update compiled Python files in __pycache__ for utils module 2025-04-29 20:38:00 +01:00
b5300f74bd Update compiled Python files in __pycache__ for models and utils modules 2025-04-29 20:35:23 +01:00
6a53e621ca ''Über Uns'' 2025-04-29 20:31:02 +01:00
a59ce652af Impressum 2025-04-29 19:26:35 +01:00
27cfc95081 Merge branch 'tills-branch' of https://git.clickcandit.com/marwinm/website into tills-branch 2025-04-29 20:24:41 +02:00
c513666391 Refactor nodeCount to config object" Explanation: This commit message follows the Conventional C... 2025-04-29 20:11:43 +02:00
ae30dbce57 Flask Fix JAHAH 2025-04-29 19:08:49 +01:00
817ddd98e9 chore: Änderungen commited 2025-04-29 20:08:12 +02:00
bfce2fc7b7 chore: Änderungen commited 2025-04-29 20:06:29 +02:00
efbcd567ee chore: Änderungen commited 2025-04-29 20:04:32 +02:00
a873765d08 feat: Implement smooth animation with adjustable pulse and flow speeds 2025-04-29 20:02:51 +02:00
efbcadb95a chore: Änderungen commited 2025-04-29 20:00:58 +02:00
da3ccaffe9 Refactor nodeCount configuration in NeuralNetworkBackground.js 2025-04-29 19:59:09 +02:00
f4e04573bd chore: Änderungen commited 2025-04-29 19:57:44 +02:00
aa253f3871 chore: Änderungen commited 2025-04-29 19:27:29 +02:00
cfd6a25b21 📝 Commit Message: "Refactor app module binary files using Conventional Commits (feat)" Explanat... 2025-04-29 19:25:18 +02:00
d307763007 Aktualisiere die Konfiguration der neuronalen Netzwerk-Hintergrundanimation in neural-network-background.js, um die Anzahl der Knoten, Variationen, Geschwindigkeiten und Sichtbarkeiten anzupassen. Füge neue Parameter für aktive Flows und Flussweiterleitung hinzu, um die Animation dynamischer zu gestalten. Optimiere die Logik zur Verwaltung aktiver Flows und verbessere die Sichtbarkeit von Verbindungen für ein flüssigeres Benutzererlebnis. 2025-04-29 15:19:33 +02:00
d7e6912e08 Aktualisiere die Umgebungsvariablen in .env mit neuen Werten für SECRET_KEY und OPENAI_API_KEY. Entferne die alte Instanz von systades.db und aktualisiere die neuronale Netzwerk-Hintergrundanimation in neural-network-background.js mit verbesserten visuellen Effekten, einschließlich einer neuen Zickzack-Bewegung für Blitze und subtileren Glüheffekten. Implementiere eine sanftere Ausblendanimation für das Canvas-Element und verbessere die Funkenanimationen. Optimiere die Cluster-Generierung und -Verteilung für eine flüssigere Benutzererfahrung. 2025-04-29 14:58:54 +02:00
ffe96074f4 Verbessere die Funktionalität des neuronalen Netzwerk-Hintergrunds in neural-network-background.js durch die Einführung einer flexiblen Konfigurationsstruktur, die benutzerdefinierte Optionen unterstützt. Optimiere die Cluster-Generierung und -Verteilung, verbessere die Verbindungslogik zwischen Knoten und implementiere erweiterte Fehlerbehandlung in den API-Funktionen in mindmap.js. Füge Fallback-Knoten hinzu, um die Benutzererfahrung zu verbessern, und implementiere visuelle Rückmeldungen für die Initialisierung der Mindmap. 2025-04-29 12:32:18 +02:00
49ccf3908a Merge remote-tracking branch 'origin/tills-branch' into tills-branch 2025-04-29 10:28:36 +02:00
9514645904 keine ahnung ehrlich 2025-04-29 10:28:15 +02:00
63f45abb3e background, mindmap 2025-04-29 10:27:07 +02:00
7d74b5a7bf Implement Flask-Migrate for database migrations in app.py, disable CSRF protection, and update requirements.txt to include Flask-Migrate. Remove obsolete systades.db file and add migration configuration files for Alembic. 2025-04-28 21:41:38 +02:00
55f2553780 Update ROADMAP.md, remove CORS & flask-cors from app.py, and update requirements.txt: no longer use CORS for Flask-SocketIO. 2025-04-28 21:24:11 +02:00
0852ea070b Add Flask-CORS and SocketIO for real-time updates, refactor database handling to use a temporary Flask app; improve error handling with @app.errorhandler decorators. 2025-04-28 15:21:11 +02:00
7a0533ac09 Verbessere die Funktionalität des Chat-Assistenten in app.py: Aktualisiere die Systemnachricht mit spezifischen Informationen zur Systades-Wissensdatenbank und erweitere die API-Nachrichtenformatierung. Füge Unterstützung für ausgewählte Elemente aus der Datenbank hinzu und erhöhe die maximale Tokenanzahl für detailliertere Antworten. Implementiere eine neue JavaScript-Datei für eine neuronale Netzwerk-Hintergrundanimation und verbessere die CSS-Stile für den Light Mode. Optimiere die Benutzeroberfläche und die Lesbarkeit in beiden Modi. Aktualisiere die Grundstile für eine konsistente Darstellung. 2025-04-28 14:49:02 +02:00
65c44ab371 Refactor node relationship handling in app.py and introduce new routes for thoughts association with nodes. 2025-04-28 13:20:41 +02:00
5399169b11 jo 2025-04-27 21:22:28 +01:00
05f6f149ad Update ROADMAP.md: Mark completion of Phases 1, 2, and 3 with detailed task lists, introduce Phase 4 for user-defined mind maps, and outline future tasks including tagging and source management. Enhance visual design and UX elements, and update current improvements and future tasks for Q3 2024. 2025-04-27 18:19:43 +02:00
12ed413c04 Update README.md: Enhance error handling for robust data processing and node references, improve connection detection between nodes, and update current status to reflect 75% completion of Phase 3. Document critical bug fixes in node visualization and API data format handling. 2025-04-27 18:18:34 +02:00
ccf5a1d678 Update README.md: Add WebGL support for animated background effects, complete Phase 2 design tasks, and enhance MindMap features with improved clustering and visualization. Update current status and next steps to reflect recent progress. 2025-04-27 18:17:52 +02:00
eb23d638e6 Merge remote-tracking branch 'origin/tills-branch' into tills-branch 2025-04-27 18:16:30 +02:00
750dba2d6f Update requirements.txt: Remove specific version for openai package to allow for flexibility in dependency management. 2025-04-27 18:10:41 +01:00
6aa3780a96 Enhance MindMapVisualization functionality: Implement connection count updates for nodes upon data processing and fallback scenarios. Refactor connected node retrieval methods to improve safety and reliability by ensuring valid node IDs. Introduce a new method for checking links between nodes, enhancing overall robustness of the mind map visualization logic. 2025-04-27 18:16:09 +02:00
8890a62026 Merge remote-tracking branch 'origin/tills-branch' into tills-branch 2025-04-27 17:57:40 +02:00
6cf9b2a627 Update compiled Python files in __pycache__: Refresh app.cpython-313.pyc and models.cpython-313.pyc to reflect recent code changes and optimizations. 2025-04-27 17:14:32 +01:00
4f6aea8e20 Update neural network background animation: Enhance color visibility and node dynamics for improved visual effects. Introduce clustering for node positioning and optimize connection rendering with increased opacity and strength. Remove old database file and update systades.db for better data management. 2025-04-27 17:57:04 +02:00
e5f485d9d7 Update COMMON_ERRORS.md: Add a new entry for common error 'D' and maintain clarity in the documentation of frequent issues and their solutions. 2025-04-27 17:15:54 +02:00
cf3fc09a63 Merge remote-tracking branch 'origin/tills-branch' into tills-branch 2025-04-27 17:15:47 +02:00
10747a8336 Add new database file: Create systades.db to support application data storage and management. 2025-04-27 17:00:27 +01:00
7eb958f3c8 Update app.py and COMMON_ERRORS.md for improved clarity and functionality: Correct the comment in app.py from "Kontext-Prozessor" to "Context-Prozessor" for better understanding. Enhance COMMON_ERRORS.md by adding new common errors and solutions related to TypeScript usage, OAuth implementation, and neural network background animation issues. Update mindmap page scripts to ensure proper global availability of functions and improve error handling for user notifications. Adjust template references for Tailwind CSS and Alpine.js to support both CDN and local versions, ensuring better resource loading and compatibility. 2025-04-27 17:14:48 +02:00
4a3092a4d2 Update OpenAI API key and enhance app functionality: Replace the OpenAI API key in the .env file for improved access. Refactor app.py to include error handling for missing API keys and implement dark mode functionality with session management. Update README.md to reflect the use of Tailwind CSS via CDN and document the Content Security Policy (CSP) adjustments. Enhance mindmap data loading with a new API endpoint for refreshing data, ensuring better user experience during database connection issues. Update styles and templates for improved UI consistency and responsiveness. 2025-04-27 16:56:16 +02:00
2d8cdc052f Refactor chat interface in index.html and main.js: Improve user interaction by optimizing chat message rendering and enhancing initialization logic for the ChatGPT assistant. Update styles for better responsiveness and visual appeal, ensuring seamless integration with existing functionalities. 2025-04-27 15:12:52 +02:00
968515ce2b Overhaul website to modernize design, integrate SVG visualizations, and enhance KI functionality; update documentation for MindMapProjekt. 2025-04-27 15:09:29 +02:00
88f8e98df0 Enhance Neural Network Background Animation: Introduce subtle flowing network aesthetics with improved color schemes and animations. Update canvas handling to ensure proper layering and visibility. Implement new flow animations along connections for a more dynamic visual experience. Adjust node and connection properties for a cleaner, more organic look. Ensure compatibility with dark mode and optimize rendering methods for both WebGL and Canvas. Update styles to maintain transparency across themes. 2025-04-27 12:16:57 +01:00
e5409eef68 Remove unused background scripts and assets: Delete background.js, network-animation.js, network-background.js, and associated media files to streamline the project. Update base.html to reflect changes in script references and ensure proper functionality of the dark mode theme. 2025-04-27 11:51:35 +01:00
013bf76446 Merge branch 'tills-branch' of https://git.clickcandit.com/marwinm/website into tills-branch 2025-04-27 11:15:48 +01:00
808a3c7bbe Update compiled Python files and database: Refresh app and init_db bytecode files, and update mindmap database to reflect recent changes and improvements. 2025-04-27 10:14:44 +01:00
5319 changed files with 854586 additions and 4783 deletions

View 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

View 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.)

View 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)

View 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)

View 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

View 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

View 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
View 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

2
.env Normal file
View File

@@ -0,0 +1,2 @@
SECRET_KEY=eed9298856dc9363cd32778265780d6904ba24e6a6b815a2cc382bcdd767ea7b
OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier

8
.vscode/jsconfig.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "esnext",
"lib": [
"esnext"
]
}
}

68
.vscode/main.js vendored Normal file
View File

@@ -0,0 +1,68 @@
/// <reference types="vscode" />
// @ts-check
// API: https://code.visualstudio.com/api/references/vscode-api
// @ts-ignore
const vscode = require('vscode');
* @typedef {import('vscode').ExtensionContext} ExtensionContext
* @typedef {import('vscode').commands} commands
* @typedef {import('vscode').window} window
* @typedef {import('vscode').TextEditor} TextEditor
* @typedef {import('vscode').TextDocument} TextDocument
*/
/**
* Aktiviert die Erweiterung und registriert den Auto-Resume-Befehl
* @param {vscode.ExtensionContext} context - Der Erweiterungskontext
*/
function activate(context) {
const disposable = vscode.commands.registerCommand('extension.autoResume', () => {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const document = editor.document;
const text = document.getText();
// Track last click time to avoid multiple clicks
let lastClickTime = 0;
// Main function that looks for and clicks the resume link
function clickResumeLink() {
// Prevent clicking too frequently (3 second cooldown)
const now = Date.now();
if (now - lastClickTime < 3000) return;
// Check if text contains rate limit text
if (text.includes('stop the agent after 25 tool calls') ||
text.includes('Note: we default stop')) {
// Find the resume link position
const resumePos = text.indexOf('resume the conversation');
if (resumePos !== -1) {
vscode.window.showInformationMessage('Auto-resuming conversation...');
lastClickTime = now;
}
}
}
// Führe periodisch aus
const interval = global.setInterval(clickResumeLink, 1000);
// Speichere das Intervall in den Subscriptions
context.subscriptions.push({
dispose: () => global.clearInterval(interval)
});
// Führe die Funktion sofort aus
clickResumeLink();
});
context.subscriptions.push(disposable);
}
function deactivate() {
// Cleanup if needed
}
module.exports = {
activate,
deactivate
}

239
COMMON_ERRORS.md Normal file
View File

@@ -0,0 +1,239 @@
# 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
- Verwendung von TypeScript anstelle von JavaScript
- Implementierung von OAuth oder anderen externen Authentifizierungsmethoden
# 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
- Falsche Einbindung der Neural Network Background Animation
- Verwendung von englischen Variablennamen in deutschen Funktionen
- Vergessen der Mindmap-Datenstruktur gemäß der Roadmap
# Häufige Fehler und Lösungen
## Datenbankfehler
### Fehler: "no such column: user.password"
**Fehlerbeschreibung:**
```
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: user.password
[SQL: SELECT user.id AS user_id, user.username AS user_username, user.email AS user_email, user.password AS user_password, user.created_at AS user_created_at, user.is_active AS user_is_active, user.role AS user_role
FROM user
WHERE user.id = ?]
```
**Ursache:**
Die Spalte `password` fehlt in der Tabelle `user` der SQLite-Datenbank. Dies kann durch eine unvollständige Datenbankinitialisierung oder ein fehlerhaftes Schema-Update verursacht worden sein.
**Lösung:**
1. **Datenbank reparieren mit dem Fix-Skript**
```bash
python fix_user_table.py
```
Dieses Skript:
- Prüft, ob die Tabelle `user` existiert und erstellt sie, falls nicht
- Prüft, ob die Spalte `password` existiert und fügt sie hinzu, falls nicht
2. **Standardbenutzer erstellen**
```bash
python create_default_users.py
```
Dieses Skript:
- Erstellt Standardbenutzer (admin, user), falls keine vorhanden sind
- Setzt Passwörter mit korrektem Hashing
3. **Datenbank testen**
```bash
python test_app.py
```
Dieses Skript überprüft:
- Ob die Datenbank existiert
- Ob die Tabelle `user` korrekt konfiguriert ist
- Ob Benutzer vorhanden sind
## Häufige Ursachen für Datenbankfehler
1. **Inkonsistente Datenbankschemas**
- Unterschiede zwischen dem SQLAlchemy-Modell und der tatsächlichen Datenbankstruktur
- Fehlende Spalten, die in den Modellen definiert sind
2. **Falsche Datenbankinitialisierung**
- Die Datenbank wurde nicht korrekt initialisiert
- Fehler bei der Migration oder dem Schema-Update
3. **Datenbankdatei-Korrumpierung**
- Die SQLite-Datenbankdatei wurde beschädigt
- Lösung: Sicherung wiederherstellen oder Datenbank neu erstellen
## Vorbeugende Maßnahmen
1. **Regelmäßige Backups**
- Tägliche Sicherung der Datenbankdatei
2. **Schema-Validierung**
- Regelmäßige Überprüfung der Datenbankstruktur
- Automatisierte Tests für Datenbankschema
3. **Datenbankmigration**
- Verwenden Sie Flask-Migrate für strukturierte Datenbank-Updates
- Dokumentieren Sie alle Schemaänderungen
## 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: CSP-Fehler (script-src-elem 'none', font-src 'none', etc.)
**Fehler:** Skripte werden aufgrund von CSP-Einschränkungen blockiert.
**Lösung:**
1. WICHTIG: CSP-Einschränkungen NICHT implementieren! Diese sind im Projekt unerwünscht.
2. Falls CSP-Header bereits vorhanden sind, entfernen Sie diese aus der Anwendung:
```python
# Entfernen Sie diesen Code, falls vorhanden:
# @app.after_request
# def add_security_headers(response):
# response.headers['Content-Security-Policy'] = '...'
# return response
```
3. Stellen Sie sicher, dass externe Ressourcen direkt geladen werden:
```html
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="{{ url_for('static', filename='js/alpine.min.js') }}" defer></script>
```
4. Für Inline-Skripte keine Nonce verwenden:
```html
<script>
// Inline-JavaScript-Code ohne Einschränkungen
</script>
```
### 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.
## 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`
## Neural Network Background
### Problem: Hintergrund-Animation wird nicht angezeigt
**Fehler:** Die Neural Network Animation im Hintergrund erscheint nicht.
**Lösung:**
1. Überprüfen Sie, ob die Datei `static/neural-network-background.js` korrekt eingebunden ist:
```html
<script src="{{ url_for('static', filename='neural-network-background.js') }}"></script>
```
2. Initialisieren Sie die Animation im Template:
```html
<script>
document.addEventListener('DOMContentLoaded', () => {
const background = new NeuralNetworkBackground();
background.initialize();
background.animate();
});
</script>
```
3. Stellen Sie sicher, dass keine CSS-Regeln die Animation überdecken:
```css
#neural-network-background {
z-index: -10;
opacity: 1;
}
```
## Mindmap-Funktionalität
### Problem: Mindmap-Daten werden nicht geladen
**Fehler:** Die dynamische Mindmap zeigt keine Daten an.
**Lösung:**
1. Überprüfen Sie die API-Endpunkte für die Mindmap-Daten:
```python
@app.route('/api/mindmap/nodes', methods=['GET'])
def get_mindmap_nodes():
# Implementierung...
```
2. Stellen Sie sicher, dass die AJAX-Anfragen korrekt implementiert sind:
```javascript
fetch('/api/mindmap/nodes')
.then(response => response.json())
.then(data => {
// Verarbeitung der Mindmap-Daten
});
```
3. Überprüfen Sie die Datenbankeinträge für Mindmap-Knoten und -Verbindungen.
## ChatGPT-Assistent
### Problem: Assistent reagiert nicht auf Eingaben
**Fehler:** Der ChatGPT-Assistent verarbeitet keine Benutzereingaben.
**Lösung:**
1. Überprüfen Sie die Einbindung der JavaScript-Datei:
```html
<script src="{{ url_for('static', filename='js/modules/chatgpt-assistant.js') }}"></script>
```
2. Stellen Sie sicher, dass der Assistent korrekt initialisiert wird:
```javascript
document.addEventListener('DOMContentLoaded', () => {
const assistant = new ChatGPTAssistant();
assistant.initialize();
});
```
3. Überprüfen Sie die API-Endpunkte für die Kommunikation mit dem Assistenten.

View File

@@ -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"]

272
README.md
View File

@@ -1,94 +1,196 @@
# 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
- WebGL für animierte Hintergrundeffekte
- **Datenbank**: SQLite mit SQLAlchemy
- **KI-Integration**: OpenAI API für intelligente Assistenz
- Interaktive Mindmap zur Visualisierung von Wissensverbindungen
- 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 und Verwendung
## 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:
```
python setup.py
#### 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
- [x] Gestaltung der Landing Page mit großer Typografie
- [x] Animierter Neurales Netzwerk-Hintergrund mit WebGL
### 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
- [x] Clustertopologie für neuronale Netzwerkdarstellung
- [x] Fehlerbehandlung für robuste Datenverarbeitung und Knotenverweise
- [x] Verbesserte Verbindungserkennung zwischen 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**: ✅ Abgeschlossen
- **Phase 3**: 🔄 In Bearbeitung (75% 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
- Animierter neuronaler Netzwerk-Hintergrund mit WebGL implementiert
- Verbesserte neuronale Cluster-Darstellung in der MindMap-Ansicht
- Behebung von kritischen Fehlern in der Knotenvisualisierung und Verbindungserkennung
- Robustere Datenverarbeitung für Mindmap-Knoten implementiert
- Fehlerbehandlung für verschiedene API-Datenformate verbessert
## Neuronaler Netzwerk-Hintergrund
Ein wesentliches neues Feature ist der animierte Hintergrund, der ein neuronales Netzwerk simuliert:
- **WebGL-basierte Rendering-Engine** für hohe Performance
- **Dynamische Knoten und Verbindungen** mit realistischem Bewegungsverhalten
- **Neuronenfeuer-Simulation** mit Signalweiterleitung zwischen Knoten
- **Clustertopologie** für realistisches Erscheinungsbild
- **Anpassbare Farbgebung und Animationsparameter**
- **Flüssige Animationen** mit über 100 Knotenpunkten
Die Animation ist vollständig responsiv und passt sich automatisch an verschiedene Bildschirmgrößen an, ohne die Browser-Performance zu beeinträchtigen.
## Mindmap-Verbesserungen
Die Mindmap-Darstellung wurde grundlegend überarbeitet:
- **D3.js Force-Directed Graph** für intuitive Knotenpositionierung
- **Verbesserte Fehlerbehandlung** für robustere Datenverarbeitung
- **Neuronale Cluster-Gruppierung** von thematisch zusammengehörigen Inhalten
- **Glasmorphismus-Effekte** für moderne visuelle Darstellung
- **Verbesserte Hover- und Selektionseffekte**
- **Flüssige Animationen** bei Knotenauswahl und -fokussierung
## Nächste Schritte
- Fertigstellung des Tagging-Systems für Gedanken
- Verbesserung der Gedankenansicht im Mindmap-Bereich
- Implementierung von Quellenmanagement
- Überarbeitung der Startseite mit neuen Features
## 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)
- WebGL-Animation (neural-network-background.js)
### 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.
### 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.
*Zuletzt aktualisiert: 15.06.2024*

172
ROADMAP.md Normal file
View File

@@ -0,0 +1,172 @@
# Systades Mindmap - Entwicklungs-Roadmap
Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorientierten Mindmap-Funktionalität für das Systades-Projekt.
## Phase 1: Grundlegendes Datenmodell und Backend (Abgeschlossen ✅)
- [x] Entwurf des Datenbankschemas für benutzerorientierte Mindmaps
- [x] Implementierung der Modelle in models.py
- [x] Erstellung der API-Endpunkte für CRUD-Operationen
- [x] Integration mit der bestehenden Benutzerauthentifizierung
- [x] Seed-Daten für die Entwicklung und Tests
## Phase 2: Dynamische Mindmap-Visualisierung (Abgeschlossen ✅)
- [x] Anpassung des Frontend-Codes zur Verwendung der DB-Daten anstelle des SVG
- [x] Implementierung von AJAX-Anfragen zum Laden der Mindmap-Daten
- [x] Dynamisches Rendering der Knoten, Verbindungen und Labels
- [x] Drag-and-Drop-Funktionalität für die Bewegung von Knoten
- [x] Zoom- und Pan-Funktionalität mit Persistenz der Ansicht
- [x] Verbesserte Fehlerbehandlung in der Knotenvisualisierung
- [x] Robustere Verbindungserkennung zwischen Knoten
- [x] Implementierung von Glasmorphismus-Effekten für moderneres UI
## Phase 3: Visuelles Design und UX (Abgeschlossen ✅)
- [x] Implementierung des Dark Mode
- [x] Entwicklung eines modernen, minimalistischen UI
- [x] Animierter neuronaler Netzwerk-Hintergrund mit WebGL
- [x] Responsive Design für alle Geräte
- [x] Verbesserte Hover- und Selektionseffekte
- [x] Clustertopologie für neuronale Netzwerkdarstellung
- [x] Animierte Neuronenfeuer-Simulation mit Signalweiterleitung
## Phase 4: Benutzerdefinierte Mindmaps (Aktuell 🔄)
- [x] UI für das Betrachten bestehender Mindmaps
- [ ] UI für das Erstellen und Bearbeiten eigener Mindmaps
- [ ] Funktion zum Hinzufügen/Entfernen von Knoten aus der öffentlichen Mindmap
- [ ] Speichern der Knotenpositionen und Ansichtseinstellungen
- [ ] Benutzerspezifische Visualisierungseinstellungen
- [ ] Dashboard mit Übersicht aller Mindmaps des Benutzers
## Phase 5: Notizen und Annotationen
- [x] Anzeige von Gedanken zu Mindmap-Knoten
- [ ] UI für das Hinzufügen privater Notizen zu Knoten
- [ ] Visuelle Anzeige von Notizen in der Mindmap
- [ ] Texteditor mit Markdown-Unterstützung für Notizen
- [ ] Kategorisierung und Farbkodierung von Notizen
- [ ] Suchfunktion für Notizen
## Phase 6: Tagging und Quellenmanagement
- [ ] Tagging-System für Inhalte implementieren
- [ ] Verknüpfen von Quellen mit Mindmap-Knoten
- [ ] Upload-Funktionalität für Dateien und Medien
- [ ] Verwaltung von Zitaten und Referenzen
- [ ] Visuelles Feedback für Tags und Quellen in der Mindmap
## Phase 7: Integrationen und Erweiterungen
- [ ] Import/Export-Funktionalität für Mindmaps (JSON, PNG)
- [ ] Teilen von Mindmaps (öffentlich/privat/mit bestimmten Benutzern)
- [ ] Kollaborative Bearbeitung von Mindmaps
- [ ] Verknüpfung mit externen Ressourcen (Links, Dateien)
- [ ] Versionierung von Mindmaps
## Phase 8: KI-Integration und Analyse
- [ ] KI-gestützte Vorschläge für Verbindungen zwischen Knoten
- [ ] Automatische Kategorisierung von Inhalten
- [ ] Visualisierung von Beziehungsstärken und -typen
- [ ] Mindmap-Statistiken und Analysen
- [ ] KI-basierte Zusammenfassung von Teilbereichen der Mindmap
## Phase 9: Optimierung und Skalierung
- [ ] Performance-Optimierung für große Mindmaps
- [ ] Verbesserung der Benutzerfreundlichkeit basierend auf Feedback
- [ ] Erweiterte Such- und Filterfunktionen
- [ ] Mobile Optimierung
- [ ] Offline-Funktionalität mit Synchronisierung
## Technische Schulden und Refactoring
- [ ] Trennung der Datenbank-Logik vom Flask-App-Code
- [ ] Einführung von Unit-Tests und Integration-Tests
- [ ] Überarbeitung der API-Dokumentation
- [ ] Caching-Strategien für bessere Performance
- [ ] 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
### Datenbankschema
Das Datenbankschema umfasst folgende Hauptentitäten:
1. **Category** - Wissenschaftliche Kategorien für die öffentliche Mindmap
2. **MindMapNode** - Öffentliche Mindmap-Knoten mit Metadaten
3. **UserMindmap** - Benutzerdefinierte Mindmaps
4. **UserMindmapNode** - Verknüpfung zwischen Benutzermindmaps und öffentlichen Knoten
5. **MindmapNote** - Benutzerspezifische Notizen
6. **Thought** - Gedanken und Inhalte, die Knoten zugeordnet sind
7. **ThoughtRelation** - Beziehungen zwischen Gedanken
### Frontend-Technologien
- D3.js für die Visualisierung der Mindmap
- WebGL für den neuronalen Netzwerk-Hintergrund
- AJAX für dynamisches Laden von Daten
- Interaktive Bedienelemente mit JavaScript
- Responsive Design mit Tailwind CSS
### Backend-APIs
Die implementierten API-Endpunkte umfassen:
- `/api/mindmap/public` - Abrufen der öffentlichen Mindmap-Struktur
- `/api/mindmap/user/<id>` - Abrufen benutzerdefinierter Mindmaps
- `/api/mindmap/<id>/add_node` - Hinzufügen eines Knotens zur Benutzer-Mindmap
- `/api/mindmap/<id>/remove_node/<node_id>` - Entfernen eines Knotens
- `/api/mindmap/<id>/update_node_position` - Aktualisierung von Knotenpositionen
- `/api/mindmap/<id>/notes` - Verwaltung von Notizen
- `/api/nodes/<id>/thoughts` - Abrufen und Hinzufügen von Gedanken zu Knoten
- `/api/get_dark_mode` - Abrufen der Dark Mode Einstellung
## Neuronaler Netzwerk-Hintergrund
Der neue WebGL-basierte Hintergrund bietet:
- WebGL-basierte Rendering-Engine für optimale Performance
- Dynamische Knoten und Verbindungen mit realistischem Verhalten
- Clustering von neuronalen Knoten für natürlicheres Erscheinungsbild
- Simulation von neuronaler Aktivität und Signalweiterleitung
- Anpassbare visuelle Parameter (Helligkeit, Dichte, Geschwindigkeit)
- Vollständig responsives Design für alle Bildschirmgrößen
## Aktuelle Verbesserungen
- Tailwind CSS wurde auf CDN-Version aktualisiert (06.06.2024)
- Content Security Policy (CSP) für Tailwind CSS CDN und WebGL konfiguriert
- Behebung kritischer Fehler in der Mindmap-Knotenvisualisierung (15.06.2024)
- Verbesserte Verbindungserkennung zwischen Knoten implementiert
- Robuste Fehlerbehandlung für verschiedene API-Datenformate
## Zukünftige Aufgaben (Q3 2024)
- Implementierung des Tagging-Systems für Gedanken
- Quellenmanagement für Mindmap-Knoten
- Erweiterte Benutzerprofilfunktionen
- Verbesserung der mobilen Benutzererfahrung
- Integration von Exportfunktionen für Mindmaps
*Zuletzt aktualisiert: 15.06.2024*
## [Entfernt] CORS-Unterstützung (flask-cors)
- Die flask-cors-Bibliothek und alle zugehörigen Initialisierungen wurden entfernt.
- CORS wird nicht mehr unterstützt oder benötigt.

0
website/TOOLS.py → TOOLS.py Executable file → Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

754
website/app.py → app.py Executable file → Normal file
View File

@@ -3,7 +3,7 @@
import os
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_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
@@ -17,12 +17,14 @@ import secrets
from sqlalchemy.sql import func
from openai import OpenAI
from dotenv import load_dotenv
from flask_socketio import SocketIO, emit
from flask_migrate import Migrate
# Modelle importieren
from models import (
db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating,
RelationType, Category, UserMindmap, UserMindmapNode, MindmapNote,
node_thought_association, user_thought_bookmark
node_thought_association, user_thought_bookmark, node_relationship
)
# Lade .env-Datei
@@ -39,9 +41,33 @@ app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', os.path.join(os.getcwd(), 'uploads'))
app.config['WTF_CSRF_ENABLED'] = False
# OpenAI API-Konfiguration
client = OpenAI(api_key="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")
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
@app.context_processor
@@ -51,7 +77,7 @@ def inject_globals():
'current_year': datetime.now().year
}
# Kontext-Prozessor für alle Templates
# Context-Prozessor für alle Templates
@app.context_processor
def inject_current_year():
return {'current_year': datetime.now().year}
@@ -63,6 +89,219 @@ db.init_app(app)
login_manager = LoginManager(app)
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
# SocketIO initialisieren
socketio = SocketIO(app)
migrate = Migrate(app, db)
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
def admin_required(f):
@wraps(f)
@@ -145,14 +384,22 @@ def index():
@app.route('/mindmap')
def mindmap():
"""Zeigt die öffentliche Mindmap an."""
# Sicherstellen, dass wir Kategorien haben
with app.app_context():
try:
# Sicherstellen, dass wir Kategorien haben
if Category.query.count() == 0:
create_default_categories()
# Hole alle Kategorien der obersten Ebene
categories = Category.query.filter_by(parent_id=None).all()
return render_template('mindmap.html', categories=categories)
# Hole alle Kategorien der obersten Ebene
categories = Category.query.filter_by(parent_id=None).all()
# 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
@app.route('/profile')
@@ -160,16 +407,47 @@ def mindmap():
def profile():
# Lade Benutzer-Mindmaps
user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
# Lade Statistiken
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(
user_thought_bookmark.c.user_id == current_user.id
).scalar()
bookmark_count = db.session.query(user_thought_bookmark).filter(
user_thought_bookmark.c.user_id == current_user.id).count()
# 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',
user=current_user,
user_mindmaps=user_mindmaps,
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
@app.route('/settings', methods=['GET', 'POST'])
@@ -202,7 +480,7 @@ def settings():
current_user.set_password(new_password)
db.session.commit()
flash('Passwort erfolgreich aktualisiert!', 'success')
return redirect(url_for('settings'))
return render_template('settings.html')
@@ -240,6 +518,10 @@ def datenschutz():
def agb():
return render_template('agb.html')
@app.route('/ueber-uns/')
def ueber_uns():
return render_template('ueber_uns.html')
# Benutzer-Mindmap-Funktionalität
@app.route('/my-mindmap/<int:mindmap_id>')
@login_required
@@ -328,33 +610,44 @@ def get_public_mindmap():
return jsonify(result)
def build_category_tree(category):
"""Rekursive Funktion zum Aufbau der Kategoriestruktur."""
nodes = []
# Hole alle Knoten in dieser Kategorie
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)
})
"""
Erstellt eine Baumstruktur für eine Kategorie mit all ihren Unterkategorien
und dazugehörigen Knoten
# Rekursiv durch Unterkaterorien
children = []
for child in category.children:
children.append(build_category_tree(child))
return {
Args:
category: Ein Category-Objekt
Returns:
dict: Eine JSON-serialisierbare Darstellung der Kategoriestruktur
"""
# Kategorie-Basisinformationen
category_dict = {
'id': category.id,
'name': category.name,
'description': category.description,
'color_code': category.color_code,
'icon': category.icon,
'nodes': nodes,
'children': children
'nodes': [],
'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>')
@login_required
@@ -623,45 +916,30 @@ def delete_note(note_id):
@app.route('/api/mindmap')
def get_mindmap():
"""API-Endpunkt zur Bereitstellung der Mindmap-Daten in hierarchischer Form."""
# Alle root-Nodes (ohne parent) abrufen
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
if not root_nodes:
# Wenn keine Nodes existieren, rufen wir initialize_database direkt auf
# anstatt create_sample_mindmap zu verwenden
with app.app_context():
initialize_database()
root_nodes = MindMapNode.query.filter_by(parent_id=None).all()
# Ergebnisse in hierarchischer Struktur zurückgeben
# Root-Knoten: Knoten ohne Eltern
root_nodes = MindMapNode.query.\
outerjoin(node_relationship, MindMapNode.id == node_relationship.c.child_id).\
filter(node_relationship.c.parent_id == None).all()
result = []
for node in root_nodes:
node_data = build_node_tree(node)
result.append(node_data)
return jsonify({"nodes": result})
def build_node_tree(node):
"""Erzeugt eine hierarchische Darstellung eines Knotens inkl. seiner Kindknoten."""
# Gedankenzähler abrufen von der many-to-many Beziehung
thought_count = len(node.thoughts)
# Daten für aktuellen Knoten
node_data = {
"id": node.id,
"name": node.name,
"description": f"Knoten mit {thought_count} Gedanken",
"description": node.description or "",
"thought_count": thought_count,
"children": []
}
# Rekursiv Kinder hinzufügen
child_nodes = MindMapNode.query.filter_by(parent_id=node.id).all()
for child_node in child_nodes:
child_data = build_node_tree(child_node)
for child in node.children:
child_data = build_node_tree(child)
node_data["children"].append(child_data)
return node_data
@app.route('/api/nodes/<int:node_id>/thoughts')
@@ -874,17 +1152,21 @@ def bookmark_thought(thought_id):
@app.route('/api/categories')
def get_categories():
"""Liefert alle verfügbaren Kategorien."""
categories = Category.query.all()
return jsonify([{
'id': category.id,
'name': category.name,
'description': category.description,
'color_code': category.color_code,
'icon': category.icon,
'parent_id': category.parent_id
} for category in categories])
"""API-Endpunkt, der alle Kategorien als hierarchische Struktur zurückgibt"""
try:
# Hole alle Kategorien der obersten Ebene
categories = Category.query.filter_by(parent_id=None).all()
# Transformiere zu einer Baumstruktur
category_tree = [build_category_tree(cat) for cat in categories]
return jsonify(category_tree)
except Exception as e:
print(f"Fehler beim Abrufen der Kategorien: {str(e)}")
return jsonify({
'success': False,
'error': 'Kategorien konnten nicht geladen werden'
}), 500
@app.route('/api/set_dark_mode', methods=['POST'])
def set_dark_mode():
@@ -930,7 +1212,7 @@ def too_many_requests(e):
# OpenAI-Integration für KI-Assistenz
@app.route('/api/assistant', methods=['POST'])
def chat_with_assistant():
"""Chatbot-API mit OpenAI Integration."""
"""Chatbot-API mit OpenAI Integration und Datenbankzugriff."""
data = request.json
# Prüfen, ob wir ein einzelnes Prompt oder ein messages-Array haben
@@ -943,9 +1225,18 @@ def chat_with_assistant():
# Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht
system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'),
"Du bist ein hilfreicher Assistent, der Menschen dabei hilft, "
"Wissen zu organisieren und zu verknüpfen. Liefere informative, "
"sachliche und gut strukturierte Antworten.")
"Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
"Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
"Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
"Wichtige Funktionen sind:\n"
"- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
"- Kategorisierung und thematische Organisation\n"
"- Interaktive Mindmaps zur Wissensvisualisierung\n"
"- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
"- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
"Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
"Antworte informativ, sachlich und gut strukturiert auf Deutsch.")
# Formatiere Nachrichten für OpenAI API
api_messages = [{"role": "system", "content": system_message}]
@@ -958,6 +1249,7 @@ def chat_with_assistant():
# Alte Implementierung für direktes Prompt
prompt = data.get('prompt', '')
context = data.get('context', '')
selected_items = data.get('selected_items', []) # Ausgewählte Elemente aus der Datenbank
if not prompt:
return jsonify({
@@ -966,163 +1258,159 @@ def chat_with_assistant():
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
system_message = (
"Du bist ein hilfreicher Assistent, der Menschen dabei hilft, "
"Wissen zu organisieren und zu verknüpfen. Liefere informative, "
"sachliche und gut strukturierte Antworten."
"Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
"Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
"Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
"Wichtige Funktionen sind:\n"
"- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
"- Kategorisierung und thematische Organisation\n"
"- Interaktive Mindmaps zur Wissensvisualisierung\n"
"- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
"- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
"Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
"Antworte informativ, sachlich und gut strukturiert auf Deutsch."
)
if context:
system_message += f"\n\nKontext: {context}"
if selected_items:
system_message += "\n\nAusgewählte Elemente aus der Datenbank:\n"
for item in selected_items:
if 'type' in item and 'data' in item:
if item['type'] == 'thought':
system_message += f"- Gedanke: {item['data'].get('title', 'Unbekannter Titel')}\n"
system_message += f" Zusammenfassung: {item['data'].get('abstract', 'Keine Zusammenfassung')}\n"
system_message += f" Keywords: {item['data'].get('keywords', 'Keine Keywords')}\n"
elif item['type'] == 'category':
system_message += f"- Kategorie: {item['data'].get('name', 'Unbekannte Kategorie')}\n"
system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
system_message += f" Unterkategorien: {item['data'].get('subcategories', 'Keine Unterkategorien')}\n"
elif item['type'] == 'mindmap':
system_message += f"- Mindmap: {item['data'].get('name', 'Unbekannte Mindmap')}\n"
system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
system_message += f" Knoten: {item['data'].get('nodes', 'Keine Knoten')}\n"
api_messages = [
{"role": "system", "content": system_message},
{"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:
# Ü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(
model="gpt-4o-mini",
messages=api_messages,
max_tokens=300,
temperature=0.7
max_tokens=1000, # Erhöht für ausführlichere Antworten und detaillierte Führungen
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
# Für das neue Format erwarten wir response statt answer
return jsonify({
'response': answer
})
except Exception as e:
import traceback
print(f"Fehler bei der OpenAI-Anfrage: {str(e)}")
print(traceback.format_exc())
return jsonify({
'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}'
}), 500
# App-Kontext-Funktion für Initialisierung der Datenbank
def create_default_categories():
"""Erstellt die Standard-Kategorien und wissenschaftlichen Bereiche."""
categories = [
{
'name': 'Naturwissenschaften',
'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'
}
]
}
]
def check_database_query(user_message):
"""
Überprüft, ob die Benutzeranfrage nach Datenbankinformationen sucht und extrahiert
relevante Daten aus der Datenbank.
"""
context = []
# Kategorien in die Datenbank einfügen
for category_data in categories:
children_data = category_data.pop('children', [])
category = Category(**category_data)
db.session.add(category)
db.session.flush() # Um die ID zu generieren
# Prüfen auf verschiedene Datenbankabfragemuster
if any(keyword in user_message.lower() for keyword in ['gedanken', 'thought', 'beitrag', 'inhalt']):
# Suche nach relevanten Gedanken
thoughts = Thought.query.filter(
db.or_(
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
for child_data in children_data:
child = Category(**child_data, parent_id=category.id)
db.session.add(child)
if thoughts:
context.append("Relevante Gedanken:")
for thought in thoughts:
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()
print("Standard-Kategorien wurden erstellt!")
def initialize_database():
"""Initialisiert die Datenbank, falls sie noch nicht existiert."""
db.create_all()
if any(keyword in user_message.lower() for keyword in ['kategorie', 'category', 'themengebiet', 'bereich']):
# 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()
if categories:
context.append("Relevante Kategorien:")
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 Category.query.count() == 0:
create_default_categories()
# Führe die Datenbankinitialisierung beim Starten der App aus
with app.app_context():
initialize_database()
if any(keyword in user_message.lower() for keyword in ['mindmap', 'karte', 'visualisierung']):
# Suche nach öffentlichen Mindmaps
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()
if mindmap_nodes:
context.append("Relevante Mindmap-Knoten:")
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')
def search_thoughts_page():
@@ -1178,4 +1466,70 @@ if __name__ == '__main__':
with app.app_context():
# Make sure tables exist
db.create_all()
app.run(host="0.0.0.0", debug=True)
socketio.run(app, debug=True, host='0.0.0.0')
@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
# Route zur Mindmap HTML-Seite
@app.route('/mindmap')
def mindmap_page():
return render_template('mindmap.html')
# Fehlerbehandlung
@app.errorhandler(404)
def not_found(e):
return jsonify({'error': 'Nicht gefunden'}), 404
@app.errorhandler(400)
def bad_request(e):
return jsonify({'error': 'Fehlerhafte Anfrage'}), 400
@app.errorhandler(500)
def server_error(e):
return jsonify({'error': 'Serverfehler'}), 500

34
check_schema.py Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3
# Verbindung zur Datenbank herstellen
conn = sqlite3.connect('systades.db')
cursor = conn.cursor()
# Liste aller Tabellen abrufen
print("Alle Tabellen in der Datenbank:")
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = cursor.fetchall()
for table in tables:
print(f"- {table[0]}")
# Schema der Datenbank abrufen
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table';")
schemas = cursor.fetchall()
# Schematische Informationen ausgeben
print("\nDatenbankschema:")
for schema in schemas:
print("\n" + str(schema[0]))
# Schema der User-Tabelle genauer untersuchen, falls vorhanden
if ('user',) in tables:
print("\n\nBenutzer-Tabellenschema:")
cursor.execute("PRAGMA table_info(user);")
user_columns = cursor.fetchall()
for column in user_columns:
print(f"Column: {column[1]}, Type: {column[2]}, NOT NULL: {column[3]}, Default: {column[4]}, Primary Key: {column[5]}")
conn.close()

View File

@@ -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

72
create_default_users.py Normal file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3
import os
from werkzeug.security import generate_password_hash
from datetime import datetime
# Prüfen, ob die Datenbank existiert
db_path = 'systades.db'
if not os.path.exists(db_path):
print(f"Datenbank {db_path} existiert nicht.")
exit(1)
# Verbindung zur Datenbank herstellen
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Überprüfen, ob bereits Benutzer vorhanden sind
cursor.execute("SELECT COUNT(*) FROM user;")
user_count = cursor.fetchone()[0]
if user_count == 0:
print("Keine Benutzer in der Datenbank gefunden. Erstelle Standardbenutzer...")
# Standardbenutzer definieren
default_users = [
{
'username': 'admin',
'email': 'admin@example.com',
'password': generate_password_hash('admin'),
'created_at': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
'is_active': 1,
'role': 'admin'
},
{
'username': 'user',
'email': 'user@example.com',
'password': generate_password_hash('user'),
'created_at': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
'is_active': 1,
'role': 'user'
}
]
# Benutzer einfügen
for user in default_users:
cursor.execute("""
INSERT INTO user (username, email, password, created_at, is_active, role)
VALUES (?, ?, ?, ?, ?, ?);
""", (
user['username'],
user['email'],
user['password'],
user['created_at'],
user['is_active'],
user['role']
))
conn.commit()
print(f"{len(default_users)} Standardbenutzer wurden erstellt.")
else:
print(f"Es sind bereits {user_count} Benutzer in der Datenbank vorhanden.")
# Überprüfen der eingefügten Benutzer
print("\nBenutzer in der Datenbank:")
cursor.execute("SELECT id, username, email, role FROM user;")
users = cursor.fetchall()
for user in users:
print(f"ID: {user[0]}, Benutzername: {user[1]}, E-Mail: {user[2]}, Rolle: {user[3]}")
conn.close()
print("\nBenutzeraktualisierung abgeschlossen.")

Binary file not shown.

Binary file not shown.

View File

@@ -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()

View File

@@ -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'"

View File

@@ -1,7 +0,0 @@
version: "3.9"
services:
web:
build: .
ports:
- "5000:5000"
restart: always

70
fix_user_table.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3
import os
# Prüfen, ob die Datenbank existiert
db_path = 'systades.db'
if not os.path.exists(db_path):
print(f"Datenbank {db_path} existiert nicht.")
exit(1)
# Verbindung zur Datenbank herstellen
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Prüfen, ob die User-Tabelle existiert
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user';")
if not cursor.fetchone():
print("Die Tabelle 'user' existiert nicht. Erstelle neue Tabelle...")
cursor.execute('''
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(80) NOT NULL UNIQUE,
email VARCHAR(120) NOT NULL UNIQUE,
password VARCHAR(512) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT 1,
role VARCHAR(20) DEFAULT 'user'
)
''')
conn.commit()
print("Die Tabelle 'user' wurde erfolgreich erstellt.")
else:
# Überprüfen, ob die Spalte 'password' existiert
cursor.execute("PRAGMA table_info(user);")
columns = cursor.fetchall()
column_names = [column[1] for column in columns]
if 'password' not in column_names:
print("Die Spalte 'password' fehlt in der Tabelle 'user'. Füge Spalte hinzu...")
cursor.execute("ALTER TABLE user ADD COLUMN password VARCHAR(512);")
conn.commit()
print("Die Spalte 'password' wurde erfolgreich hinzugefügt.")
else:
print("Die Spalte 'password' existiert bereits in der Tabelle 'user'.")
# Überprüfen der aktualisierten Spaltenstruktur
print("\nAktualisierte Tabellenspalten der 'user'-Tabelle:")
cursor.execute("PRAGMA table_info(user);")
updated_columns = cursor.fetchall()
for column in updated_columns:
print(f"Column: {column[1]}, Type: {column[2]}, NOT NULL: {column[3]}, Default: {column[4]}, Primary Key: {column[5]}")
# Datenbanktabellen anzeigen
print("\nAlle Tabellen in der Datenbank:")
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = cursor.fetchall()
for table in tables:
print(f"- {table[0]}")
# Schemaüberprüfung der user-Tabelle
print("\nSchema der 'user'-Tabelle:")
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='user';")
schema = cursor.fetchone()
if schema:
print(schema[0])
conn.close()
print("\nDatenbankaktualisierung abgeschlossen.")

246
init_db.py Normal file
View File

@@ -0,0 +1,246 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from app import app, initialize_database, db_path
from models import db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating, RelationType
from models import Category, UserMindmap, UserMindmapNode, MindmapNote
import os
import sqlite3
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
# Erstelle eine temporäre Flask-App, um die Datenbank zu initialisieren
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///systades.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
def init_db():
with app.app_context():
print("Initialisiere Datenbank...")
# Tabellen erstellen
db.create_all()
print("Tabellen wurden erstellt.")
# Standardbenutzer erstellen, falls keine vorhanden sind
if User.query.count() == 0:
print("Erstelle Standardbenutzer...")
create_default_users()
# Standardkategorien erstellen, falls keine vorhanden sind
if Category.query.count() == 0:
print("Erstelle Standardkategorien...")
create_default_categories()
# Beispiel-Mindmap erstellen, falls keine Knoten vorhanden sind
if MindMapNode.query.count() == 0:
print("Erstelle Beispiel-Mindmap...")
create_sample_mindmap()
print("Datenbankinitialisierung abgeschlossen.")
def create_default_users():
"""Erstellt Standardbenutzer für die Anwendung"""
users = [
{
'username': 'admin',
'email': 'admin@example.com',
'password': 'admin',
'role': 'admin'
},
{
'username': 'user',
'email': 'user@example.com',
'password': 'user',
'role': 'user'
}
]
for user_data in users:
password = user_data.pop('password')
user = User(**user_data)
user.set_password(password)
db.session.add(user)
db.session.commit()
print(f"{len(users)} Benutzer wurden erstellt.")
def create_default_categories():
"""Erstellt die Standardkategorien für die Mindmap"""
categories = [
{
'name': 'Konzept',
'description': 'Abstrakte Ideen und theoretische Konzepte',
'color_code': '#6366f1',
'icon': 'lightbulb'
},
{
'name': 'Technologie',
'description': 'Hardware, Software, Tools und Plattformen',
'color_code': '#10b981',
'icon': 'cpu'
},
{
'name': 'Prozess',
'description': 'Workflows, Methodologien und Vorgehensweisen',
'color_code': '#f59e0b',
'icon': 'git-branch'
},
{
'name': 'Person',
'description': 'Personen, Teams und Organisationen',
'color_code': '#ec4899',
'icon': 'user'
},
{
'name': 'Dokument',
'description': 'Dokumentationen, Referenzen und Ressourcen',
'color_code': '#3b82f6',
'icon': 'file-text'
}
]
for cat_data in categories:
category = Category(**cat_data)
db.session.add(category)
db.session.commit()
print(f"{len(categories)} Kategorien wurden erstellt.")
def create_sample_mindmap():
"""Erstellt eine Beispiel-Mindmap mit Knoten und Beziehungen"""
# Kategorien für die Zuordnung
categories = Category.query.all()
category_map = {cat.name: cat for cat in categories}
# Beispielknoten erstellen
nodes = [
{
'name': 'Wissensmanagement',
'description': 'Systematische Erfassung, Speicherung und Nutzung von Wissen in Organisationen.',
'color_code': '#6366f1',
'icon': 'database',
'category': category_map.get('Konzept'),
'x': 0,
'y': 0
},
{
'name': 'Mind-Mapping',
'description': 'Technik zur visuellen Darstellung von Informationen und Zusammenhängen.',
'color_code': '#10b981',
'icon': 'git-branch',
'category': category_map.get('Prozess'),
'x': 200,
'y': -150
},
{
'name': 'Cytoscape.js',
'description': 'JavaScript-Bibliothek für die Visualisierung und Manipulation von Graphen.',
'color_code': '#3b82f6',
'icon': 'code',
'category': category_map.get('Technologie'),
'x': 350,
'y': -50
},
{
'name': 'Socket.IO',
'description': 'Bibliothek für Echtzeit-Kommunikation zwischen Client und Server.',
'color_code': '#3b82f6',
'icon': 'zap',
'category': category_map.get('Technologie'),
'x': 350,
'y': 100
},
{
'name': 'Kollaboration',
'description': 'Zusammenarbeit mehrerer Benutzer an gemeinsamen Inhalten.',
'color_code': '#f59e0b',
'icon': 'users',
'category': category_map.get('Prozess'),
'x': 200,
'y': 150
},
{
'name': 'SQLite',
'description': 'Leichtgewichtige relationale Datenbank, die ohne Server-Prozess auskommt.',
'color_code': '#3b82f6',
'icon': 'database',
'category': category_map.get('Technologie'),
'x': 0,
'y': 200
},
{
'name': 'Flask',
'description': 'Leichtgewichtiges Python-Webframework für die Entwicklung von Webanwendungen.',
'color_code': '#3b82f6',
'icon': 'server',
'category': category_map.get('Technologie'),
'x': -200,
'y': 150
},
{
'name': 'REST API',
'description': 'Architekturstil für verteilte Systeme, insbesondere Webanwendungen.',
'color_code': '#10b981',
'icon': 'link',
'category': category_map.get('Konzept'),
'x': -200,
'y': -150
},
{
'name': 'Dokumentation',
'description': 'Strukturierte Erfassung und Beschreibung von Informationen und Prozessen.',
'color_code': '#ec4899',
'icon': 'file-text',
'category': category_map.get('Dokument'),
'x': -350,
'y': 0
}
]
# Knoten in die Datenbank einfügen
node_objects = {}
for node_data in nodes:
category = node_data.pop('category', None)
x = node_data.pop('x', 0)
y = node_data.pop('y', 0)
node = MindMapNode(**node_data)
if category:
node.category_id = category.id
db.session.add(node)
db.session.flush() # Generiert IDs für neue Objekte
node_objects[node.name] = node
# Beziehungen erstellen
relationships = [
('Wissensmanagement', 'Mind-Mapping'),
('Wissensmanagement', 'Kollaboration'),
('Wissensmanagement', 'Dokumentation'),
('Mind-Mapping', 'Cytoscape.js'),
('Kollaboration', 'Socket.IO'),
('Wissensmanagement', 'SQLite'),
('SQLite', 'Flask'),
('Flask', 'REST API'),
('REST API', 'Socket.IO'),
('REST API', 'Dokumentation')
]
for parent_name, child_name in relationships:
parent = node_objects.get(parent_name)
child = node_objects.get(child_name)
if parent and child:
parent.children.append(child)
db.session.commit()
print(f"{len(nodes)} Knoten und {len(relationships)} Beziehungen wurden erstellt.")
if __name__ == '__main__':
init_db()
print("Datenbank wurde erfolgreich initialisiert!")
print("Sie können die Anwendung jetzt mit 'python app.py' starten")
print("Anmelden mit:")
print(" Admin: username=admin, password=admin")
print(" User: username=user, password=user")

1
migrations/README Normal file
View File

@@ -0,0 +1 @@
Single-database configuration for Flask.

Binary file not shown.

50
migrations/alembic.ini Normal file
View File

@@ -0,0 +1,50 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

113
migrations/env.py Normal file
View File

@@ -0,0 +1,113 @@
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions['migrate'].db.get_engine()
except (TypeError, AttributeError):
# this works with Flask-SQLAlchemy>=3
return current_app.extensions['migrate'].db.engine
def get_engine_url():
try:
return get_engine().url.render_as_string(hide_password=False).replace(
'%', '%%')
except AttributeError:
return str(get_engine().url).replace('%', '%%')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option('sqlalchemy.url', get_engine_url())
target_db = current_app.extensions['migrate'].db
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_metadata():
if hasattr(target_db, 'metadatas'):
return target_db.metadatas[None]
return target_db.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=get_metadata(), literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
conf_args = current_app.extensions['migrate'].configure_args
if conf_args.get("process_revision_directives") is None:
conf_args["process_revision_directives"] = process_revision_directives
connectable = get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=get_metadata(),
**conf_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View File

@@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,46 @@
"""Add password column to user
Revision ID: d4406f5b12f7
Revises:
Create Date: 2025-04-28 21:26:37.430823
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd4406f5b12f7'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('password', sa.String(length=512), nullable=False, server_default="changeme"))
batch_op.add_column(sa.Column('is_active', sa.Boolean(), nullable=True))
batch_op.add_column(sa.Column('role', sa.String(length=20), nullable=True))
batch_op.drop_column('last_login')
batch_op.drop_column('bio')
batch_op.drop_column('password_hash')
batch_op.drop_column('is_admin')
batch_op.drop_column('avatar')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('avatar', sa.VARCHAR(length=200), nullable=True))
batch_op.add_column(sa.Column('is_admin', sa.BOOLEAN(), nullable=True))
batch_op.add_column(sa.Column('password_hash', sa.VARCHAR(length=128), nullable=True))
batch_op.add_column(sa.Column('bio', sa.TEXT(), nullable=True))
batch_op.add_column(sa.Column('last_login', sa.DATETIME(), nullable=True))
batch_op.drop_column('role')
batch_op.drop_column('is_active')
batch_op.drop_column('password')
# ### end Alembic commands ###

112
website/models.py → models.py Executable file → Normal file
View File

@@ -6,6 +6,8 @@ from flask_login import UserMixin
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from enum import Enum
import uuid as uuid_pkg
import os
db = SQLAlchemy()
@@ -43,30 +45,28 @@ user_thought_bookmark = db.Table('user_thought_bookmark',
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
class User(UserMixin, db.Model):
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
password = db.Column(db.String(512), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime)
avatar = db.Column(db.String(200))
bio = db.Column(db.Text)
is_active = db.Column(db.Boolean, default=True)
role = db.Column(db.String(20), default="user") # 'user', 'admin', 'moderator'
# Beziehungen
thoughts = db.relationship('Thought', backref='author', lazy=True)
comments = db.relationship('Comment', backref='author', lazy=True)
user_mindmaps = db.relationship('UserMindmap', backref='user', lazy=True)
mindmap_notes = db.relationship('MindmapNote', backref='user', lazy=True)
bookmarked_thoughts = db.relationship('Thought', secondary=user_thought_bookmark,
backref=db.backref('bookmarked_by', lazy='dynamic'))
# Relationships
threads = db.relationship('Thread', backref='creator', lazy=True)
messages = db.relationship('Message', backref='author', lazy=True)
projects = db.relationship('Project', backref='owner', lazy=True)
def __repr__(self):
return f'<User {self.username}>'
def set_password(self, password):
self.password_hash = generate_password_hash(password)
self.password = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
return check_password_hash(self.password, password)
class Category(db.Model):
"""Wissenschaftliche Kategorien für die Gliederung der öffentlichen Mindmap"""
@@ -81,6 +81,9 @@ class Category(db.Model):
children = db.relationship('Category', backref=db.backref('parent', remote_side=[id]))
nodes = db.relationship('MindMapNode', backref='category', lazy=True)
def __repr__(self):
return f'<Category {self.name}>'
class MindMapNode(db.Model):
"""Öffentliche Mindmap-Knoten, die für alle Benutzer sichtbar sind"""
id = db.Column(db.Integer, primary_key=True)
@@ -92,7 +95,9 @@ class MindMapNode(db.Model):
created_at = db.Column(db.DateTime, default=datetime.utcnow)
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True)
__table_args__ = {'extend_existing': True}
# Beziehungen für Baumstruktur (mehrere Eltern möglich)
parents = db.relationship(
'MindMapNode',
@@ -111,6 +116,20 @@ class MindMapNode(db.Model):
# Beziehung zum Ersteller
created_by = db.relationship('User', backref='created_nodes')
def __repr__(self):
return f'<MindMapNode {self.name}>'
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'description': self.description,
'color_code': self.color_code,
'icon': self.icon,
'category_id': self.category_id,
'created_at': self.created_at.isoformat() if self.created_at else None
}
class UserMindmap(db.Model):
"""Benutzerspezifische Mindmap, die vom Benutzer personalisierbar ist"""
id = db.Column(db.Integer, primary_key=True)
@@ -227,4 +246,63 @@ class Comment(db.Model):
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Thread model
class Thread(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Relationships
messages = db.relationship('Message', backref='thread', lazy=True, cascade="all, delete-orphan")
def __repr__(self):
return f'<Thread {self.title}>'
# Message model
class Message(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
thread_id = db.Column(db.Integer, db.ForeignKey('thread.id'), nullable=False)
role = db.Column(db.String(20), default="user") # 'user', 'assistant', 'system'
def __repr__(self):
return f'<Message {self.id} by {self.user_id}>'
# Project model
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(150), nullable=False)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
# Relationships
documents = db.relationship('Document', backref='project', lazy=True, cascade="all, delete-orphan")
def __repr__(self):
return f'<Project {self.title}>'
# Document model
class Document(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(150), nullable=False)
content = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
project_id = db.Column(db.Integer, db.ForeignKey('project.id'), nullable=False)
filename = db.Column(db.String(150), nullable=True)
file_path = db.Column(db.String(300), nullable=True)
file_type = db.Column(db.String(50), nullable=True)
file_size = db.Column(db.Integer, nullable=True)
def __repr__(self):
return f'<Document {self.title}>'

View File

@@ -5,10 +5,13 @@ email-validator
python-dotenv
werkzeug==2.2.3
flask-sqlalchemy==3.0.5
openai==1.3.0
openai
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
pytest-flask==1.2.0
Flask-Migrate
flask-socketio==5.3.6
python-engineio==4.8.2
python-socketio==5.11.1

View File

@@ -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
View 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
View 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;
}

526
static/css/base-styles.css Normal file
View File

@@ -0,0 +1,526 @@
/* Base Styles - Dark, Mystical Theme */
/* Global Variables */
:root {
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: 'JetBrains Mono', SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* Light Theme */
--bg-primary-light: #f8fafc;
--bg-secondary-light: #f1f5f9;
--bg-tertiary-light: #e2e8f0;
--text-primary-light: #1e293b;
--text-secondary-light: #475569;
--accent-primary-light: #7c3aed;
--accent-secondary-light: #8b5cf6;
--accent-tertiary-light: #a78bfa;
--border-light: #e2e8f0;
--shadow-light: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--glow-light: 0 0 15px rgba(139, 92, 246, 0.3);
/* Dark Theme */
--bg-primary-dark: #0a0e19;
--bg-secondary-dark: #111827;
--bg-tertiary-dark: #1f2937;
--text-primary-dark: #f9fafb;
--text-secondary-dark: #e5e7eb;
--accent-primary-dark: #6d28d9;
--accent-secondary-dark: #8b5cf6;
--accent-tertiary-dark: #a78bfa;
--border-dark: #1f2937;
--shadow-dark: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
--glow-dark: 0 0 15px rgba(124, 58, 237, 0.5);
/* Transitions */
--transition-fast: 150ms ease-in-out;
--transition-normal: 300ms ease-in-out;
--transition-slow: 500ms ease-in-out;
/* Light mode optimierte Farben */
--light-bg: #f9fafb;
--light-text: #1e293b;
--light-heading: #0f172a;
--light-primary: #3b82f6;
--light-primary-hover: #4f46e5;
--light-secondary: #6b7280;
--light-border: #e5e7eb;
--light-card-bg: rgba(255, 255, 255, 0.92);
--light-navbar-bg: rgba(255, 255, 255, 0.92);
--light-input-bg: #ffffff;
--light-input-border: #d1d5db;
--light-input-focus: #3b82f6;
--light-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
/* Base Styles */
html {
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
scroll-behavior: smooth;
}
body {
transition: background-color var(--transition-normal), color var(--transition-normal);
overflow-x: hidden;
min-height: 100vh;
font-family: var(--font-sans);
line-height: 1.5;
}
/* Dark Mode */
html.dark body {
background-color: var(--bg-primary-dark);
color: var(--text-primary-dark);
}
/* Light Mode */
body:not(.dark) {
background-color: var(--light-bg);
color: var(--light-text);
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
.hero-heading {
font-size: 2.75rem;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.1;
}
.section-heading {
font-size: 2rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.gradient-text {
background-clip: text;
-webkit-background-clip: text;
color: transparent;
display: inline-block;
}
html.dark .gradient-text {
background-image: linear-gradient(135deg, var(--accent-primary-dark), var(--accent-secondary-dark));
text-shadow: var(--glow-dark);
}
.gradient-text {
background-image: linear-gradient(135deg, var(--accent-primary-light), var(--accent-secondary-light));
text-shadow: var(--glow-light);
}
/* Mystical elements */
.mystical-border {
position: relative;
}
.mystical-border::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid;
border-radius: inherit;
pointer-events: none;
opacity: 0.3;
}
html.dark .mystical-border::before {
border-color: var(--accent-primary-dark);
box-shadow: var(--glow-dark);
}
.mystical-border::before {
border-color: var(--accent-primary-light);
box-shadow: var(--glow-light);
}
/* Navigation Links */
.nav-link {
position: relative;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: var(--transition-normal);
}
html.dark .nav-link {
color: var(--text-secondary-dark);
}
.nav-link {
color: var(--text-secondary-light);
}
html.dark .nav-link:hover {
color: var(--text-primary-dark);
background-color: rgba(31, 41, 55, 0.5);
}
.nav-link:hover {
color: var(--text-primary-light);
background-color: rgba(241, 245, 249, 0.5);
}
html.dark .nav-link-active {
color: var(--accent-tertiary-dark);
background-color: rgba(109, 40, 217, 0.15);
}
.nav-link-light-active {
color: var(--accent-primary-light);
background-color: rgba(139, 92, 246, 0.1);
}
/* Glass Morphism Effects */
.glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
.glass-navbar-light {
background-color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-color: rgba(226, 232, 240, 0.5);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
}
.glass-morphism {
transition: background-color var(--transition-normal), backdrop-filter var(--transition-normal);
}
/* Cards */
.mystical-card {
border-radius: 0.75rem;
overflow: hidden;
transition: var(--transition-normal);
}
html.dark .mystical-card {
background-color: var(--bg-secondary-dark);
border: 1px solid var(--border-dark);
box-shadow: var(--shadow-dark);
}
.mystical-card {
background-color: var(--bg-secondary-light);
border: 1px solid var(--border-light);
box-shadow: var(--shadow-light);
}
html.dark .mystical-card:hover {
box-shadow: var(--glow-dark), var(--shadow-dark);
border-color: var(--accent-primary-dark);
}
.mystical-card:hover {
box-shadow: var(--glow-light), var(--shadow-light);
border-color: var(--accent-primary-light);
}
/* Buttons */
.mystical-button {
padding: 0.625rem 1.25rem;
border-radius: 0.5rem;
font-weight: 500;
transition: var(--transition-normal);
position: relative;
overflow: hidden;
}
.mystical-button::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, transparent, rgba(255, 255, 255, 0.05), transparent);
transform: translateX(-100%);
transition: transform 0.8s ease-in-out;
}
.mystical-button:hover::before {
transform: translateX(100%);
}
html.dark .mystical-button-primary {
background-color: var(--accent-primary-dark);
color: white;
}
.mystical-button-primary {
background-color: var(--accent-primary-light);
color: white;
}
html.dark .mystical-button-primary:hover {
background-color: var(--accent-secondary-dark);
box-shadow: var(--glow-dark);
}
.mystical-button-primary:hover {
background-color: var(--accent-secondary-light);
box-shadow: var(--glow-light);
}
html.dark .mystical-button-secondary {
background-color: var(--bg-tertiary-dark);
color: var(--text-primary-dark);
border: 1px solid var(--border-dark);
}
.mystical-button-secondary {
background-color: var(--bg-tertiary-light);
color: var(--text-primary-light);
border: 1px solid var(--border-light);
}
html.dark .mystical-button-secondary:hover {
background-color: var(--bg-secondary-dark);
border-color: var(--accent-tertiary-dark);
}
.mystical-button-secondary:hover {
background-color: var(--bg-secondary-light);
border-color: var(--accent-tertiary-light);
}
/* Inputs */
.mystical-input {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: var(--transition-normal);
width: 100%;
outline: none;
}
html.dark .mystical-input {
background-color: var(--bg-tertiary-dark);
border: 1px solid var(--border-dark);
color: var(--text-primary-dark);
}
.mystical-input {
background-color: var(--bg-tertiary-light);
border: 1px solid var(--border-light);
color: var(--text-primary-light);
}
html.dark .mystical-input:focus {
border-color: var(--accent-tertiary-dark);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.25);
}
.mystical-input:focus {
border-color: var(--accent-tertiary-light);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
}
/* Animations */
@keyframes floatAnimation {
0% { transform: translateY(0); }
50% { transform: translateY(-5px); }
100% { transform: translateY(0); }
}
.animate-float {
animation: floatAnimation 3s ease-in-out infinite;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
/* Scroll Bars */
::-webkit-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
html.dark ::-webkit-scrollbar-track {
background: var(--bg-secondary-dark);
}
::-webkit-scrollbar-track {
background: var(--bg-secondary-light);
}
html.dark ::-webkit-scrollbar-thumb {
background: var(--accent-primary-dark);
border-radius: 0.25rem;
}
::-webkit-scrollbar-thumb {
background: var(--accent-primary-light);
border-radius: 0.25rem;
}
html.dark ::-webkit-scrollbar-thumb:hover {
background: var(--accent-secondary-dark);
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent-secondary-light);
}
/* Responsive Utilities */
@media (max-width: 640px) {
.hero-heading {
font-size: 2rem;
}
.section-heading {
font-size: 1.5rem;
}
}
/* Additional background elements */
.mystical-dot {
position: absolute;
border-radius: 50%;
opacity: 0.15;
filter: blur(3px);
z-index: -1;
transition: opacity var(--transition-normal);
}
html.dark .mystical-dot {
background-color: var(--accent-primary-dark);
box-shadow: 0 0 15px var(--accent-primary-dark);
}
.mystical-dot {
background-color: var(--accent-primary-light);
box-shadow: 0 0 15px var(--accent-primary-light);
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Focus styles for keyboard navigation */
:focus-visible {
outline: 2px solid var(--accent-primary-light);
outline-offset: 2px;
}
html.dark :focus-visible {
outline-color: var(--accent-primary-dark);
}
/* Light Mode Überschriften */
body:not(.dark) h1,
body:not(.dark) h2,
body:not(.dark) h3,
body:not(.dark) h4,
body:not(.dark) h5,
body:not(.dark) h6 {
color: var(--light-heading);
}
/* Light Mode Links */
body:not(.dark) a {
color: var(--light-primary);
}
body:not(.dark) a:hover {
color: var(--light-primary-hover);
}
/* Light Mode Buttons */
body:not(.dark) .btn,
body:not(.dark) button:not(.toggle) {
background-color: var(--light-primary);
color: white;
border: none;
box-shadow: var(--light-shadow);
border-radius: 0.375rem;
padding: 0.5rem 1rem;
transition: all 0.3s ease;
}
body:not(.dark) .btn:hover,
body:not(.dark) button:not(.toggle):hover {
background-color: var(--light-primary-hover);
transform: translateY(-2px);
box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
}
/* Light Mode Cards und Panels */
body:not(.dark) .card,
body:not(.dark) .panel {
background-color: var(--light-card-bg);
border: 1px solid var(--light-border);
border-radius: 0.5rem;
box-shadow: var(--light-shadow);
}
/* Light Mode Tabelle */
body:not(.dark) table {
background-color: var(--light-card-bg);
border-collapse: collapse;
}
body:not(.dark) th {
background-color: var(--light-bg);
color: var(--light-heading);
border-bottom: 1px solid var(--light-border);
}
body:not(.dark) td {
border-bottom: 1px solid var(--light-border);
}
/* Light Mode Inputs */
body:not(.dark) input,
body:not(.dark) textarea,
body:not(.dark) select {
background-color: var(--light-input-bg);
border: 1px solid var(--light-input-border);
color: var(--light-text);
border-radius: 0.375rem;
padding: 0.5rem;
}
body:not(.dark) input:focus,
body:not(.dark) textarea:focus,
body:not(.dark) select:focus {
border-color: var(--light-input-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
outline: none;
}
/* Navbar im Light Mode verbessern */
body:not(.dark) nav,
body:not(.dark) .navbar {
background-color: var(--light-navbar-bg);
box-shadow: var(--light-shadow);
border-bottom: 1px solid var(--light-border);
}

View File

@@ -0,0 +1,106 @@
/* Neural Network Background CSS */
/* Make sure the neural network background is always visible */
#neural-network-background {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
z-index: -10 !important; /* Below content but above regular background */
pointer-events: none !important;
opacity: 1 !important;
}
/* Override any solid background colors for the body */
body, body.dark {
background-color: transparent !important;
}
/* Make sure any background color is removed */
html.dark, html {
background-color: transparent !important;
}
/* Make sure any fixed backgrounds are removed */
#app-container {
background-color: transparent !important;
}
/* Ensure content is properly visible over the background */
.glass-morphism {
background-color: rgba(17, 24, 39, 0.6) !important;
backdrop-filter: blur(5px) !important;
}
/* Dark Mode - Navbar */
body.dark .glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.7) !important;
}
/* Light Mode - Verbesserter Navbar */
body .glass-navbar-light {
background-color: rgba(255, 255, 255, 0.92) !important;
backdrop-filter: blur(10px) !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
border-bottom: 1px solid rgba(220, 220, 220, 0.5) !important;
}
/* Light Mode - Verbesserte Lesbarkeit für Navbar-Elemente */
body:not(.dark) .navbar-link,
body:not(.dark) .navbar-item {
color: #1e3a8a !important; /* Dunkles Blau für bessere Lesbarkeit */
}
body:not(.dark) .navbar-link:hover,
body:not(.dark) .navbar-item:hover {
color: #4f46e5 !important; /* Helles Lila beim Hover */
background-color: rgba(240, 245, 255, 0.9) !important;
}
/* Light Mode - Buttons verbessert */
body:not(.dark) .btn,
body:not(.dark) button {
background-color: #3b82f6 !important; /* Klares Blau statt Grau */
color: white !important;
border: none !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
}
body:not(.dark) .btn:hover,
body:not(.dark) button:hover {
background-color: #4f46e5 !important; /* Lila beim Hover */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.12) !important;
}
/* Verbesserte Karten im Light Mode */
body:not(.dark) .card,
body:not(.dark) .panel {
background-color: rgba(255, 255, 255, 0.92) !important;
border: 1px solid rgba(220, 220, 220, 0.8) !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important;
}
/* Verbesserte Lesbarkeit für Text im Light Mode */
body:not(.dark) {
color: #1e293b !important; /* Dunkles Blau-Grau statt Schwarz */
}
body:not(.dark) h1,
body:not(.dark) h2,
body:not(.dark) h3,
body:not(.dark) h4,
body:not(.dark) h5,
body:not(.dark) h6 {
color: #0f172a !important; /* Fast schwarz für Überschriften */
}
/* Make sure footer has proper transparency and styling */
body.dark footer {
background-color: rgba(10, 14, 25, 0.7) !important;
}
body:not(.dark) footer {
background-color: rgba(249, 250, 251, 0.92) !important;
border-top: 1px solid rgba(220, 220, 220, 0.8) !important;
}

View File

@@ -1441,4 +1441,204 @@ html, body {
position: sticky;
top: 0;
z-index: 1000;
}
/* Light Mode Optimierungen für wichtige UI-Komponenten */
/* Buttons im Light Mode */
.btn-primary:not(.dark-mode .btn-primary) {
background-color: var(--light-primary, #3b82f6);
color: white;
border: none;
font-weight: 500;
}
.btn-primary:not(.dark-mode .btn-primary):hover {
background-color: var(--light-primary-hover, #4f46e5);
transform: translateY(-1px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.btn-secondary:not(.dark-mode .btn-secondary) {
background-color: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
font-weight: 500;
}
.btn-secondary:not(.dark-mode .btn-secondary):hover {
background-color: #e5e7eb;
}
/* Navbar im Light Mode */
.navbar:not(.dark-mode .navbar),
.nav:not(.dark-mode .nav) {
background-color: rgba(255, 255, 255, 0.95);
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.navbar:not(.dark-mode .navbar) .nav-link,
.nav:not(.dark-mode .nav) .nav-link {
color: #1e3a8a;
font-weight: 500;
}
.navbar:not(.dark-mode .navbar) .nav-link:hover,
.nav:not(.dark-mode .nav) .nav-link:hover {
color: #4f46e5;
}
.navbar:not(.dark-mode .navbar) .navbar-brand,
.nav:not(.dark-mode .nav) .navbar-brand {
color: #0f172a;
font-weight: 700;
}
/* Dropdown Menüs im Light Mode */
.dropdown-menu:not(.dark-mode .dropdown-menu) {
background-color: white;
border: 1px solid #e5e7eb;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05), 0 10px 15px rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
padding: 0.5rem 0;
}
.dropdown-item:not(.dark-mode .dropdown-item) {
color: #1e293b;
padding: 0.5rem 1rem;
}
.dropdown-item:not(.dark-mode .dropdown-item):hover {
background-color: #f1f5f9;
color: #4f46e5;
}
/* Karten im Light Mode */
.card:not(.dark-mode .card) {
background-color: white;
border: 1px solid #e5e7eb;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.03), 0 1px 3px rgba(0, 0, 0, 0.05);
border-radius: 0.5rem;
overflow: hidden;
}
.card-header:not(.dark-mode .card-header) {
background-color: #f8fafc;
border-bottom: 1px solid #e5e7eb;
padding: 1rem 1.5rem;
}
.card-footer:not(.dark-mode .card-footer) {
background-color: #f8fafc;
border-top: 1px solid #e5e7eb;
}
/* Formulare im Light Mode */
.form-control:not(.dark-mode .form-control) {
background-color: white;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
padding: 0.5rem 0.75rem;
color: #1e293b;
}
.form-control:not(.dark-mode .form-control):focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
}
/* Tabs im Light Mode */
.nav-tabs:not(.dark-mode .nav-tabs) {
border-bottom-color: #e5e7eb;
}
.nav-tabs:not(.dark-mode .nav-tabs) .nav-link {
color: #64748b;
border: 1px solid transparent;
}
.nav-tabs:not(.dark-mode .nav-tabs) .nav-link:hover {
border-color: #e5e7eb #e5e7eb #e5e7eb;
color: #3b82f6;
}
.nav-tabs:not(.dark-mode .nav-tabs) .nav-link.active {
color: #0f172a;
background-color: white;
border-color: #e5e7eb #e5e7eb white;
font-weight: 500;
}
/* Alerts im Light Mode */
.alert:not(.dark-mode .alert) {
border-radius: 0.5rem;
border: 1px solid transparent;
}
.alert-primary:not(.dark-mode .alert-primary) {
background-color: #eff6ff;
border-color: #bfdbfe;
color: #1e40af;
}
.alert-success:not(.dark-mode .alert-success) {
background-color: #f0fdf4;
border-color: #bbf7d0;
color: #166534;
}
.alert-warning:not(.dark-mode .alert-warning) {
background-color: #fffbeb;
border-color: #fef3c7;
color: #92400e;
}
.alert-danger:not(.dark-mode .alert-danger) {
background-color: #fef2f2;
border-color: #fecaca;
color: #b91c1c;
}
/* Badges im Light Mode */
.badge:not(.dark-mode .badge) {
font-weight: 500;
padding: 0.25em 0.6em;
border-radius: 0.375rem;
}
.badge-primary:not(.dark-mode .badge-primary) {
background-color: #3b82f6;
color: white;
}
.badge-secondary:not(.dark-mode .badge-secondary) {
background-color: #f3f4f6;
color: #1f2937;
}
/* Tabellen im Light Mode */
table:not(.dark-mode table) {
background-color: white;
border-collapse: collapse;
width: 100%;
}
table:not(.dark-mode table) th {
background-color: #f8fafc;
border-bottom: 1px solid #e5e7eb;
color: #0f172a;
font-weight: 600;
padding: 0.75rem;
text-align: left;
}
table:not(.dark-mode table) td {
border-bottom: 1px solid #e5e7eb;
padding: 0.75rem;
color: #1e293b;
}
table:not(.dark-mode table) tr:hover {
background-color: #f8fafc;
}

6
static/css/tailwind.min.css vendored Normal file
View File

@@ -0,0 +1,6 @@
/**
* Failed to bundle using Rollup v2.79.2: the file imports a not supported node.js built-in module "fs".
* If you believe this to be an issue with jsDelivr, and not with the package itself, please open an issue at https://github.com/jsdelivr/jsdelivr
*/
throw new Error('Failed to bundle using Rollup v2.79.2: the file imports a not supported node.js built-in module "fs". If you believe this to be an issue with jsDelivr, and not with the package itself, please open an issue at https://github.com/jsdelivr/jsdelivr');

View File

@@ -450,6 +450,76 @@ class D3Extensions {
// Pulsanimation starten
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

BIN
static/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

35
static/fonts/inter.css Normal file
View 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');
}

View 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');
}

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

5
static/js/alpine.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -2,23 +2,11 @@
* MindMap - Hauptdatei für globale JavaScript-Funktionen
*/
// Import des ChatGPT-Assistenten
import ChatGPTAssistant from './modules/chatgpt-assistant.js';
/**
* Hauptmodul für die MindMap-Anwendung
* Verwaltet die globale Anwendungslogik
*/
document.addEventListener('DOMContentLoaded', function() {
// Initialisiere die Anwendung
MindMap.init();
// Wende Dunkel-/Hellmodus an
const isDarkMode = localStorage.getItem('darkMode') === 'dark';
document.documentElement.classList.toggle('dark', isDarkMode);
});
/**
* Hauptobjekt der MindMap-Anwendung
*/
@@ -27,7 +15,7 @@ const MindMap = {
initialized: false,
darkMode: document.documentElement.classList.contains('dark'),
pageInitializers: {},
currentPage: document.body.dataset.page,
currentPage: null,
/**
* Initialisiert die MindMap-Anwendung
@@ -35,13 +23,18 @@ const MindMap = {
init() {
if (this.initialized) return;
// Setze currentPage erst jetzt, wenn DOM garantiert geladen ist
this.currentPage = document.body && document.body.dataset ? document.body.dataset.page : null;
console.log('MindMap-Anwendung wird initialisiert...');
// Initialisiere den ChatGPT-Assistenten
const assistant = new ChatGPTAssistant();
assistant.init();
// Speichere als Teil von MindMap
this.assistant = assistant;
if (typeof ChatGPTAssistant !== 'undefined') {
const assistant = new ChatGPTAssistant();
assistant.init();
// Speichere als Teil von MindMap
this.assistant = assistant;
}
// Seiten-spezifische Initialisierer aufrufen
if (this.currentPage && this.pageInitializers[this.currentPage]) {
@@ -74,6 +67,12 @@ const MindMap = {
try {
console.log('Initialisiere Mindmap...');
// Prüfe, ob MindMapVisualization geladen ist
if (typeof MindMapVisualization === 'undefined') {
console.error('MindMapVisualization-Klasse ist nicht definiert!');
return;
}
// Initialisiere die Mindmap
const mindmap = new MindMapVisualization('#mindmap-container', {
height: mindmapContainer.clientHeight || 600,
@@ -224,6 +223,13 @@ const MindMap = {
});
}
};
window.MindMap = MindMap;
// Globale Export für andere Module
window.MindMap = MindMap;
document.addEventListener('DOMContentLoaded', function() {
// Initialisiere die Anwendung
MindMap.init();
// Wende Dunkel-/Hellmodus an
const isDarkMode = localStorage.getItem('darkMode') === 'dark';
document.documentElement.classList.toggle('dark', isDarkMode);
});

234
static/js/mindmap.html Normal file
View File

@@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interaktive Mindmap</title>
<!-- Cytoscape.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
<!-- Socket.IO -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
<!-- Feather Icons (optional) -->
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f9fafb;
color: #111827;
line-height: 1.5;
}
.container {
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
}
.header {
background-color: #1f2937;
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 1.5rem;
font-weight: 500;
}
.toolbar {
background-color: #f3f4f6;
padding: 0.75rem;
display: flex;
gap: 0.5rem;
border-bottom: 1px solid #e5e7eb;
}
.btn {
background-color: #3b82f6;
color: white;
border: none;
border-radius: 0.25rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
cursor: pointer;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn:hover {
background-color: #2563eb;
}
.btn-secondary {
background-color: #6b7280;
}
.btn-secondary:hover {
background-color: #4b5563;
}
.btn-danger {
background-color: #ef4444;
}
.btn-danger:hover {
background-color: #dc2626;
}
.search-container {
flex: 1;
display: flex;
margin-left: 1rem;
}
.search-input {
width: 100%;
max-width: 300px;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.25rem;
font-size: 0.875rem;
}
#cy {
flex: 1;
width: 100%;
position: relative;
}
.category-filters {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
padding: 0.75rem;
background-color: #ffffff;
border-bottom: 1px solid #e5e7eb;
}
.category-filter {
border: none;
border-radius: 0.25rem;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
cursor: pointer;
transition: opacity 0.2s;
color: white;
}
.category-filter:not(.active) {
opacity: 0.6;
}
.category-filter:hover:not(.active) {
opacity: 0.8;
}
.footer {
background-color: #f3f4f6;
padding: 0.75rem;
text-align: center;
font-size: 0.75rem;
color: #6b7280;
border-top: 1px solid #e5e7eb;
}
/* Kontextmenü Styling */
#context-menu {
position: absolute;
background-color: white;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
#context-menu .menu-item {
padding: 0.5rem 1rem;
cursor: pointer;
}
#context-menu .menu-item:hover {
background-color: #f3f4f6;
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>Interaktive Mindmap</h1>
<div class="search-container">
<input type="text" id="search-mindmap" class="search-input" placeholder="Suchen...">
</div>
</header>
<div class="toolbar">
<button id="addNode" class="btn">
<i data-feather="plus-circle"></i>
Knoten hinzufügen
</button>
<button id="addEdge" class="btn">
<i data-feather="git-branch"></i>
Verbindung erstellen
</button>
<button id="editNode" class="btn btn-secondary">
<i data-feather="edit-2"></i>
Knoten bearbeiten
</button>
<button id="deleteNode" class="btn btn-danger">
<i data-feather="trash-2"></i>
Knoten löschen
</button>
<button id="deleteEdge" class="btn btn-danger">
<i data-feather="scissors"></i>
Verbindung löschen
</button>
<button id="reLayout" class="btn btn-secondary">
<i data-feather="refresh-cw"></i>
Layout neu anordnen
</button>
<button id="exportMindmap" class="btn btn-secondary">
<i data-feather="download"></i>
Exportieren
</button>
</div>
<div id="category-filters" class="category-filters">
<!-- Wird dynamisch befüllt -->
</div>
<div id="cy"></div>
<footer class="footer">
Mindmap-Anwendung © 2023
</footer>
</div>
<!-- Unsere Mindmap JS -->
<script src="../js/mindmap.js"></script>
<!-- Icons initialisieren -->
<script>
document.addEventListener('DOMContentLoaded', () => {
if (typeof feather !== 'undefined') {
feather.replace();
}
});
</script>
</body>
</html>

749
static/js/mindmap.js Normal file
View File

@@ -0,0 +1,749 @@
/**
* Mindmap.js - Interaktive Mind-Map Implementierung
* - Cytoscape.js für Graph-Rendering
* - Fetch API für REST-Zugriffe
* - Socket.IO für Echtzeit-Synchronisation
*/
(async () => {
/* 1. Initialisierung und Grundkonfiguration */
const cy = cytoscape({
container: document.getElementById('cy'),
style: [
{
selector: 'node',
style: {
'label': 'data(name)',
'text-valign': 'center',
'color': '#fff',
'background-color': 'data(color)',
'width': 45,
'height': 45,
'font-size': 11,
'text-outline-width': 1,
'text-outline-color': '#000',
'text-outline-opacity': 0.5,
'text-wrap': 'wrap',
'text-max-width': 80
}
},
{
selector: 'node[icon]',
style: {
'background-image': function(ele) {
return `static/img/icons/${ele.data('icon')}.svg`;
},
'background-width': '60%',
'background-height': '60%',
'background-position-x': '50%',
'background-position-y': '40%',
'text-margin-y': 10
}
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': '#888',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
'target-arrow-color': '#888'
}
},
{
selector: ':selected',
style: {
'border-width': 3,
'border-color': '#f8f32b'
}
}
],
layout: {
name: 'breadthfirst',
directed: true,
padding: 30,
spacingFactor: 1.2
}
});
/* 2. Hilfs-Funktionen für API-Zugriffe */
const get = async endpoint => {
try {
const response = await fetch(endpoint);
if (!response.ok) {
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
return []; // Leeres Array zurückgeben bei Fehlern
}
return await response.json();
} catch (error) {
console.error(`Fehler beim Abrufen von ${endpoint}:`, error);
return []; // Leeres Array zurückgeben bei Netzwerkfehlern
}
};
const post = async (endpoint, body) => {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!response.ok) {
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
return {}; // Leeres Objekt zurückgeben bei Fehlern
}
return await response.json();
} catch (error) {
console.error(`Fehler beim POST zu ${endpoint}:`, error);
return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
}
};
const del = async endpoint => {
try {
const response = await fetch(endpoint, { method: 'DELETE' });
if (!response.ok) {
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
return {}; // Leeres Objekt zurückgeben bei Fehlern
}
return await response.json();
} catch (error) {
console.error(`Fehler beim DELETE zu ${endpoint}:`, error);
return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
}
};
/* 3. Kategorien laden für Style-Informationen */
let categories = await get('/api/categories');
/* 4. Daten laden und Rendering */
const loadMindmap = async () => {
try {
// Nodes und Beziehungen parallel laden
const [nodes, relationships] = await Promise.all([
get('/api/mind_map_nodes'),
get('/api/node_relationships')
]);
// Graph leeren (für Reload-Fälle)
cy.elements().remove();
// Überprüfen, ob nodes ein Array ist, wenn nicht, setze es auf ein leeres Array
const nodesArray = Array.isArray(nodes) ? nodes : [];
// Knoten zum Graph hinzufügen
cy.add(
nodesArray.map(node => {
// Kategorie-Informationen für Styling abrufen
const category = categories.find(c => c.id === node.category_id) || {};
return {
data: {
id: node.id.toString(),
name: node.name,
description: node.description,
color: node.color_code || category.color_code || '#6b7280',
icon: node.icon || category.icon,
category_id: node.category_id
},
position: node.x && node.y ? { x: node.x, y: node.y } : undefined
};
})
);
// Überprüfen, ob relationships ein Array ist, wenn nicht, setze es auf ein leeres Array
const relationshipsArray = Array.isArray(relationships) ? relationships : [];
// Kanten zum Graph hinzufügen
cy.add(
relationshipsArray.map(rel => ({
data: {
id: `${rel.parent_id}_${rel.child_id}`,
source: rel.parent_id.toString(),
target: rel.child_id.toString()
}
}))
);
// Wenn keine Knoten geladen wurden, Fallback-Knoten erstellen
if (nodesArray.length === 0) {
// Mindestens einen Standardknoten hinzufügen
cy.add({
data: {
id: 'fallback-1',
name: 'Mindmap',
description: 'Erstellen Sie hier Ihre eigene Mindmap',
color: '#3b82f6',
icon: 'help-circle'
},
position: { x: 300, y: 200 }
});
// Erfolgsmeldung anzeigen
console.log('Mindmap erfolgreich initialisiert mit Fallback-Knoten');
// Info-Meldung für Benutzer anzeigen
const infoBox = document.createElement('div');
infoBox.classList.add('info-message');
infoBox.style.position = 'absolute';
infoBox.style.top = '50%';
infoBox.style.left = '50%';
infoBox.style.transform = 'translate(-50%, -50%)';
infoBox.style.padding = '15px 20px';
infoBox.style.backgroundColor = 'rgba(59, 130, 246, 0.9)';
infoBox.style.color = 'white';
infoBox.style.borderRadius = '8px';
infoBox.style.zIndex = '5';
infoBox.style.maxWidth = '80%';
infoBox.style.textAlign = 'center';
infoBox.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
infoBox.innerHTML = 'Mindmap erfolgreich initialisiert.<br>Verwenden Sie die Werkzeugleiste, um Knoten hinzuzufügen.';
document.getElementById('cy').appendChild(infoBox);
// Meldung nach 5 Sekunden ausblenden
setTimeout(() => {
infoBox.style.opacity = '0';
infoBox.style.transition = 'opacity 0.5s ease';
setTimeout(() => {
if (infoBox.parentNode) {
infoBox.parentNode.removeChild(infoBox);
}
}, 500);
}, 5000);
}
// Layout anwenden wenn keine Positionsdaten vorhanden
const nodesWithoutPosition = cy.nodes().filter(node =>
!node.position() || (node.position().x === 0 && node.position().y === 0)
);
if (nodesWithoutPosition.length > 0) {
cy.layout({
name: 'breadthfirst',
directed: true,
padding: 30,
spacingFactor: 1.2
}).run();
}
// Tooltip-Funktionalität
cy.nodes().unbind('mouseover').bind('mouseover', (event) => {
const node = event.target;
const description = node.data('description');
if (description) {
const tooltip = document.getElementById('node-tooltip') ||
document.createElement('div');
if (!tooltip.id) {
tooltip.id = 'node-tooltip';
tooltip.style.position = 'absolute';
tooltip.style.backgroundColor = '#333';
tooltip.style.color = '#fff';
tooltip.style.padding = '8px';
tooltip.style.borderRadius = '4px';
tooltip.style.maxWidth = '250px';
tooltip.style.zIndex = 10;
tooltip.style.pointerEvents = 'none';
tooltip.style.transition = 'opacity 0.2s';
tooltip.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
document.body.appendChild(tooltip);
}
const renderedPosition = node.renderedPosition();
const containerRect = cy.container().getBoundingClientRect();
tooltip.innerHTML = description;
tooltip.style.left = (containerRect.left + renderedPosition.x + 25) + 'px';
tooltip.style.top = (containerRect.top + renderedPosition.y - 15) + 'px';
tooltip.style.opacity = '1';
}
});
cy.nodes().unbind('mouseout').bind('mouseout', () => {
const tooltip = document.getElementById('node-tooltip');
if (tooltip) {
tooltip.style.opacity = '0';
}
});
} catch (error) {
console.error('Fehler beim Laden der Mindmap:', error);
alert('Die Mindmap konnte nicht geladen werden. Bitte prüfen Sie die Konsole für Details.');
}
};
// Initial laden
await loadMindmap();
/* 5. Socket.IO für Echtzeit-Synchronisation */
const socket = io();
socket.on('node_added', async (node) => {
// Kategorie-Informationen für Styling abrufen
const category = categories.find(c => c.id === node.category_id) || {};
cy.add({
data: {
id: node.id.toString(),
name: node.name,
description: node.description,
color: node.color_code || category.color_code || '#6b7280',
icon: node.icon || category.icon,
category_id: node.category_id
}
});
// Layout neu anwenden, wenn nötig
if (!node.x || !node.y) {
cy.layout({ name: 'breadthfirst', directed: true, padding: 30 }).run();
}
});
socket.on('node_updated', (node) => {
const cyNode = cy.$id(node.id.toString());
if (cyNode.length > 0) {
// Kategorie-Informationen für Styling abrufen
const category = categories.find(c => c.id === node.category_id) || {};
cyNode.data({
name: node.name,
description: node.description,
color: node.color_code || category.color_code || '#6b7280',
icon: node.icon || category.icon,
category_id: node.category_id
});
if (node.x && node.y) {
cyNode.position({ x: node.x, y: node.y });
}
}
});
socket.on('node_deleted', (nodeId) => {
const cyNode = cy.$id(nodeId.toString());
if (cyNode.length > 0) {
cy.remove(cyNode);
}
});
socket.on('relationship_added', (rel) => {
cy.add({
data: {
id: `${rel.parent_id}_${rel.child_id}`,
source: rel.parent_id.toString(),
target: rel.child_id.toString()
}
});
});
socket.on('relationship_deleted', (rel) => {
const edgeId = `${rel.parent_id}_${rel.child_id}`;
const cyEdge = cy.$id(edgeId);
if (cyEdge.length > 0) {
cy.remove(cyEdge);
}
});
socket.on('category_updated', async () => {
// Kategorien neu laden
categories = await get('/api/categories');
// Nodes aktualisieren, die diese Kategorie verwenden
cy.nodes().forEach(node => {
const categoryId = node.data('category_id');
if (categoryId) {
const category = categories.find(c => c.id === categoryId);
if (category) {
node.data('color', node.data('color_code') || category.color_code);
node.data('icon', node.data('icon') || category.icon);
}
}
});
});
/* 6. UI-Interaktionen */
// Knoten hinzufügen
const btnAddNode = document.getElementById('addNode');
if (btnAddNode) {
btnAddNode.addEventListener('click', async () => {
const name = prompt('Knotenname eingeben:');
if (!name) return;
const description = prompt('Beschreibung (optional):');
// Kategorie auswählen
let categoryId = null;
if (categories.length > 0) {
const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
const categoryChoice = prompt(
`Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
'0'
);
if (categoryChoice !== null) {
const index = parseInt(categoryChoice, 10);
if (!isNaN(index) && index >= 0 && index < categories.length) {
categoryId = categories[index].id;
}
}
}
// Knoten erstellen
await post('/api/mind_map_node', {
name,
description,
category_id: categoryId
});
// Darstellung wird durch Socket.IO Event übernommen
});
}
// Verbindung hinzufügen
const btnAddEdge = document.getElementById('addEdge');
if (btnAddEdge) {
btnAddEdge.addEventListener('click', async () => {
const sel = cy.$('node:selected');
if (sel.length !== 2) {
alert('Bitte genau zwei Knoten auswählen (Parent → Child)');
return;
}
const [parent, child] = sel.map(node => node.id());
await post('/api/node_relationship', {
parent_id: parent,
child_id: child
});
// Darstellung wird durch Socket.IO Event übernommen
});
}
// Knoten bearbeiten
const btnEditNode = document.getElementById('editNode');
if (btnEditNode) {
btnEditNode.addEventListener('click', async () => {
const sel = cy.$('node:selected');
if (sel.length !== 1) {
alert('Bitte genau einen Knoten auswählen');
return;
}
const node = sel[0];
const nodeData = node.data();
const name = prompt('Knotenname:', nodeData.name);
if (!name) return;
const description = prompt('Beschreibung:', nodeData.description || '');
// Kategorie auswählen
let categoryId = nodeData.category_id;
if (categories.length > 0) {
const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
const categoryChoice = prompt(
`Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
categories.findIndex(c => c.id === categoryId).toString()
);
if (categoryChoice !== null) {
const index = parseInt(categoryChoice, 10);
if (!isNaN(index) && index >= 0 && index < categories.length) {
categoryId = categories[index].id;
}
}
}
// Knoten aktualisieren
await post(`/api/mind_map_node/${nodeData.id}`, {
name,
description,
category_id: categoryId
});
// Darstellung wird durch Socket.IO Event übernommen
});
}
// Knoten löschen
const btnDeleteNode = document.getElementById('deleteNode');
if (btnDeleteNode) {
btnDeleteNode.addEventListener('click', async () => {
const sel = cy.$('node:selected');
if (sel.length !== 1) {
alert('Bitte genau einen Knoten auswählen');
return;
}
if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
const nodeId = sel[0].id();
await del(`/api/mind_map_node/${nodeId}`);
// Darstellung wird durch Socket.IO Event übernommen
}
});
}
// Verbindung löschen
const btnDeleteEdge = document.getElementById('deleteEdge');
if (btnDeleteEdge) {
btnDeleteEdge.addEventListener('click', async () => {
const sel = cy.$('edge:selected');
if (sel.length !== 1) {
alert('Bitte genau eine Verbindung auswählen');
return;
}
if (confirm('Sind Sie sicher, dass Sie diese Verbindung löschen möchten?')) {
const edge = sel[0];
const parentId = edge.source().id();
const childId = edge.target().id();
await del(`/api/node_relationship/${parentId}/${childId}`);
// Darstellung wird durch Socket.IO Event übernommen
}
});
}
// Layout aktualisieren
const btnReLayout = document.getElementById('reLayout');
if (btnReLayout) {
btnReLayout.addEventListener('click', () => {
cy.layout({
name: 'breadthfirst',
directed: true,
padding: 30,
spacingFactor: 1.2
}).run();
});
}
/* 7. Position speichern bei Drag & Drop */
cy.on('dragfree', 'node', async (e) => {
const node = e.target;
const position = node.position();
await post(`/api/mind_map_node/${node.id()}/position`, {
x: Math.round(position.x),
y: Math.round(position.y)
});
// Andere Benutzer erhalten die Position über den node_updated Event
});
/* 8. Kontextmenü (optional) */
const setupContextMenu = () => {
cy.on('cxttap', 'node', function(e) {
const node = e.target;
const nodeData = node.data();
// Position des Kontextmenüs berechnen
const renderedPosition = node.renderedPosition();
const containerRect = cy.container().getBoundingClientRect();
const menuX = containerRect.left + renderedPosition.x;
const menuY = containerRect.top + renderedPosition.y;
// Kontextmenü erstellen oder aktualisieren
let contextMenu = document.getElementById('context-menu');
if (!contextMenu) {
contextMenu = document.createElement('div');
contextMenu.id = 'context-menu';
contextMenu.style.position = 'absolute';
contextMenu.style.backgroundColor = '#fff';
contextMenu.style.border = '1px solid #ccc';
contextMenu.style.borderRadius = '4px';
contextMenu.style.padding = '5px 0';
contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
contextMenu.style.zIndex = 1000;
document.body.appendChild(contextMenu);
}
// Menüinhalte
contextMenu.innerHTML = `
<div class="menu-item" data-action="edit">Knoten bearbeiten</div>
<div class="menu-item" data-action="connect">Verbindung erstellen</div>
<div class="menu-item" data-action="delete">Knoten löschen</div>
`;
// Styling für Menüpunkte
const menuItems = contextMenu.querySelectorAll('.menu-item');
menuItems.forEach(item => {
item.style.padding = '8px 20px';
item.style.cursor = 'pointer';
item.style.fontSize = '14px';
item.addEventListener('mouseover', function() {
this.style.backgroundColor = '#f0f0f0';
});
item.addEventListener('mouseout', function() {
this.style.backgroundColor = 'transparent';
});
// Event-Handler
item.addEventListener('click', async function() {
const action = this.getAttribute('data-action');
switch(action) {
case 'edit':
// Knoten bearbeiten (gleiche Logik wie beim Edit-Button)
const name = prompt('Knotenname:', nodeData.name);
if (name) {
const description = prompt('Beschreibung:', nodeData.description || '');
await post(`/api/mind_map_node/${nodeData.id}`, { name, description });
}
break;
case 'connect':
// Modus zum Verbinden aktivieren
cy.nodes().unselect();
node.select();
alert('Wählen Sie nun einen zweiten Knoten aus, um eine Verbindung zu erstellen');
break;
case 'delete':
if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
await del(`/api/mind_map_node/${nodeData.id}`);
}
break;
}
// Menü schließen
contextMenu.style.display = 'none';
});
});
// Menü positionieren und anzeigen
contextMenu.style.left = menuX + 'px';
contextMenu.style.top = menuY + 'px';
contextMenu.style.display = 'block';
// Event-Listener zum Schließen des Menüs
const closeMenu = function() {
if (contextMenu) {
contextMenu.style.display = 'none';
}
document.removeEventListener('click', closeMenu);
};
// Verzögerung, um den aktuellen Click nicht zu erfassen
setTimeout(() => {
document.addEventListener('click', closeMenu);
}, 0);
e.preventDefault();
});
};
// Kontextmenü aktivieren (optional)
// setupContextMenu();
/* 9. Export-Funktion (optional) */
const btnExport = document.getElementById('exportMindmap');
if (btnExport) {
btnExport.addEventListener('click', () => {
const elements = cy.json().elements;
const exportData = {
nodes: elements.nodes.map(n => ({
id: n.data.id,
name: n.data.name,
description: n.data.description,
category_id: n.data.category_id,
x: Math.round(n.position?.x || 0),
y: Math.round(n.position?.y || 0)
})),
relationships: elements.edges.map(e => ({
parent_id: e.data.source,
child_id: e.data.target
}))
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'mindmap_export.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
}
/* 10. Filter-Funktion nach Kategorien (optional) */
const setupCategoryFilters = () => {
const filterContainer = document.getElementById('category-filters');
if (!filterContainer || !categories.length) return;
filterContainer.innerHTML = '';
// "Alle anzeigen" Option
const allBtn = document.createElement('button');
allBtn.innerText = 'Alle Kategorien';
allBtn.className = 'category-filter active';
allBtn.onclick = () => {
document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
allBtn.classList.add('active');
cy.nodes().removeClass('filtered').show();
cy.edges().show();
};
filterContainer.appendChild(allBtn);
// Filter-Button pro Kategorie
categories.forEach(category => {
const btn = document.createElement('button');
btn.innerText = category.name;
btn.className = 'category-filter';
btn.style.backgroundColor = category.color_code;
btn.style.color = '#fff';
btn.onclick = () => {
document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
btn.classList.add('active');
const matchingNodes = cy.nodes().filter(node => node.data('category_id') === category.id);
cy.nodes().addClass('filtered').hide();
matchingNodes.removeClass('filtered').show();
// Verbindungen zu/von diesen Knoten anzeigen
cy.edges().hide();
matchingNodes.connectedEdges().show();
};
filterContainer.appendChild(btn);
});
};
// Filter-Funktionalität aktivieren (optional)
// setupCategoryFilters();
/* 11. Suchfunktion (optional) */
const searchInput = document.getElementById('search-mindmap');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
if (!searchTerm) {
cy.nodes().removeClass('search-hidden').show();
cy.edges().show();
return;
}
cy.nodes().forEach(node => {
const name = node.data('name').toLowerCase();
const description = (node.data('description') || '').toLowerCase();
if (name.includes(searchTerm) || description.includes(searchTerm)) {
node.removeClass('search-hidden').show();
node.connectedEdges().show();
} else {
node.addClass('search-hidden').hide();
// Kanten nur verstecken, wenn beide verbundenen Knoten versteckt sind
node.connectedEdges().forEach(edge => {
const otherNode = edge.source().id() === node.id() ? edge.target() : edge.source();
if (otherNode.hasClass('search-hidden')) {
edge.hide();
}
});
}
});
});
}
console.log('Mindmap erfolgreich initialisiert');
})();

View 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();
}
}
}
// Mache die Klasse global verfügbar
window.ChatGPTAssistant = ChatGPTAssistant;

View File

@@ -3,14 +3,29 @@
* Spezifische Funktionen für die Mindmap-Seite
*/
document.addEventListener('DOMContentLoaded', function() {
// Registriere den Initialisierer im MindMap-Objekt
if (window.MindMap) {
// Füge das Modul zum globalen MindMap-Objekt hinzu
if (!window.MindMap) {
window.MindMap = {};
}
// Registriere den Initialisierer im MindMap-Objekt
if (window.MindMap) {
window.MindMap.pageInitializers = window.MindMap.pageInitializers || {};
window.MindMap.pageInitializers.mindmap = initMindmapPage;
}
// Initialisiere die Mindmap-Seite nur, wenn alle Abhängigkeiten vorhanden sind
if (window.MindMap && typeof MindMapVisualization !== 'undefined') {
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
window.MindMap.pageInitializers = window.MindMap.pageInitializers || {};
window.MindMap.pageInitializers.mindmap = initMindmapPage;
initMindmapPage();
}
}
document.addEventListener('DOMContentLoaded', function() {
// Prüfe, ob wir auf der Mindmap-Seite sind und initialisiere
if (document.body.dataset.page === 'mindmap') {
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
initMindmapPage();
}
});
@@ -19,6 +34,10 @@ document.addEventListener('DOMContentLoaded', function() {
* Initialisiert die Mindmap-Seite
*/
function initMindmapPage() {
console.log('Mindmap-Seite Initialisierung startet...');
console.log('D3 Bibliothek verfügbar:', typeof d3 !== 'undefined');
console.log('MindMapVisualization verfügbar:', typeof MindMapVisualization !== 'undefined');
const mindmapContainer = document.getElementById('mindmap-container');
const thoughtsContainer = document.getElementById('thoughts-container');
@@ -26,6 +45,7 @@ function initMindmapPage() {
console.error('Mindmap-Container nicht gefunden!');
return;
}
console.log('Mindmap-Container gefunden:', mindmapContainer);
// Prüfe, ob D3.js geladen ist
if (typeof d3 === 'undefined') {
@@ -41,17 +61,47 @@ function initMindmapPage() {
return;
}
// Prüfe, ob MindMapVisualization definiert ist
if (typeof MindMapVisualization === 'undefined') {
console.error('MindMapVisualization-Klasse ist nicht definiert!');
mindmapContainer.innerHTML = `
<div class="glass-effect p-6 text-center">
<div class="text-red-500 mb-4">
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
</div>
<p class="text-xl">MindMap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
</div>
`;
return;
}
// Erstelle die Mindmap-Visualisierung
const mindmap = new MindMapVisualization('#mindmap-container', {
height: 600,
onNodeClick: handleNodeClick
});
// Globale Referenz für die Zoom-Buttons erstellen
window.mindmapInstance = mindmap;
// Lade die Mindmap-Daten
mindmap.loadData();
try {
console.log('Versuche, MindMapVisualization zu erstellen...');
const mindmap = new MindMapVisualization('#mindmap-container', {
height: 600,
onNodeClick: handleNodeClick
});
// Globale Referenz für die Zoom-Buttons erstellen
window.mindmapInstance = mindmap;
// Lade die Mindmap-Daten
mindmap.loadData();
console.log('MindMapVisualization erfolgreich erstellt und geladen');
} catch (error) {
console.error('Fehler beim Erstellen der MindMapVisualization:', error);
mindmapContainer.innerHTML = `
<div class="glass-effect p-6 text-center">
<div class="text-red-500 mb-4">
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
</div>
<p class="text-xl">Fehler beim Erstellen der Mindmap-Visualisierung:</p>
<p class="text-md mt-2">${error.message}</p>
</div>
`;
return;
}
// Suchfunktion für die Mindmap
const searchInput = document.getElementById('mindmap-search');
@@ -426,13 +476,13 @@ function initMindmapPage() {
// Erfolgsbenachrichtigung
if (window.MindMap && window.MindMap.showNotification) {
MindMap.showNotification('Gedanke erfolgreich gespeichert.', 'success');
window.MindMap.showNotification('Gedanke erfolgreich gespeichert.', 'success');
}
} catch (error) {
console.error('Fehler beim Speichern:', error);
if (window.MindMap && window.MindMap.showNotification) {
MindMap.showNotification('Fehler beim Speichern des Gedankens.', 'error');
window.MindMap.showNotification('Fehler beim Speichern des Gedankens.', 'error');
}
}
});
@@ -448,7 +498,7 @@ function initMindmapPage() {
}
/**
* Zeigt die Kommentare zu einem Gedanken an
* Füge globale Funktionen für das Mindmap-Objekt hinzu
*/
window.showComments = async function(thoughtId) {
try {
@@ -470,7 +520,11 @@ window.showComments = async function(thoughtId) {
} catch (error) {
console.error('Fehler beim Laden der Kommentare:', error);
MindMap.showNotification('Fehler beim Laden der Kommentare.', 'error');
if (window.MindMap && window.MindMap.showNotification) {
window.MindMap.showNotification('Fehler beim Laden der Kommentare.', 'error');
} else {
alert('Fehler beim Laden der Kommentare.');
}
}
};
@@ -497,7 +551,11 @@ window.showRelations = async function(thoughtId) {
} catch (error) {
console.error('Fehler beim Laden der Beziehungen:', error);
MindMap.showNotification('Fehler beim Laden der Beziehungen.', 'error');
if (window.MindMap && window.MindMap.showNotification) {
window.MindMap.showNotification('Fehler beim Laden der Beziehungen.', 'error');
} else {
alert('Fehler beim Laden der Beziehungen.');
}
}
};

View File

@@ -163,7 +163,7 @@ class MindMapVisualization {
// API-Aufruf mit kürzerem Timeout im Hintergrund durchführen
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout - reduced from 10
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout
const response = await fetch('/api/mindmap', {
signal: controller.signal,
@@ -175,8 +175,55 @@ class MindMapVisualization {
clearTimeout(timeoutId);
if (!response.ok) {
console.warn(`HTTP Fehler: ${response.status}, verwende Standarddaten`);
return; // Keep using default data
console.warn(`HTTP Fehler: ${response.status}, versuche erneute Verbindung`);
// Bei Verbindungsfehler versuchen, die Verbindung neu herzustellen
const retryResponse = await fetch('/api/refresh-mindmap', {
headers: {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
}
});
if (!retryResponse.ok) {
throw new Error(`Retry failed with status: ${retryResponse.status}`);
}
const retryData = await retryResponse.json();
if (!retryData.success || !retryData.nodes || retryData.nodes.length === 0) {
console.warn('Keine Mindmap-Daten nach Neuversuch, verwende weiterhin Standard-Daten.');
return; // Keep using default data
}
// Flache Liste von Knoten und Verbindungen erstellen
this.nodes = [];
this.links = [];
// Knoten direkt übernehmen
retryData.nodes.forEach(node => {
this.nodes.push({
id: node.id,
name: node.name,
description: node.description || '',
thought_count: node.thought_count || 0,
color: this.generateColorFromString(node.name),
});
// Verbindungen hinzufügen
if (node.connections && node.connections.length > 0) {
node.connections.forEach(conn => {
this.links.push({
source: node.id,
target: conn.target
});
});
}
});
// Visualisierung aktualisieren mit den tatsächlichen Daten
this.updateVisualization();
return;
}
const data = await response.json();
@@ -771,6 +818,26 @@ class MindMapVisualization {
this.updateNodeAppearance(d.id, isBookmarked);
});
}
/**
* Gibt alle direkt verbundenen Knoten eines Knotens zurück
* @param {Object} node - Der Knoten, für den die Verbindungen gesucht werden
* @returns {Array} Array der verbundenen Knotenobjekte
*/
getConnectedNodes(node) {
if (!node || !this.links || !this.nodes) return [];
const nodeId = node.id;
const connectedIds = new Set();
this.links.forEach(link => {
if (link.source === nodeId || (link.source && link.source.id === nodeId)) {
connectedIds.add(link.target.id ? link.target.id : link.target);
}
if (link.target === nodeId || (link.target && link.target.id === nodeId)) {
connectedIds.add(link.source.id ? link.source.id : link.source);
}
});
return this.nodes.filter(n => connectedIds.has(n.id));
}
}
// Exportiere die Klasse für die Verwendung in anderen Modulen

View File

@@ -477,6 +477,9 @@ class MindMapVisualization {
this.nodes = processed.nodes;
this.links = processed.links;
// Verbindungszählungen aktualisieren
this.updateConnectionCounts();
// Visualisierung aktualisieren
this.updateVisualization();
@@ -492,6 +495,9 @@ class MindMapVisualization {
this.nodes = this.defaultNodes;
this.links = this.defaultLinks;
// Verbindungszählungen auch für Fallback-Daten aktualisieren
this.updateConnectionCounts();
// Fehler anzeigen
this.showError('Mindmap-Daten konnten nicht geladen werden. Verwende Standarddaten.');
this.showFlash('Fehler beim Laden der Mindmap-Daten. Standarddaten werden angezeigt.', 'error');
@@ -936,7 +942,7 @@ class MindMapVisualization {
// Highlights für verbundene Nodes und Links hinzufügen
if (this.g) {
// Verbundene Nodes identifizieren
const connectedNodes = this.getConnectedNodes(d);
const connectedNodes = this.getConnectedNodesById(d.id);
const connectedNodeIds = connectedNodes.map(node => node.id);
// Alle Nodes etwas transparenter machen
@@ -1035,7 +1041,7 @@ class MindMapVisualization {
}
// Falls ein Node ausgewählt ist, den Highlight-Status für diesen beibehalten
else if (this.selectedNode && this.g) {
const connectedNodes = this.getConnectedNodes(this.selectedNode);
const connectedNodes = this.getConnectedNodesById(this.selectedNode.id);
const connectedNodeIds = connectedNodes.map(node => node.id);
// Alle Nodes auf den richtigen Highlight-Status setzen
@@ -1101,23 +1107,87 @@ class MindMapVisualization {
// Findet alle verbundenen Knoten zu einem gegebenen Knoten
getConnectedNodes(node) {
if (!this.links || !this.nodes) return [];
if (!this.links || !this.nodes || !node) return [];
// Sicherstellen, dass der Knoten eine ID hat
const nodeId = node.id || node;
return this.nodes.filter(n =>
this.links.some(link =>
(link.source.id === node.id && link.target.id === n.id) ||
(link.target.id === node.id && link.source.id === n.id)
)
this.links.some(link => {
const sourceId = link.source?.id || link.source;
const targetId = link.target?.id || link.target;
return (sourceId === nodeId && targetId === n.id) ||
(targetId === nodeId && sourceId === n.id);
})
);
}
// Prüft, ob zwei Knoten verbunden sind
isConnected(a, b) {
return this.links.some(link =>
(link.source.id === a.id && link.target.id === b.id) ||
(link.target.id === a.id && link.source.id === b.id)
if (!this.links || !a || !b) return false;
// Sicherstellen, dass die Knoten IDs haben
const aId = a.id || a;
const bId = b.id || b;
return this.links.some(link => {
const sourceId = link.source?.id || link.source;
const targetId = link.target?.id || link.target;
return (sourceId === aId && targetId === bId) ||
(targetId === aId && sourceId === bId);
});
}
// Überprüft, ob ein Link zwischen zwei Knoten existiert
hasLink(source, target) {
if (!this.links || !source || !target) return false;
// Sicherstellen, dass die Knoten IDs haben
const sourceId = source.id || source;
const targetId = target.id || target;
return this.links.some(link => {
const linkSourceId = link.source?.id || link.source;
const linkTargetId = link.target?.id || link.target;
return (linkSourceId === sourceId && linkTargetId === targetId) ||
(linkTargetId === sourceId && linkSourceId === targetId);
});
}
// Sicherere Methode zum Abrufen verbundener Knoten, die Prüfungen enthält
getConnectedNodesById(nodeId) {
if (!this.links || !this.nodes || !nodeId) return [];
return this.nodes.filter(n =>
this.links.some(link => {
const sourceId = link.source.id || link.source;
const targetId = link.target.id || link.target;
return (sourceId === nodeId && targetId === n.id) ||
(targetId === nodeId && sourceId === n.id);
})
);
}
// Aktualisiert die Verbindungszählungen für alle Knoten
updateConnectionCounts() {
if (!this.nodes || !this.links) return;
// Für jeden Knoten die Anzahl der Verbindungen berechnen
this.nodes.forEach(node => {
// Sichere Methode, um verbundene Knoten zu zählen
const connectedNodes = this.nodes.filter(n =>
n.id !== node.id && this.links.some(link => {
const sourceId = link.source.id || link.source;
const targetId = link.target.id || link.target;
return (sourceId === node.id && targetId === n.id) ||
(targetId === node.id && sourceId === n.id);
})
);
// Speichere die Anzahl als Eigenschaft des Knotens
node.connectionCount = connectedNodes.length;
});
}
// Klick-Handler für Knoten
nodeClicked(event, d) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

508
static/style.css Normal file
View File

@@ -0,0 +1,508 @@
/* Main Systades Styles - Dark Mystical Theme */
/* Import Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');
/* Root Variables */
:root {
/* Light Theme Colors */
--light-bg-primary: #f8fafc;
--light-bg-secondary: #f1f5f9;
--light-text-primary: #1e293b;
--light-text-secondary: #475569;
--light-accent-primary: #7c3aed;
--light-accent-secondary: #8b5cf6;
--light-border: #e2e8f0;
/* Dark Theme Colors */
--dark-bg-primary: #0a0e19;
--dark-bg-secondary: #111827;
--dark-text-primary: #f9fafb;
--dark-text-secondary: #e5e7eb;
--dark-accent-primary: #6d28d9;
--dark-accent-secondary: #8b5cf6;
--dark-border: #1f2937;
/* Common */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
/* Transitions */
--transition-fast: 150ms ease-in-out;
--transition-normal: 300ms ease-in-out;
--transition-slow: 500ms ease-in-out;
}
/* Base Elements */
body {
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color var(--transition-normal), color var(--transition-normal);
background-color: transparent !important; /* Ensure background is transparent */
}
/* HTML root element should also be transparent */
html {
background-color: transparent !important;
}
html.dark {
background-color: transparent !important;
}
/* Theme Specific - keep the color but remove background */
body {
color: var(--light-text-primary);
}
body.dark {
color: var(--dark-text-primary);
background-color: transparent;
}
/* Ensure proper contrast in both modes */
body:not(.dark) {
--text-primary: var(--light-text-primary);
--text-secondary: var(--light-text-secondary);
--bg-primary: var(--light-bg-primary);
--bg-secondary: var(--light-bg-secondary);
}
body.dark {
--text-primary: var(--dark-text-primary);
--text-secondary: var(--dark-text-secondary);
--bg-primary: var(--dark-bg-primary);
--bg-secondary: var(--dark-bg-secondary);
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
p {
line-height: 1.6;
}
.gradient-text {
background-clip: text;
-webkit-background-clip: text;
color: transparent;
position: relative;
}
body .gradient-text {
background-image: linear-gradient(135deg, var(--light-accent-primary), var(--light-accent-secondary));
}
body.dark .gradient-text {
background-image: linear-gradient(135deg, var(--dark-accent-primary), var(--dark-accent-secondary));
}
/* Subtle glow for dark mode gradient text */
body.dark .gradient-text::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
filter: blur(8px);
opacity: 0.3;
background-image: inherit;
z-index: -1;
pointer-events: none;
}
/* Containers and Layout */
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: 1rem;
padding-right: 1rem;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
/* Glass Morphism */
.glass-morphism {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
body .glass-navbar-light {
background-color: rgba(255, 255, 255, 0.8);
border-color: rgba(226, 232, 240, 0.5);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
}
body.dark .glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.8);
border-color: rgba(31, 41, 55, 0.5);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
/* Navigation */
.nav-link {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: all var(--transition-normal);
}
body .nav-link {
color: var(--light-text-secondary);
}
body.dark .nav-link {
color: var(--dark-text-secondary);
}
body .nav-link:hover {
color: var(--light-text-primary);
background-color: rgba(241, 245, 249, 0.5);
}
body.dark .nav-link:hover {
color: var(--dark-text-primary);
background-color: rgba(31, 41, 55, 0.5);
}
body .nav-link-light-active {
color: var(--light-accent-primary);
background-color: rgba(139, 92, 246, 0.1);
}
body.dark .nav-link-active {
color: var(--dark-accent-secondary);
background-color: rgba(109, 40, 217, 0.15);
}
/* Buttons */
.btn {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all var(--transition-normal);
display: inline-flex;
align-items: center;
justify-content: center;
}
body .btn-primary {
background-color: var(--light-accent-primary);
color: white;
}
body.dark .btn-primary {
background-color: var(--dark-accent-primary);
color: white;
}
body .btn-primary:hover {
background-color: var(--light-accent-secondary);
box-shadow: 0 0 15px rgba(124, 58, 237, 0.3);
}
body.dark .btn-primary:hover {
background-color: var(--dark-accent-secondary);
box-shadow: 0 0 15px rgba(109, 40, 217, 0.5);
}
body .btn-secondary {
background-color: transparent;
border: 1px solid var(--light-border);
color: var(--light-text-primary);
}
body.dark .btn-secondary {
background-color: transparent;
border: 1px solid var(--dark-border);
color: var(--dark-text-primary);
}
body .btn-secondary:hover {
background-color: var(--light-bg-secondary);
border-color: var(--light-accent-secondary);
}
body.dark .btn-secondary:hover {
background-color: var(--dark-bg-secondary);
border-color: var(--dark-accent-secondary);
}
/* Cards */
.card {
border-radius: 0.75rem;
overflow: hidden;
transition: all var(--transition-normal);
}
body .card {
background-color: white;
border: 1px solid var(--light-border);
box-shadow: var(--shadow);
}
body.dark .card {
background-color: var(--dark-bg-secondary);
border: 1px solid var(--dark-border);
box-shadow: var(--shadow);
}
body .card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-md);
}
body.dark .card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
/* Form Elements */
.form-input {
width: 100%;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: all var(--transition-normal);
}
body .form-input {
background-color: white;
border: 1px solid var(--light-border);
color: var(--light-text-primary);
}
body.dark .form-input {
background-color: var(--dark-bg-secondary);
border: 1px solid var(--dark-border);
color: var(--dark-text-primary);
}
body .form-input:focus {
outline: none;
border-color: var(--light-accent-secondary);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
}
body.dark .form-input:focus {
outline: none;
border-color: var(--dark-accent-secondary);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.25);
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.animate-float {
animation: float 5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
.animate-pulse {
animation: pulse 3s ease-in-out infinite;
}
/* Utilities */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.shadow-elevation {
transition: box-shadow var(--transition-normal), transform var(--transition-normal);
}
body .shadow-elevation {
box-shadow: var(--shadow);
}
body.dark .shadow-elevation {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
body .shadow-elevation:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
body.dark .shadow-elevation:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
transform: translateY(-2px);
}
/* Tooltips */
.tooltip {
position: relative;
}
.tooltip:hover::before {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
white-space: nowrap;
z-index: 10;
opacity: 0;
animation: fadeIn 0.3s ease-out forwards;
}
body .tooltip:hover::before {
background-color: var(--light-text-primary);
color: white;
}
body.dark .tooltip:hover::before {
background-color: var(--dark-text-primary);
color: var(--dark-bg-primary);
}
/* Mystical elements */
.mystical-border {
position: relative;
overflow: hidden;
}
.mystical-border::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid;
border-radius: inherit;
pointer-events: none;
opacity: 0.3;
transition: opacity var(--transition-normal);
}
body .mystical-border::after {
border-color: var(--light-accent-primary);
}
body.dark .mystical-border::after {
border-color: var(--dark-accent-primary);
}
.mystical-border:hover::after {
opacity: 0.6;
}
/* Responsive Design Helpers */
@media (max-width: 640px) {
.container {
padding-left: 1rem;
padding-right: 1rem;
}
.hero-heading {
font-size: 2rem;
}
.section-heading {
font-size: 1.5rem;
}
}
/* Accessibility */
:focus-visible {
outline: 2px solid;
outline-offset: 2px;
}
body :focus-visible {
outline-color: var(--light-accent-primary);
}
body.dark :focus-visible {
outline-color: var(--dark-accent-primary);
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
body ::-webkit-scrollbar-track {
background: var(--light-bg-secondary);
}
body.dark ::-webkit-scrollbar-track {
background: var(--dark-bg-secondary);
}
body ::-webkit-scrollbar-thumb {
background: var(--light-accent-primary);
border-radius: 0.25rem;
}
body.dark ::-webkit-scrollbar-thumb {
background: var(--dark-accent-primary);
border-radius: 0.25rem;
}
body ::-webkit-scrollbar-thumb:hover {
background: var(--light-accent-secondary);
}
body.dark ::-webkit-scrollbar-thumb:hover {
background: var(--dark-accent-secondary);
}

6
static/three.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -14,9 +14,11 @@
<meta name="keywords" content="systades, wissen, visualisierung, lernen, gedanken, theorie">
<meta name="author" content="Systades-Team">
<!-- Tailwind CSS über CDN -->
<!-- Tailwind CSS - CDN für Entwicklung und Produktion (laut Vorgabe) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Alternative lokale Version, falls die CDN-Version blockiert wird -->
<script>
tailwind = window.tailwind || {};
tailwind.config = {
darkMode: 'class',
theme: {
@@ -63,13 +65,12 @@
}
</script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<!-- Local Font Files -->
<link href="{{ url_for('static', filename='fonts/inter.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='fonts/jetbrains-mono.css') }}" rel="stylesheet">
<!-- Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Icons - Self-hosted Font Awesome -->
<link href="{{ url_for('static', filename='css/all.min.css') }}" rel="stylesheet">
<!-- Assistent CSS -->
<link href="{{ url_for('static', filename='css/assistant.css') }}" rel="stylesheet">
@@ -80,89 +81,129 @@
<!-- Base-Styles ausgelagert in eigene Datei -->
<link href="{{ url_for('static', filename='css/base-styles.css') }}" rel="stylesheet">
<!-- Alpine.js -->
<!-- Alpine.js - CDN Version -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js"></script>
<!-- Network Background Script -->
<script src="{{ url_for('static', filename='network-background.js') }}"></script>
<!-- Neural Network Background CSS -->
<link href="{{ url_for('static', filename='css/neural-network-background.css') }}" rel="stylesheet">
<!-- D3.js für Visualisierungen -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Hauptmodul laden (als ES6 Modul) -->
<script type="module">
import MindMap from "{{ url_for('static', filename='js/main.js') }}";
// Alpine.js-Integration
document.addEventListener('alpine:init', () => {
Alpine.data('layout', () => ({
darkMode: false,
mobileMenuOpen: false,
userMenuOpen: false,
showSettingsModal: false,
init() {
this.fetchDarkModeFromSession();
},
fetchDarkModeFromSession() {
// Lade den Dark Mode-Status vom Server
fetch('/get_dark_mode')
.then(response => response.json())
.then(data => {
if (data.success) {
this.darkMode = data.darkMode === 'true';
document.querySelector('html').classList.toggle('dark', this.darkMode);
}
})
.catch(error => {
console.error('Fehler beim Laden der Dark Mode-Einstellung:', error);
});
},
toggleDarkMode() {
this.darkMode = !this.darkMode;
document.querySelector('html').classList.toggle('dark', this.darkMode);
// Speichere den Dark Mode-Status auf dem Server
fetch('/set_dark_mode', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ darkMode: this.darkMode })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Zusätzlich im localStorage speichern für sofortige Reaktion bei Seitenwechsel
localStorage.setItem('darkMode', this.darkMode ? 'dark' : 'light');
// Event auslösen für andere Komponenten
document.dispatchEvent(new CustomEvent('darkModeToggled', {
detail: { isDark: this.darkMode }
}));
} else {
console.error('Fehler beim Speichern der Dark Mode-Einstellung:', data.error);
}
})
.catch(error => {
console.error('Fehler beim Speichern der Dark Mode-Einstellung:', error);
});
}
}));
});
// MindMap global verfügbar machen (für Alpine.js und andere nicht-Module Skripte)
window.MindMap = MindMap;
</script>
<!-- Marked.js für Markdown-Parsing -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- ChatGPT Assistant -->
<script src="{{ url_for('static', filename='js/modules/chatgpt-assistant.js') }}"></script>
<!-- MindMap Visualization Module -->
<script src="{{ url_for('static', filename='js/modules/mindmap.js') }}"></script>
<!-- MindMap Page Module -->
<script src="{{ url_for('static', filename='js/modules/mindmap-page.js') }}"></script>
<!-- Neural Network Background Script -->
<script src="{{ url_for('static', filename='neural-network-background.js') }}"></script>
<!-- Hauptmodul laden (als traditionelles Skript) -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<!-- Seitenspezifische Styles -->
{% block extra_css %}{% endblock %}
</head>
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden">
<!-- Globaler Hintergrund -->
<div class="full-page-bg"></div>
<!-- Statischer Fallback-Hintergrund (wird nur angezeigt, wenn JavaScript deaktiviert ist) -->
<div class="fixed inset-0 z-[-9] bg-cover bg-center opacity-50"></div>
<!-- Custom dark mode styles -->
<!-- ► ► FarbToken strikt getrennt ◄ ◄ -->
<style>
/* LightMode */
:root {
--bg-primary:#f4f6fa;
--bg-secondary:#e9ecf3;
--text-primary:#232837;
--text-secondary:#475569;
--accent-primary:#7c3aed;
--accent-secondary:#8b5cf6;
--glow-effect:0 0 8px rgba(139,92,246,.08);
}
/* DarkMode */
.dark {
--bg-primary:#181c24;
--bg-secondary:#232837;
--text-primary:#f9fafb;
--text-secondary:#e5e7eb;
--accent-primary:#6d28d9;
--accent-secondary:#8b5cf6;
--glow-effect:0 0 8px rgba(124,58,237,.15);
}
body {
@apply min-h-screen bg-[color:var(--bg-primary)] text-[color:var(--text-primary)] transition-colors duration-300;
}
/* Utilities */
.mystical-glow { text-shadow: var(--glow-effect); }
.gradient-text {
background:linear-gradient(135deg,var(--accent-primary),var(--accent-secondary));
-webkit-background-clip:text; background-clip:text; color:transparent; text-shadow:none;
}
.glass-morphism { backdrop-filter: blur(10px); }
.glass-navbar { @apply glass-morphism border backdrop-blur-xl; }
.light .glass-navbar { background-color:rgba(255,255,255,.8); border-color:rgba(0,0,0,.05); }
.dark .glass-navbar { background-color:rgba(10,14,25,.8); border-color:rgba(255,255,255,.05); }
</style>
</head>
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark bg-gray-900 text-white" x-data="{
darkMode: true,
mobileMenuOpen: false,
userMenuOpen: false,
showSettingsModal: false,
init() {
this.fetchDarkModeFromSession();
},
fetchDarkModeFromSession() {
fetch('/api/get_dark_mode')
.then(response => response.json())
.then(data => {
if (data.success) {
this.darkMode = data.darkMode === 'true';
document.querySelector('html').classList.toggle('dark', this.darkMode);
}
})
.catch(error => {
console.error('Fehler beim Laden der Dark Mode-Einstellung:', error);
});
},
toggleDarkMode() {
this.darkMode = !this.darkMode;
document.querySelector('html').classList.toggle('dark', this.darkMode);
fetch('/api/set_dark_mode', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ darkMode: this.darkMode })
})
.then(response => response.json())
.then(data => {
if (data.success) {
localStorage.setItem('darkMode', this.darkMode ? 'dark' : 'light');
document.dispatchEvent(new CustomEvent('darkModeToggled', {
detail: { isDark: this.darkMode }
}));
} else {
console.error('Fehler beim Speichern der Dark Mode-Einstellung:', data.error);
}
})
.catch(error => {
console.error('Fehler beim Speichern der Dark Mode-Einstellung:', error);
});
}
}">
<!-- App-Container -->
<div id="app-container" class="flex flex-col min-h-screen" x-data="layout">
<div id="app-container" class="flex flex-col min-h-screen">
<!-- Hauptnavigation -->
<nav class="sticky top-0 left-0 right-0 z-50 transition-all duration-300 py-4 px-5 border-b glass-morphism"
x-bind:class="darkMode ? 'glass-navbar-dark border-white/10' : 'glass-navbar-light border-gray-200/50'">
@@ -199,8 +240,8 @@
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.toggleAssistant(true)"
class="nav-link flex items-center"
x-bind:class="darkMode
? 'bg-gradient-to-r from-purple-600/80 to-blue-500/80 text-white font-medium px-4 py-2 rounded-xl hover:shadow-lg transition-all duration-300 hover:-translate-y-0.5'
: 'bg-gradient-to-r from-purple-500/20 to-blue-400/20 text-gray-800 font-medium px-4 py-2 rounded-xl hover:shadow-md transition-all duration-300 hover:-translate-y-0.5'">
? 'bg-gradient-to-r from-purple-900/90 to-indigo-800/90 text-white font-medium px-4 py-2 rounded-xl hover:shadow-lg hover:shadow-purple-800/30 transition-all duration-300'
: 'bg-gradient-to-r from-purple-600/30 to-indigo-500/30 text-gray-800 font-medium px-4 py-2 rounded-xl hover:shadow-md transition-all duration-300'">
<i class="fa-solid fa-robot mr-2"></i>KI-Chat
</button>
{% if current_user.is_authenticated %}
@@ -221,9 +262,9 @@
<div class="relative w-12 h-6">
<input type="checkbox" id="darkModeToggle" class="sr-only" x-model="darkMode">
<div class="block w-12 h-6 rounded-full transition-colors duration-300"
x-bind:class="darkMode ? 'bg-blue-400/50' : 'bg-gray-400/50'"></div>
x-bind:class="darkMode ? 'bg-purple-800/50' : 'bg-gray-400/50'"></div>
<div class="dot absolute left-1 top-1 w-4 h-4 rounded-full transition-transform duration-300 shadow-md"
x-bind:class="darkMode ? 'bg-blue-500 transform translate-x-6' : 'bg-white'"></div>
x-bind:class="darkMode ? 'bg-purple-600 transform translate-x-6' : 'bg-white'"></div>
</div>
<div class="ml-3 hidden sm:block"
x-bind:class="darkMode ? 'text-white/90' : 'text-gray-700'">
@@ -301,13 +342,22 @@
</div>
</div>
{% else %}
<a href="{{ url_for('login') }}"
class="flex items-center px-4 py-2.5 rounded-xl font-medium transition-all duration-300"
x-bind:class="darkMode
? 'bg-gray-800/80 text-white hover:bg-gray-700/80 shadow-md hover:shadow-lg hover:-translate-y-0.5'
: 'bg-gray-200/80 text-gray-800 hover:bg-gray-300/80 shadow-sm hover:shadow-md hover:-translate-y-0.5'">
<i class="fa-solid fa-user mr-2"></i>Mein Konto
</a>
<div class="flex items-center space-x-2">
<a href="{{ url_for('login') }}"
class="py-2 px-4 rounded-lg transition-all duration-300"
x-bind:class="darkMode
? 'text-white/90 hover:bg-dark-700/80'
: 'text-gray-700 hover:bg-gray-100/80'">
<i class="fa-solid fa-sign-in-alt mr-2"></i>Login
</a>
<a href="{{ url_for('register') }}"
class="py-2 px-4 rounded-lg transition-all duration-300 font-medium"
x-bind:class="darkMode
? 'bg-purple-800/80 text-white hover:bg-purple-700/80'
: 'bg-purple-600/20 text-gray-700 hover:bg-purple-600/30'">
Registrieren
</a>
</div>
{% endif %}
<!-- Mobilmenü-Button -->
@@ -324,6 +374,7 @@
<!-- Mobile Menü -->
<div x-show="mobileMenuOpen"
x-cloak
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0"
@@ -455,6 +506,10 @@
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Impressum
</a>
<a href="{{ url_for('ueber_uns') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Über uns
</a>
<a href="{{ url_for('datenschutz') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Datenschutz
@@ -504,11 +559,9 @@
{% block scripts %}{% endblock %}
<!-- KI-Chat Initialisierung -->
<script type="module">
// Importiere und initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
<script>
// Initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
// dass er auf jeder Seite verfügbar ist, selbst wenn MindMap nicht geladen ist
import ChatGPTAssistant from "{{ url_for('static', filename='js/modules/chatgpt-assistant.js') }}";
document.addEventListener('DOMContentLoaded', function() {
// Prüfen, ob der Assistent bereits durch MindMap initialisiert wurde
if (!window.MindMap || !window.MindMap.assistant) {
@@ -524,5 +577,38 @@
}
});
</script>
<!-- Dark/Light-Mode persistent und robust -->
<script>
(function() {
function applyMode(mode) {
if (mode === 'dark') {
document.documentElement.classList.add('dark');
localStorage.setItem('colorMode', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('colorMode', 'light');
}
}
// Beim Laden: Präferenz aus localStorage oder System übernehmen
const stored = localStorage.getItem('colorMode');
if (stored === 'dark' || stored === 'light') {
applyMode(stored);
} else {
// Systempräferenz als Fallback
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
applyMode(prefersDark ? 'dark' : 'light');
}
// Umschalter für alle Mode-Toggles
window.toggleColorMode = function() {
const isDark = document.documentElement.classList.contains('dark');
applyMode(isDark ? 'light' : 'dark');
};
// Optional: globales Event für andere Skripte
window.addEventListener('storage', function(e) {
if (e.key === 'colorMode') applyMode(e.newValue);
});
})();
</script>
</body>
</html>

33
templates/errors/403.html Normal file
View 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
View 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
View 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
View 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 %}

64
templates/impressum.html Normal file
View File

@@ -0,0 +1,64 @@
{% extends "base.html" %}
{% block title %}Impressum{% endblock %}
{% block content %}
<div class="max-w-3xl mx-auto">
<div class="card p-6 md:p-8">
<h1 class="text-3xl font-bold mb-6 gradient-text">Impressum</h1>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Angaben gemäß § 5 TMG und § 55 RStV</h2>
<p class="mb-4">
Diese Website wird privat betrieben von:<br>
Marwin Medczinski<br>
Fasanenstraße 30<br>
16761 Hennigsdorf<br>
Deutschland
</p>
</p>
<p class="mb-4">
<strong>Kontakt:</strong><br>
Telefon: +49 (0) 173 8041824<br>
E-Mail: medczinski.marwin@gmx.de
</p>
</section>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Inhaltlich Verantwortlicher gemäß § 55 Abs. 2 RStV</h2>
<p class="mb-4">
Marwin Medczinski<br>
Fasanenstraße 30<br>
16761 Hennigsdorf
</p>
</section>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Hinweis zur Streitbeilegung</h2>
<p class="mb-4">Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: <a href="https://ec.europa.eu/consumers/odr/" target="_blank" class="text-purple-600 hover:text-purple-800">https://ec.europa.eu/consumers/odr/</a></p>
<p class="mb-4">Ich bin nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.</p>
</section>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Haftungsausschluss</h2>
<h3 class="text-lg font-bold mb-2">Haftung für Inhalte</h3>
<p class="mb-4">Die Inhalte dieser Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte kann ich jedoch keine Gewähr übernehmen. Als Diensteanbieter bin ich gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG bin ich als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.</p>
<p class="mb-4">Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werde ich diese Inhalte umgehend entfernen.</p>
<h3 class="text-lg font-bold mb-2">Haftung für Links</h3>
<p class="mb-4">Diese Website enthält Links zu externen Webseiten Dritter, auf deren Inhalte ich keinen Einfluss habe. Deshalb kann ich für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar.</p>
<p class="mb-4">Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Links umgehend entfernen.</p>
</section>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Urheberrecht</h2>
<p class="mb-4">Die durch mich erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen meiner schriftlichen Zustimmung. Downloads und Kopien dieser Seite sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.</p>
<p>Soweit die Inhalte auf dieser Seite nicht von mir erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitte ich um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Inhalte umgehend entfernen.</p>
</section>
</div>
</div>
{% endblock %}

520
templates/index.html Normal file
View File

@@ -0,0 +1,520 @@
{% extends "base.html" %}
{% block title %}Wissensnetzwerk{% endblock %}
{% block extra_css %}
<style>
/* Full height and width for the page */
html, body {
min-height: 100vh;
width: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
/* Remove gradient backgrounds */
.hero-gradient, .bg-fade {
background: none !important;
clip-path: none !important;
}
/* Style elements */
.mystical-line {
height: 1px;
background: linear-gradient(to right, transparent, rgba(109, 40, 217, 0.2), transparent);
}
.dark .mystical-line {
background: linear-gradient(to right, transparent, rgba(139, 92, 246, 0.2), transparent);
}
/* Text reveal animation */
@keyframes textReveal {
0% { clip-path: polygon(0 0, 0 0, 0 100%, 0 100%); }
100% { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }
}
.text-reveal {
animation: textReveal 1s cubic-bezier(0.77, 0, 0.18, 1) forwards;
}
.delay-1 { animation-delay: 0.2s; }
.delay-2 { animation-delay: 0.4s; }
.delay-3 { animation-delay: 0.6s; }
/* Home page specific styles */
.featured-card {
transition: transform 0.5s ease, box-shadow 0.5s ease;
border: 1px solid;
border-color: rgba(139, 92, 246, 0.1);
}
.dark .featured-card {
border-color: rgba(109, 40, 217, 0.2);
background-color: rgba(17, 24, 39, 0.7);
}
.featured-card:hover {
transform: translateY(-5px);
}
.dark .featured-card:hover {
box-shadow: 0 5px 15px rgba(109, 40, 217, 0.2);
border-color: rgba(109, 40, 217, 0.3);
}
.featured-card:hover {
box-shadow: 0 5px 15px rgba(139, 92, 246, 0.1);
border-color: rgba(139, 92, 246, 0.2);
}
/* Chat section styles */
.embedded-chat {
height: 350px;
border-radius: 1rem;
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid;
}
.dark .embedded-chat {
background-color: rgba(17, 24, 39, 0.7);
border-color: rgba(109, 40, 217, 0.2);
}
.embedded-chat {
background-color: rgba(255, 255, 255, 0.7);
border-color: rgba(139, 92, 246, 0.1);
}
#embedded-chat-messages {
height: 250px;
overflow-y: auto;
padding: 1rem;
}
/* Chat typing indicator */
.typing-dots span {
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
margin-right: 3px;
background-color: currentColor;
opacity: 0.5;
}
.typing-dots span:nth-child(1) {
animation: dot-pulse 1.2s infinite ease-in-out;
}
.typing-dots span:nth-child(2) {
animation: dot-pulse 1.2s infinite ease-in-out 0.2s;
}
.typing-dots span:nth-child(3) {
animation: dot-pulse 1.2s infinite ease-in-out 0.4s;
}
@keyframes dot-pulse {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.3); opacity: 1; }
}
</style>
{% endblock %}
{% block content %}
<!-- Hero Section -->
<section class="relative pt-20 pb-24">
<!-- Hero Content -->
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="text-center mb-16">
<h1 class="hero-heading mb-8 text-gray-900 dark:text-white">
<div class="overflow-hidden">
<span class="gradient-text inline-block text-reveal">Wissen</span>
</div>
<div class="overflow-hidden mt-2">
<span class="inline-block text-reveal delay-1">neu</span>
</div>
<div class="mt-2 relative overflow-hidden">
<span class="relative inline-block text-reveal delay-2">vernetzen
<div class="absolute -bottom-2 left-0 right-0 h-1 bg-gradient-to-r from-purple-500/0 via-purple-500/70 to-purple-500/0 rounded-full"></div>
</span>
</div>
</h1>
<div class="overflow-hidden">
<p class="text-xl md:text-2xl text-gray-700 dark:text-gray-300 max-w-3xl mx-auto mb-12 text-reveal delay-3">
Erkunde komplexe Ideen visuell, schaffe Verbindungen und teile deine Gedanken
in einem interaktiven Wissensnetzwerk.
</p>
</div>
<div class="flex flex-col sm:flex-row gap-5 justify-center">
<a href="{{ url_for('mindmap') }}" class="mystical-button mystical-button-primary group transition-all duration-300">
<span class="flex items-center justify-center">
<i class="fa-solid fa-diagram-project mr-3 text-purple-200 group-hover:text-white transition-all duration-300"></i>
<span class="relative">
Mindmap erkunden
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-white group-hover:w-full transition-all duration-300"></span>
</span>
</span>
</a>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="mystical-button mystical-button-secondary group transition-all duration-300">
<span class="flex items-center justify-center">
<i class="fa-solid fa-user-plus mr-3 group-hover:text-accent-secondary-dark dark:group-hover:text-accent-secondary-light transition-all duration-300"></i>
<span class="relative">
Konto erstellen
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-accent-primary-light dark:bg-accent-primary-dark group-hover:w-full transition-all duration-300"></span>
</span>
</span>
</a>
{% endif %}
</div>
</div>
<!-- Central logo and name -->
<div class="relative w-full max-w-4xl mx-auto h-40 sm:h-60 mb-16">
<div class="absolute inset-0 flex items-center justify-center">
<div class="text-center">
<div class="text-3xl font-bold gradient-text mb-2 animate-float">Systades</div>
<div class="text-lg text-gray-700 dark:text-gray-300">WISSEN VERNETZEN</div>
</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-16">
<h2 class="section-heading mb-4 text-gray-900 dark:text-white">Was ist <span class="gradient-text">Systades?</span></h2>
<p class="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
Ein modernes Werkzeug zum Visualisieren, Erforschen und Teilen von Wissen in einem interaktiven
Netzwerk, das dir hilft, Verbindungen zu entdecken und dein Wissen zu organisieren.
</p>
</div>
<!-- Feature Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
<!-- Feature 1: Visualize -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm">
<div class="mb-4 text-purple-600 dark:text-purple-400">
<i class="fa-solid fa-diagram-project text-3xl"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Visualisiere Wissen</h3>
<p class="text-gray-700 dark:text-gray-300">
Organisiere Gedanken und Ideen in einem interaktiven Netzwerk, das komplexe Beziehungen
visuell darstellt und neue Verbindungen offenbart.
</p>
</div>
<!-- Feature 2: Connect -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm">
<div class="mb-4 text-indigo-600 dark:text-indigo-400">
<i class="fa-solid fa-network-wired text-3xl"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Verknüpfe Gedanken</h3>
<p class="text-gray-700 dark:text-gray-300">
Entdecke Zusammenhänge zwischen scheinbar unzusammenhängenden Ideen und schaffe dein
persönliches Wissensnetzwerk über verschiedene Bereiche hinweg.
</p>
</div>
<!-- Feature 3: Share -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm">
<div class="mb-4 text-purple-600 dark:text-purple-400">
<i class="fa-solid fa-share-nodes text-3xl"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Teile und Entdecke</h3>
<p class="text-gray-700 dark:text-gray-300">
Tausche Erkenntnisse mit anderen aus und erweitere dein Wissen durch die
Perspektiven und Gedanken der Community.
</p>
</div>
</div>
</div>
</section>
<!-- AI Assistant Preview -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="section-heading mb-4 text-gray-900 dark:text-white">Dein <span class="gradient-text">KI-Assistent</span></h2>
<p class="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
Unser integrierter KI-Assistent hilft dir, Wissen zu organisieren, Verbindungen zu finden und
Einsichten zu gewinnen.
</p>
</div>
<!-- Chat Interface Preview -->
<div class="max-w-3xl mx-auto">
<div class="embedded-chat" id="demo-chat">
<!-- Chat Header -->
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-3">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<span class="font-medium text-gray-800 dark:text-gray-200">Systades Assistent (4o-mini)</span>
</div>
<div>
<button id="open-real-assistant" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<i class="fa-solid fa-expand"></i>
</button>
</div>
</div>
<!-- Chat Messages -->
<div id="embedded-chat-messages" class="border-b border-gray-200 dark:border-gray-700">
<!-- Assistant Message -->
<div class="mb-4 flex">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<div class="text-gray-700 dark:text-gray-300 markdown-content">
<p>Hallo! Ich bin dein Systades-Assistent. Wie kann ich dir heute helfen?</p>
<p>Du kannst mir Fragen zu:</p>
<ul>
<li><strong>Gedanken</strong> in der Datenbank</li>
<li><strong>Kategorien</strong> und Wissensgebieten</li>
<li><strong>Mindmaps</strong> und Visualisierungsmöglichkeiten</li>
</ul>
<p>stellen.</p>
</div>
</div>
</div>
<!-- User Message -->
<div class="mb-4 flex justify-end">
<div class="bg-purple-100 dark:bg-purple-900/30 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-800 dark:text-gray-200">
Kann ich mit deiner Hilfe eine Mindmap zum Thema Künstliche Intelligenz erstellen?
</p>
</div>
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-gray-700 dark:text-gray-300 ml-2 flex-shrink-0">
<i class="fa-solid fa-user text-sm"></i>
</div>
</div>
<!-- Assistant Response -->
<div class="mb-4 flex" id="demo-ai-response">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<div class="text-gray-700 dark:text-gray-300 markdown-content">
<p>Ja, natürlich! Ich kann dir dabei helfen, eine Mindmap zum Thema <strong>Künstliche Intelligenz</strong> zu erstellen.</p>
<p>Du kannst wie folgt vorgehen:</p>
<ol>
<li>Gehe zur <strong>Mindmap</strong>-Ansicht</li>
<li>Suche nach dem Knoten "Künstliche Intelligenz" unter der Kategorie "Technologie"</li>
<li>Füge diesen Knoten zu deiner persönlichen Mindmap hinzu</li>
<li>Ergänze verwandte Themen wie <em>Machine Learning, Neural Networks oder Data Science</em></li>
</ol>
<p>Soll ich dir noch mehr spezifische Informationen zu KI-Teilgebieten geben?</p>
</div>
</div>
</div>
</div>
<!-- Chat Input -->
<div class="p-4">
<div class="flex">
<input type="text" placeholder="Stelle eine Frage..." class="mystical-input flex-grow" disabled>
<button class="ml-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-2 rounded-lg disabled:opacity-50" disabled>
<i class="fa-solid fa-paper-plane"></i>
</button>
</div>
<!-- Quick Queries -->
<div class="mt-3 flex flex-wrap gap-2">
<span class="text-xs text-gray-500 dark:text-gray-400 mr-1">Beispiele:</span>
<button data-question="Was sind die wichtigsten Grundlagen der Künstlichen Intelligenz?" class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer transition-colors">KI-Grundlagen</button>
<button data-question="Wie kann ich eine Mindmap zum Thema Neuronale Netzwerke erstellen?" class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer transition-colors">Mindmap erstellen</button>
<button data-question="Zeige mir alle verfügbaren Kategorien in der Datenbank" class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer transition-colors">Datenbank durchsuchen</button>
</div>
</div>
</div>
</div>
<!-- Try it Button -->
<div class="text-center mt-10">
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.sendQuestion('Hallo! Ich möchte mehr über die Funktionen der Wissensdatenbank erfahren. Was kann ich hier alles machen?')"
class="mystical-button mystical-button-primary inline-flex items-center">
<i class="fa-solid fa-robot mr-2"></i>
KI-Assistenten ausprobieren
</button>
</div>
</div>
</section>
<!-- Getting Started Section -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="section-heading mb-4 text-gray-900 dark:text-white">So <span class="gradient-text">funktioniert's</span></h2>
<p class="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
In wenigen einfachen Schritten kannst du mit Systades beginnen, dein Wissen zu organisieren.
</p>
</div>
<!-- Step Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Step 1 -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm relative overflow-hidden">
<div class="absolute top-4 right-4 w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center text-purple-600 dark:text-purple-400 font-bold">
1
</div>
<h3 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Konto erstellen</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Registriere dich für ein kostenloses Konto, um deine persönliche Wissenslandschaft zu erstellen.
</p>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Jetzt registrieren <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
{% endif %}
</div>
<!-- Step 2 -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm relative overflow-hidden">
<div class="absolute top-4 right-4 w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center text-purple-600 dark:text-purple-400 font-bold">
2
</div>
<h3 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Mindmap erkunden</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Entdecke die öffentliche Wissensmindmap und füge Knoten zu deiner persönlichen Landschaft hinzu.
</p>
<a href="{{ url_for('mindmap') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Zur Mindmap <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
</div>
<!-- Step 3 -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm relative overflow-hidden">
<div class="absolute top-4 right-4 w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center text-purple-600 dark:text-purple-400 font-bold">
3
</div>
<h3 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Gedanken teilen</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Teile deine eigenen Gedanken, verbinde sie mit vorhandenen Knoten und baue das kollektive Wissen aus.
</p>
{% if current_user.is_authenticated %}
<a href="{{ url_for('profile') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Zum Profil <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
{% else %}
<a href="{{ url_for('login') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Jetzt anmelden <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
{% endif %}
</div>
</div>
</div>
</section>
<!-- Call to Action -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 class="section-heading mb-6 text-gray-900 dark:text-white">Bereit, dein <span class="gradient-text">Wissen zu vernetzen</span>?</h2>
<p class="text-lg text-gray-700 dark:text-gray-300 mb-8">
Tritt unserer wachsenden Community bei und entdecke eine neue Art, Wissen zu organisieren und zu teilen.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('mindmap') }}" class="mystical-button mystical-button-primary">
<i class="fa-solid fa-diagram-project mr-2"></i> Mindmap erkunden
</a>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="mystical-button mystical-button-secondary">
<i class="fa-solid fa-user-plus mr-2"></i> Konto erstellen
</a>
{% endif %}
</div>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Expand-Button mit dem echten Assistenten verknüpfen
const openRealAssistantBtn = document.getElementById('open-real-assistant');
if (openRealAssistantBtn) {
openRealAssistantBtn.addEventListener('click', function() {
if (window.MindMap && window.MindMap.assistant) {
window.MindMap.assistant.toggleAssistant(true);
}
});
}
// Auch die Beispiel-Buttons im Demo-Chat klickbar machen
const quickQueryButtons = document.querySelectorAll('.quick-query-btn');
quickQueryButtons.forEach(button => {
button.addEventListener('click', function() {
if (window.MindMap && window.MindMap.assistant) {
const question = button.getAttribute('data-question');
if (question) {
window.MindMap.assistant.sendQuestion(question);
} else {
// Fallback auf den Button-Text, falls kein data-question Attribut gesetzt ist
const queryText = button.textContent.trim();
window.MindMap.assistant.sendQuestion(queryText);
}
}
});
});
// Styling für die markdown-content hinzufügen
const style = document.createElement('style');
style.textContent = `
.markdown-content h1, .markdown-content h2, .markdown-content h3,
.markdown-content h4, .markdown-content h5, .markdown-content h6 {
font-weight: bold;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.markdown-content h1 { font-size: 1.4rem; }
.markdown-content h2 { font-size: 1.3rem; }
.markdown-content h3 { font-size: 1.2rem; }
.markdown-content h4 { font-size: 1.1rem; }
.markdown-content ul, .markdown-content ol {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.markdown-content ul { list-style-type: disc; }
.markdown-content ol { list-style-type: decimal; }
.markdown-content p { margin: 0.5rem 0; }
.markdown-content code {
font-family: monospace;
background-color: rgba(0, 0, 0, 0.1);
padding: 1px 4px;
border-radius: 3px;
}
.markdown-content pre {
background-color: rgba(0, 0, 0, 0.1);
padding: 0.5rem;
border-radius: 4px;
overflow-x: auto;
margin: 0.5rem 0;
}
.dark .markdown-content code {
background-color: rgba(255, 255, 255, 0.1);
}
.dark .markdown-content pre {
background-color: rgba(255, 255, 255, 0.1);
}
`;
document.head.appendChild(style);
});
</script>
{% endblock %}

234
templates/mindmap.html Normal file
View File

@@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interaktive Mindmap</title>
<!-- Cytoscape.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
<!-- Socket.IO -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
<!-- Feather Icons (optional) -->
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f9fafb;
color: #111827;
line-height: 1.5;
}
.container {
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
}
.header {
background-color: #1f2937;
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 1.5rem;
font-weight: 500;
}
.toolbar {
background-color: #f3f4f6;
padding: 0.75rem;
display: flex;
gap: 0.5rem;
border-bottom: 1px solid #e5e7eb;
}
.btn {
background-color: #3b82f6;
color: white;
border: none;
border-radius: 0.25rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
cursor: pointer;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn:hover {
background-color: #2563eb;
}
.btn-secondary {
background-color: #6b7280;
}
.btn-secondary:hover {
background-color: #4b5563;
}
.btn-danger {
background-color: #ef4444;
}
.btn-danger:hover {
background-color: #dc2626;
}
.search-container {
flex: 1;
display: flex;
margin-left: 1rem;
}
.search-input {
width: 100%;
max-width: 300px;
padding: 0.5rem;
border: 1px solid #d1d5db;
border-radius: 0.25rem;
font-size: 0.875rem;
}
#cy {
flex: 1;
width: 100%;
position: relative;
}
.category-filters {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
padding: 0.75rem;
background-color: #ffffff;
border-bottom: 1px solid #e5e7eb;
}
.category-filter {
border: none;
border-radius: 0.25rem;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
cursor: pointer;
transition: opacity 0.2s;
color: white;
}
.category-filter:not(.active) {
opacity: 0.6;
}
.category-filter:hover:not(.active) {
opacity: 0.8;
}
.footer {
background-color: #f3f4f6;
padding: 0.75rem;
text-align: center;
font-size: 0.75rem;
color: #6b7280;
border-top: 1px solid #e5e7eb;
}
/* Kontextmenü Styling */
#context-menu {
position: absolute;
background-color: white;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
#context-menu .menu-item {
padding: 0.5rem 1rem;
cursor: pointer;
}
#context-menu .menu-item:hover {
background-color: #f3f4f6;
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>Interaktive Mindmap</h1>
<div class="search-container">
<input type="text" id="search-mindmap" class="search-input" placeholder="Suchen...">
</div>
</header>
<div class="toolbar">
<button id="addNode" class="btn">
<i data-feather="plus-circle"></i>
Knoten hinzufügen
</button>
<button id="addEdge" class="btn">
<i data-feather="git-branch"></i>
Verbindung erstellen
</button>
<button id="editNode" class="btn btn-secondary">
<i data-feather="edit-2"></i>
Knoten bearbeiten
</button>
<button id="deleteNode" class="btn btn-danger">
<i data-feather="trash-2"></i>
Knoten löschen
</button>
<button id="deleteEdge" class="btn btn-danger">
<i data-feather="scissors"></i>
Verbindung löschen
</button>
<button id="reLayout" class="btn btn-secondary">
<i data-feather="refresh-cw"></i>
Layout neu anordnen
</button>
<button id="exportMindmap" class="btn btn-secondary">
<i data-feather="download"></i>
Exportieren
</button>
</div>
<div id="category-filters" class="category-filters">
<!-- Wird dynamisch befüllt -->
</div>
<div id="cy"></div>
<footer class="footer">
Mindmap-Anwendung © 2023
</footer>
</div>
<!-- Unsere Mindmap JS -->
<script src="{{ url_for('static', filename='js/mindmap.js') }}"></script>
<!-- Icons initialisieren -->
<script>
document.addEventListener('DOMContentLoaded', () => {
if (typeof feather !== 'undefined') {
feather.replace();
}
});
</script>
</body>
</html>

View File

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

79
templates/ueber_uns.html Normal file
View File

@@ -0,0 +1,79 @@
{% extends "base.html" %}
{% block title %}Über uns{% endblock %}
{% block content %}
<div class="max-w-3xl mx-auto">
<div class="card p-6 md:p-8">
<h1 class="text-3xl font-bold mb-6 gradient-text">Über uns</h1>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Unsere Vision</h2>
<p class="mb-4">
Systades ist ein innovatives Projekt, das darauf abzielt, das Teilen und Vernetzen von Wissen und Gedanken zu revolutionieren. Unsere Plattform ermöglicht es Nutzern, ihre Ideen in interaktiven Mindmaps zu organisieren und mit anderen zu teilen, wodurch ein kollaboratives Netzwerk des Wissens entsteht.
</p>
<p class="mb-4">
Wir glauben daran, dass Wissen am wertvollsten ist, wenn es geteilt und vernetzt wird. Durch die Verbindung verschiedener Perspektiven und Denkansätze entstehen neue Erkenntnisse und Innovationen.
</p>
</section>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Das Team</h2>
<p class="mb-4">
Till Tomczak und Marwin Medczinski arbeiten gemeinsam daran, Systades kontinuierlich zu verbessern und weiterzuentwickeln.
</p>
<!-- Platz für Team-Mitglieder -->
<div class="team-members space-y-6">
<!-- Beispiel für ein Team-Mitglied (kann als Vorlage verwendet werden) -->
<!--
<div class="team-member p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
<h3 class="text-lg font-bold mb-2">[Name]</h3>
<p class="text-gray-600 dark:text-gray-300 mb-2">[Position/Rolle]</p>
<p class="text-sm">[Kurze Beschreibung oder Verantwortlichkeiten]</p>
</div>
-->
</div>
</section>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Unsere Mission</h2>
<p class="mb-4">
Wir setzen uns dafür ein, eine Plattform zu schaffen, die:
</p>
<ul class="list-disc list-inside space-y-2 mb-4">
<li>Intuitive Werkzeuge für die Organisation und Visualisierung von Wissen bereitstellt</li>
<li>Die Zusammenarbeit und den Austausch zwischen Nutzern fördert</li>
<li>Kreativität und innovative Denkansätze unterstützt</li>
<li>Einen sicheren und respektvollen Raum für intellektuellen Austausch bietet</li>
</ul>
</section>
<section class="mb-8">
<h2 class="text-xl font-bold mb-4">Technologie & Innovation</h2>
<p class="mb-4">
Systades nutzt modernste Technologien und innovative Ansätze, um eine optimale Nutzererfahrung zu gewährleisten. Unsere Plattform wird kontinuierlich weiterentwickelt, um neue Funktionen und Verbesserungen zu integrieren.
</p>
<p>
Wir legen besonderen Wert auf:
</p>
<ul class="list-disc list-inside space-y-2 mt-2">
<li>Intuitive Benutzeroberfläche</li>
<li>Hohe Performance und Zuverlässigkeit</li>
<li>Datensicherheit und Privatsphäre</li>
<li>Barrierefreiheit und Inklusivität</li>
</ul>
</section>
<section>
<h2 class="text-xl font-bold mb-4">Kontakt & Feedback</h2>
<p class="mb-4">
Wir freuen uns über Ihr Feedback und Ihre Ideen zur Verbesserung von Systades. Gemeinsam können wir die Plattform weiter optimieren und an die Bedürfnisse unserer Nutzer anpassen.
</p>
<p>
Kontaktieren Sie uns gerne für Fragen, Anregungen oder Kooperationsmöglichkeiten.
</p>
</section>
</div>
</div>
{% endblock %}

60
test_app.py Normal file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3
import os
import requests
import time
print("Systades Anwendungstest")
print("=======================")
# Prüfen, ob die Datenbank existiert
db_path = 'systades.db'
if not os.path.exists(db_path):
print(f"Datenbank {db_path} existiert nicht.")
exit(1)
# Datenbankprüfung
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Prüfen, ob die User-Tabelle existiert und Benutzer enthält
cursor.execute("SELECT COUNT(*) FROM user;")
user_count = cursor.fetchone()[0]
print(f"Anzahl der Benutzer in der Datenbank: {user_count}")
if user_count == 0:
print("WARNUNG: Keine Benutzer in der Datenbank gefunden!")
print("Bitte führen Sie das Skript 'create_default_users.py' aus, um Standardbenutzer zu erstellen.")
conn.close()
exit(1)
# Tabellenschema prüfen
cursor.execute("PRAGMA table_info(user);")
columns = cursor.fetchall()
column_names = [column[1] for column in columns]
print(f"Spalten in der User-Tabelle: {', '.join(column_names)}")
# Überprüfen, ob die password-Spalte existiert
if 'password' not in column_names:
print("FEHLER: Die Spalte 'password' fehlt in der Tabelle 'user'!")
print("Bitte führen Sie das Skript 'fix_user_table.py' aus, um die Datenbank zu reparieren.")
conn.close()
exit(1)
# Benutzer für Testlogin abrufen
cursor.execute("SELECT username, email FROM user LIMIT 1;")
test_user = cursor.fetchone()
if test_user:
print(f"Testbenutzer für Login: {test_user[0]} (E-Mail: {test_user[1]})")
else:
print("FEHLER: Konnte keinen Testbenutzer abrufen.")
conn.close()
print("\nDie Datenbank scheint korrekt konfiguriert zu sein.")
print("Sie können nun die Anwendung starten und sich mit den folgenden Zugangsdaten anmelden:")
print(" Admin: username=admin, password=admin")
print(" User: username=user, password=user")
print("\nTest abgeschlossen.")

19
website/utils/__init__.py → utils/__init__.py Executable file → Normal file
View File

@@ -7,14 +7,14 @@ This package contains various utilities for database management,
user management, and server administration.
"""
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
# Import utilities that don't depend on app.py first
from .db_check import check_db_connection, initialize_db_if_needed
# Define the list of all available utilities
__all__ = [
# Database utilities
'check_db_connection',
'initialize_db_if_needed',
'fix_database_schema',
'rebuild_database',
'test_database_connection',
@@ -31,4 +31,11 @@ __all__ = [
# Server management
'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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More