Compare commits

234 Commits

Author SHA1 Message Date
f0d13cae78 feat: update logging format in app.log for better readability 2025-06-01 15:14:48 +01:00
a60b5c31ca chore: update compiled Python files in __pycache__ directories 2025-06-01 14:33:16 +01:00
f5c2e70a11 feat: Implementierung von Benachrichtigungen und sozialen Funktionen; Hinzufügen von API-Endpunkten für Benachrichtigungen, Benutzer-Follows und soziale Interaktionen; Verbesserung des Logging-Systems zur besseren Nachverfolgbarkeit von Systemereignissen. 2025-05-28 22:08:56 +02:00
1f4394e9b6 fix: update log format in app.log for better readability 2025-05-22 12:55:28 +01:00
37c457ca3f Kategorien- Button Funktion 2025-05-22 12:15:50 +01:00
936d983cb3 style: update mindmap.css for improved layout and design consistency 2025-05-22 12:02:17 +01:00
9ed9adfeaf 🔧 fix: update database and log files for improved stability 2025-05-20 10:11:07 +01:00
9c1475844c Merge branch 'main' of https://git.clickcandit.com/marwinm/website 2025-05-20 09:21:54 +01:00
310d0af0d1 feat: update logging format in app.log for better readability 2025-05-20 09:20:49 +01:00
cab8d28aeb 🔧 chore: Protokollaktualisierungen zur Dokumentation des Anwendungsstarts in app.log; mehrere Startmeldungen hinzugefügt für verbesserte Nachverfolgbarkeit. 2025-05-17 18:33:06 +02:00
9dc44f94f6 feat: update mindmap.js for improved performance and functionality 2025-05-17 10:25:40 +01:00
5b9ae85453 feat: Erweiterung der Mindmap-Funktionalität durch Verbesserung der Ladeanimation, Fehlerbehandlung und CSS-Anpassungen; Protokollaktualisierungen zur Fehlerverfolgung und Optimierung der Benutzeroberfläche. 2025-05-16 21:02:09 +01:00
302d5213ef feat: Erweiterung der Mindmap-Funktionalität durch Hinzufügen von Hover-Events für Knoten und Verbesserung der Zoom-Steuerung; Info-Panel für Knotendetails implementiert. 2025-05-16 20:40:53 +01:00
bb3211ab3d feat: enhance mindmap functionality and update UI components 2025-05-16 20:35:01 +01:00
2a246ee063 feat: Verbesserung der Mindmap-Funktionalität durch Einführung von Unterseiten und Anpassung des Designs in update_mindmap.js; Protokollaktualisierungen und Cache-Optimierungen vorgenommen. 2025-05-16 20:29:48 +01:00
fc8861c73c wir haben unterkategorien 2025-05-16 20:14:51 +01:00
8c49e7396e feat: enhance mindmap update functionality in update_mindmap.js 2025-05-16 20:04:36 +01:00
8e3c81fd06 🔧 chore: update cached Python files and logs for better performance 2025-05-16 15:02:44 +01:00
f18d23cfea 🔧 fix: update compiled Python cache and log file for better performance 2025-05-16 14:04:21 +01:00
d3405a7031 chore: Aktualisierung der Mindmap-Funktionalität und Integration von CSS/JS-Templates 2025-05-14 13:58:51 +02:00
5793902e47 chore: Änderungen commited 2025-05-14 13:56:11 +02:00
e73ccd7e80 "Update mindmap template with Convention-Format: templates/templates/mindmap.html" 2025-05-14 13:53:57 +02:00
e6784b712d 🎉 feat: "Add Minducture mindmap CSS and JS template integration" 2025-05-14 13:51:16 +02:00
35b5f321d4 chore: Änderungen commited 2025-05-14 13:48:24 +02:00
b68f65cc76 "Update mindmap Mindmap functionality 2025-05-14 13:47:35 +02:00
3a2f721f63 chore: Änderungen commited 2025-05-14 13:44:48 +02:00
5933195196 "Refactor Mindate mindmap UI updates for mindmap controls and components" (feat) 2025-05-14 13:41:24 +02:00
beccfa25a6 chore: Änderungen commited 2025-05-14 13:37:29 +02:00
bc5cef3ba8 "feat: Implement mindmap control enhancements and controls updates" 2025-05-14 12:51:53 +02:00
b867af9c8b chore: Änderungen commited 2025-05-14 12:47:02 +02:00
ee04432a49 chore: Änderungen commited 2025-05-14 12:43:13 +02:00
bbcee7f610 chore: Änderungen commited 2025-05-14 12:39:23 +02:00
1eb47fc230 chore: Änderungen commited 2025-05-14 12:15:57 +02:00
2921c5a824 chore: Änderungen commited 2025-05-14 12:13:19 +02:00
c98e238841 chore: Änderungen commited 2025-05-14 12:07:57 +02:00
af30a208ca chore: Änderungen commited 2025-05-14 12:02:15 +02:00
2e2f35ccc1 chore: Änderungen commited 2025-05-14 11:58:31 +02:00
fd293e53e1 chore: Änderungen commited 2025-05-14 11:55:58 +02:00
2b19cb000b chore: Änderungen commited 2025-05-14 11:39:58 +02:00
3aefe6c5e6 chore: Änderungen commited 2025-05-14 11:23:33 +02:00
c7b87dc643 chore: Änderungen commited 2025-05-14 11:21:10 +02:00
dc96252013 feat: enhance mindmap functionality with improved error handling and editing features; update logging for better debugging insights 2025-05-12 20:48:26 +02:00
ab56f44ae9 feat: update mindmap styles and improve node and edge rendering logic for better visualization 2025-05-12 20:36:12 +02:00
61124f5266 🗑️ chore: remove unused database and routing scripts; update cached Python bytecode files in __pycache__ directories 2025-05-12 20:31:20 +02:00
fab8d10f03 🔧 chore: Aktualisierung der Protokollierung mit zusätzlichen 404-Fehlern und Anwendungsstartmeldungen in app.log; Aktualisierung der Bytecode-Datei in __pycache__ 2025-05-12 19:54:41 +01:00
dec30e4681 feat: update app logic and improve mindmap functionality 2025-05-12 19:27:18 +01:00
a1bd999c6a feat: update app logic and improve mindmap functionality 2025-05-12 18:56:49 +01:00
b1d33ce643 feat: update app logic and improve logging functionality 2025-05-12 18:26:33 +01:00
293f877017 fix: update log file and remove unnecessary bytecode cache 2025-05-12 17:56:27 +01:00
e86d0b0f90 feat: update logging format in app.log for better readability 2025-05-11 14:05:59 +01:00
059fd167d6 🔧 chore: update cached Python bytecode files in __pycache__ directories 2025-05-11 13:30:26 +01:00
256d38e140 chore: Aktualisierung der Protokollierung mit zusätzlichen Fehlern 404 und Anwendungsstartmeldungen in app.log 2025-05-11 03:12:59 +02:00
4b75489631 chore: Erweiterung der Protokollierung mit zusätzlichen Startmeldungen der Anwendung in app.log 2025-05-11 01:13:33 +02:00
cb95c78276 "Refactor logging and improved app logging000_feat: Update code refactoring for enhanced performance enhancement in app module" 2025-05-11 00:31:29 +02:00
00cb100467 chore: Änderungen commited 2025-05-11 00:25:50 +02:00
8c66461dc8 chore: Änderungen commited 2025-05-11 00:23:27 +02:00
566f84fc0c "Refactor code cache for app.pycache__/app.c python file (feat)" 2025-05-11 00:20:49 +02:00
07eae42ba3 chore: Änderungen commited 2025-05-11 00:16:30 +02:00
0a1bebd862 "Update log file path for Conventionalenhanced logging: Migrations" 2025-05-11 00:13:40 +02:00
59b79b3466 chore: Änderungen commited 2025-05-11 00:04:49 +02:00
6f5526b648 📝 "Ref (Feature) Log Rotation) Logs now implemented for log rotation logs in app. 2025-05-11 00:01:50 +02:00
21148f0c0e chore: Änderungen commited 2025-05-10 23:56:26 +02:00
ba6cac32a9 "Refactor app. Implementing new feature for user authentication (feat): Add API endpoint enhancement)" 2025-05-10 23:53:54 +02:00
be767e9f27 Fehlerprotokollierung aktualisiert: Mehrere 404-Fehler für nicht gefundene URLs hinzugefügt, einschließlich detaillierter Rückverfolgungen und Benutzerinformationen. Diese Änderungen verbessern die Nachverfolgbarkeit von Anwendungsfehlern und unterstützen die Fehlerbehebung. 2025-05-10 23:52:41 +02:00
6aaf073ffb "Update log file structure for better logging and log rotation" 2025-05-10 23:45:42 +02:00
b6080f96cf chore: Änderungen commited 2025-05-10 23:40:37 +02:00
9ebf4b7abd "Refactor log rotation: update logs/logs/app.log, purge rotated to simplify and deleted postcss 2025-05-10 23:40:18 +02:00
5d35983f15 chore: Änderungen commited 2025-05-10 23:36:28 +02:00
7278ece2b8 "feat: Add PostCSS 2025-05-10 23:36:23 +02:00
f677e98795 "Refactor database schema for user authentication and data migration" (feat: feat or fix) 2025-05-10 23:29:42 +02:00
40c3f6d9b4 📝 "Refactor: Update log rotation correction in logging update to app.log files 2025-05-10 23:26:14 +02:00
9939db731b chore: Änderungen commited 2025-05-10 23:23:32 +02:00
d0f32a8355 chore: Änderungen commited 2025-05-10 23:20:10 +02:00
02d1801fc9 "Refactor app log file path update" 2025-05-10 23:17:13 +02:00
c51a8e23ca chore: Aktualisierung der Codebasis und Verbesserung der Struktur für bessere Wartbarkeit 2025-05-10 23:15:45 +02:00
1600647bc4 chore: Änderungen commited 2025-05-10 23:15:08 +02:00
82d03f6c48 "Refactor app. Update import/Improve Python Cython code formatting (#__" 2025-05-10 23:12:51 +02:00
d1352286b7 chore: Änderungen commited 2025-05-10 23:10:31 +02:00
e7b3374c53 feat: Hinzufügen von CSS-Stilen für die Suchergebnisse und deren Darstellung in der Benutzer-Mindmap-Vorlage zur Verbesserung der Benutzeroberfläche 2025-05-10 23:05:44 +02:00
4bf046c657 feat: Hinzufügen einer Suchleiste und Import-/Export-Buttons zur Benutzer-Mindmap-Vorlage für verbesserte Benutzerinteraktion 2025-05-10 23:05:27 +02:00
892a1212d9 chore: Änderungen commited 2025-05-10 23:04:52 +02:00
8440b7c30d "Refactor database connection for improved data consistency (feat" 2025-05-10 23:01:22 +02:00
74c2783b1a "Refactor app. Update import statements and for improved usability improvements (feat(feat): Implementing)" 2025-05-10 22:58:56 +02:00
fcd82eb5c9 chore: Änderungen commited 2025-05-10 22:56:49 +02:00
c654986f65 "Refactor user mind map template updates for better user experience" (feat) 2025-05-10 22:54:31 +02:00
f4ab617c59 chore: Änderungen commited 2025-05-10 22:52:11 +02:00
9c36179f29 "Refactor UI refactoring: Simplify app.c-specific components 2025-05-10 22:43:13 +02:00
f292cf1ce5 chore: Änderungen commited 2025-05-10 22:35:14 +02:00
3a20ea0282 chore: Änderungen commited 2025-05-10 22:28:19 +02:00
44986bfa23 "feat: Refactor UI update for my_my_account_account template" 2025-05-10 22:24:33 +02:00
41195a44cb chore: Änderungen commited 2025-05-10 22:20:49 +02:00
e1cd23230d "Refactor database schema for user authentication system data" 2025-05-10 22:17:26 +02:00
77095e91b6 chore: Änderungen commited 2025-05-10 22:14:16 +02:00
6322e046c5 "Feature: Integrate app and script and related files for Mindmap 2025-05-10 22:11:43 +02:00
2584bae149 chore: aktualisiere kompilierte Python-Bytecode-Datei in __pycache__ 2025-05-10 21:59:18 +01:00
c0bd7a3986 feat: add systades.db and update __pycache__ for improved performance 2025-05-10 21:58:29 +01:00
dec4a57b89 feat: enhance mindmap update functionality in app.py and update_mindmap.js 2025-05-10 21:27:57 +01:00
6a3b3a81c1 feat: verbessere neuronale Netzwerkdarstellung und Mindmap-Layout durch Anpassungen an Farben, Größen und Animationen 2025-05-10 21:19:54 +01:00
629813c486 feat(app): Aktualisierung der Datenbankinitialisierung für Flask 2.2+ Kompatibilität und Verbesserung der Initialisierungslogik 2025-05-10 22:11:33 +02:00
7cb2bf1ed0 feat: enhance mindmap and neural network background functionality 2025-05-10 20:40:13 +01:00
ed1d41d316 chore: automatic commit 2025-05-10 18:18 2025-05-10 18:18:34 +01:00
fe3cf81bc7 "Add/update or fix?" 2025-05-10 18:09:53 +02:00
2e68ae30b8 "Update .env and migration files for database schema" 2025-05-10 18:07:49 +02:00
858fdf5c44 chore: Änderungen commited 2025-05-10 18:05:14 +02:00
4948f3ad2a "Refactor database schema for database connection and data integrity improvements" 2025-05-10 18:02:27 +02:00
52954e51f1 Merge branch 'main' of https://git.clickcandit.com/marwinm/website 2025-05-10 16:04:40 +01:00
14f1356551 chore: update compiled Python bytecode files in __pycache__ directories 2025-05-10 15:49:21 +01:00
44c7183e97 feat(profile): verbessere das Profil-Layout mit neuen Tab-Stilen, verbessere die Benutzeroberfläche für Profil- und Passwortaktualisierungen und füge Animationen für Tab-Inhalte hinzu. 2025-05-10 15:56:14 +02:00
d99cae4956 chore: Änderungen commited 2025-05-10 15:42:34 +02:00
3ae5f2527c Merge branches 'main' and 'main' of https://git.clickcandit.com/marwinm/website 2025-05-10 15:35:35 +02:00
412dabd5c1 feat(mindmap): füge Icons und Farben für Kategorien hinzu, verbessere das Layout und die Darstellung der Mindmap in update_mindmap.js 2025-05-09 20:18:45 +01:00
5ade301f80 noch nicht ganz 2025-05-09 20:06:35 +01:00
118f8ed132 feat: enhance mindmap update functionality in update_mindmap.js 2025-05-09 20:00:46 +01:00
121f46df01 feat(mindmap): erweitere die Funktionalität zur Handhabung von Unterthemen und verbessere die Benutzerinteraktion durch neue Navigations- und Layout-Elemente in update_mindmap.js 2025-05-09 19:58:03 +01:00
4b0613eb6b feat(mindmap): entferne veraltete Mindmap-Module und -Dateien zur Optimierung der Codebasis und Verbesserung der Wartbarkeit 2025-05-09 19:46:26 +01:00
dd172d8596 feat(mindmap): improve performance of mind map rendering logic 2025-05-09 19:28:00 +01:00
653b3abe91 ich geh schlafen 2025-05-06 22:50:04 +01:00
ec50886145 es wird spät 2025-05-06 22:47:08 +01:00
c888dcc452 feat: enhance mindmap update functionality in update_mindmap.js 2025-05-06 22:26:28 +01:00
acceec4352 hab glaube gefixed 2025-05-06 22:24:49 +01:00
f093a6211c ich bin dran ! 2025-05-06 22:12:46 +01:00
58a5ea00bd Mindmap wird nicht initialisiert ! :( 2025-05-06 21:58:27 +01:00
aeb829e36a feat(mindmap): enhance interaction and initialization logic in mindmap files 2025-05-06 21:53:54 +01:00
49e5e19b7c feat(mindmap): aktualisiere die Mindmap-Datenstruktur mit neuen Knoten und Kanten, um die Benutzerinteraktion zu verbessern. Füge dynamische Knotenbeschreibungen hinzu und implementiere eine Funktion zur Aktualisierung der Mindmap, die das Layout optimiert und die Benutzererfahrung verbessert. 2025-05-06 21:25:17 +01:00
903e095b66 feat: enhance mindmap functionality and improve user interaction 2025-05-06 21:23:29 +01:00
2d083f5c0a feat: update mindmap template for improved layout and usability 2025-05-06 20:52:51 +01:00
cbe8dc3bd0 "Refactor database schema for database operations and db maintenance utilities improvements" 2025-05-05 07:04:37 +02:00
7c1533c20d feat: improve mindmap interaction and enhance performance in JS files 2025-05-03 20:22:59 +01:00
c285b7d8dc feat(mindmap): verbessere die Benutzerinteraktion und das visuelle Design der Mindmap. Füge dynamische Knotenbeschreibungen hinzu, aktualisiere die Farbpalette und optimiere die Zoom- und Knoteninteraktionen. Erweitere die Seitenleisten mit neuen Panels und verbessere die Animationen für ein ansprechenderes Nutzererlebnis. 2025-05-03 19:57:28 +01:00
21ddd38e13 feat(mindmap): enhance functionality for better user interaction 2025-05-03 19:52:35 +01:00
1cf7bfbf76 feat: erweitere die Mindmap-API zur dynamischen Erstellung und Anzeige wissenschaftlicher Knoten sowie zur Verbesserung der Fehlerbehandlung. Füge neues Skript zur Aktualisierung der Mindmap hinzu. 2025-05-03 19:44:18 +01:00
40b28134fc feat: entferne nicht verwendete Forum-Funktionen aus app.py und aktualisiere die Basisvorlage für die Suchfunktion 2025-05-03 19:31:34 +01:00
d5fababd49 feat: update app logic and enhance base template rendering 2025-05-03 19:19:53 +01:00
7c742debdf 🔧 chore: passe die Konfiguration des neuronalen Netzwerks an, indem die Knotenanzahl auf 10 und die Clusteranzahl auf 7 erhöht wird, um die Verteilung zu optimieren. 2025-05-03 16:00:48 +01:00
4a4271a23c feat: aktualisiere das Benutzerprofil, um grundlegende Statistiken anzuzeigen und die Handhabung von Fehlern zu verbessern, während die Datenbankstruktur berücksichtigt wird. 2025-05-03 15:37:33 +01:00
c1038b479f feat: update app.py to improve performance and optimize resource usage 2025-05-03 15:34:49 +01:00
cd0083544a feat: update profile template for improved user experience 2025-05-03 14:59:49 +01:00
a03bec2dff chore: update database and remove unused compiled Python files 2025-05-03 13:37:07 +01:00
997479581d 🔧 chore: update compiled Python bytecode files in __pycache__ directories 2025-05-03 12:39:17 +01:00
8153390e35 Verbessere das Design der Profilseite: Aktualisiere CSS-Stile für eine verbesserte Benutzeroberfläche im Light Mode, einschließlich optimierter Hintergründe, Rahmen und Schattierungen für verschiedene Elemente wie Einstellungen, Statistiken und Interaktionselemente. 2025-05-02 19:56:25 +02:00
bfa155628e "Refactor UIKern:000:2 amend template.pycache__/app.00/app.c file modifications to enhance user experience in-0000usability." 2025-05-02 19:53:04 +02:00
700a8a3b89 chore: Änderungen commited 2025-05-02 19:46:47 +02:00
808481ffe7 chore: Änderungen commited 2025-05-02 19:44:34 +02:00
e2c8cfaacf "Refactor templating templates for better" 2025-05-02 19:41:56 +02:00
78e37fa717 chore: Änderungen commited 2025-05-02 19:39:44 +02:00
b2cf50626a "Add default avatar icon for avatar" 2025-05-02 19:26:55 +02:00
7f48526315 chore: Aktualisiere die kompilierte Python-Datei im __pycache__ Verzeichnis 2025-05-02 19:24:20 +02:00
84f8a6bf31 chore: Änderungen commited 2025-05-02 19:23:38 +02:00
7003c89447 "Refactor database schema for user profiles and profile templates updated to remove obsolete db table 'systades (feat): systades.db) system data)" 2025-05-02 19:21:19 +02:00
d0821db983 chore: Änderungen commited 2025-05-02 19:18:49 +02:00
f0c4c514c4 "Refactor Mindicate mindmap Update for Mindate Mindous Mind mindmap Modifications in the Mind (feat/update to-mendments toughly, update forums ofthe mind-peed_induced.cetapane 2025-05-02 19:16:34 +02:00
304a399b85 chore: Änderungen commited 2025-05-02 19:13:14 +02:00
a5396c0d6e 🎨 style: update base styles and add user mindmap template 2025-05-02 19:06:09 +02:00
9cc4e70cba 🎨 style: erweitere die CSS-Stile für Schaltflächen und Links im Basis-Template zur Verbesserung der Benutzeroberfläche 2025-05-02 19:01:47 +02:00
a8cac08d30 feat: enhance chatgpt assistant styling and functionality improvements 2025-05-02 18:59:13 +02:00
42a7485ce1 🎨 style: update CSS and remove unused JS for improved design consistency 2025-05-02 18:57:01 +02:00
54a5ccc224 🎨 style: update neural network background CSS for improved aesthetics 2025-05-02 18:54:33 +02:00
a99f82d4cf feat: add theme toggle functionality and update related files 2025-05-02 18:52:18 +02:00
699127f41f 🎨 style: update UI and database schema for improved user experience 2025-05-02 18:49:02 +02:00
e8d356a27a 🎨 style: update base styles and templates for improved layout and design 2025-05-02 18:46:05 +02:00
daf2704253 feat: update profile template and remove compiled Python file 2025-05-02 18:42:56 +02:00
084059449f 🎨 style: update profile template and improve app.py functionality 2025-05-02 18:38:59 +02:00
c9bbc6ff25 🎨 style: update styles and layout in base templates and CSS files 2025-05-02 18:33:41 +02:00
742e3fda20 feat: enhance UI and functionality for mindmap creation and profile pages 2025-05-02 18:31:25 +02:00
54aa246b79 feat: add create_mindmap template for mind mapping functionality 2025-05-02 18:28:54 +02:00
505fb9aa47 🔄 chore: aktualisiere die Bytecode-Dateien im __pycache__-Verzeichnis und die systades.db-Datenbankdatei 2025-05-02 18:27:26 +02:00
e4e6541b8c 🎉 feat: update app logic and database schema for improved performance 2025-05-02 18:22:02 +02:00
e724181915 feat: improve user management and update styles for better UX 2025-05-02 18:19:58 +02:00
460c3f987e 🎨 style: update base styles and database schema for improved layout 2025-05-02 18:14:04 +02:00
7f33dea278 feat(database): add new systades.db instance and update existing file 2025-05-02 18:11:57 +02:00
726d9c9c70 🎉 feat(database): add user fields and password column migrations 2025-05-02 18:09:33 +02:00
81170fbd3d 🎨 style: update base styles and background for improved UI consistency 2025-05-02 18:02:00 +02:00
eff3fda1ca chore: update Python bytecode files in __pycache__ directory 2025-05-02 17:54:46 +02:00
d49b266d96 User Profil Fix Versuch 1 2025-05-02 16:48:00 +01:00
34a08c4a6a feat: aktualisiere Favicon mit neuem Design und passe die HTML-Vorlage an, um das neue Favicon zu integrieren 2025-05-02 16:17:39 +01:00
7918de1723 feat: update favicon generation and add neuron favicon image 2025-05-02 16:07:46 +01:00
a0e4cd2208 feat: implementiere Mindmap-Funktionalität mit dynamischer Datenladung und verbesserten Benutzeroberflächen-Elementen in mindmap.html und mindmap-init.js 2025-05-02 09:27:22 +01:00
2199d6007c feat: verbessere das Layout und die Benutzeroberfläche des Chatbereichs in index.html mit neuen Stilen und verbesserten Eingabefeldern 2025-05-02 09:14:01 +01:00
7fb9452d09 feat: passe die Konfiguration des neuronalen Netzwerks an, um die Knotenanzahl zu reduzieren und die Clusteranzahl zu erhöhen 2025-05-02 09:07:02 +01:00
1f3e60efde feat: update index.html template for improved layout and accessibility 2025-05-02 09:04:48 +01:00
5e97381c8f nenn mich designer 2025-05-02 08:51:40 +01:00
4c402423c0 feat: verbessere die Logik des neuronalen Netzwerk-Hintergrunds mit optimierten Animationen und vereinfachter Struktur 2025-05-02 08:40:34 +01:00
6d2595e3a6 feat: update neural network background logic for improved performance 2025-05-02 08:33:45 +01:00
29b44e5c52 Community funktioniert nicht 2025-05-02 08:25:06 +01:00
693e542d5f UTF8 in .env, FLASK DEBUG 2025-05-02 08:06:38 +01:00
4c3e476338 feat: update environment config and add community preview template 2025-05-02 08:01:23 +01:00
613c38ccb2 🔧 chore: update environment variables in .env file 2025-05-02 07:30:58 +01:00
91fdd43fe0 🔒 chore: entferne den OpenAI API-Schlüssel aus der Beispielumgebung für Sicherheitsgründe 2025-05-01 21:15:04 +01:00
f36dd5ffaa OpenAI Api Key 2025-05-01 21:10:19 +01:00
2e1c3ce8b0 Community erstellt 2025-05-01 21:04:37 +01:00
d80c4c9aec feat(models): update model structure for improved data handling 2025-05-01 20:56:05 +01:00
3b0bea959c 🔧 fix: update venv configuration for improved compatibility 2025-05-01 20:25:01 +01:00
cb3bfe0e6a LOL 2025-05-01 19:57:26 +01:00
fd63810845 feat: update environment and scripts for improved functionality 2025-05-01 16:29:57 +02:00
883973fe7b feat: update environment variables and enhance mindmap functionality 2025-05-01 16:27:40 +02:00
027e632856 feat: update example.env with new configuration settings 2025-05-01 16:24:29 +02:00
406289e54f 🎨 feat(mindmap): improve rendering performance and optimize code structure 2025-05-01 16:16:26 +02:00
71b33e6cec feat(mindmap): enhance mindmap rendering performance and responsiveness 2025-05-01 16:14:14 +02:00
c74d3164bb 🎨 feat: update mindmap templates and JS module for improved UI design 2025-05-01 16:11:42 +02:00
4982cddeef 🎉 feat: update Dockerfile and scripts for improved functionality 2025-05-01 16:05:52 +02:00
631619ccb4 feat: update docker-compose.yml for improved service configuration 2025-05-01 16:01:00 +02:00
f9881b678d 🔄 chore: aktualisiere die .env-Datei für verbesserte Konfiguration 2025-05-01 11:27:11 +01:00
259ce3cf69 feat: update Dockerfile and docker-compose for improved build process 2025-05-01 11:24:27 +01:00
9f4743eaea feat: update cached Python files and add new static image asset 2025-05-01 10:55:30 +01:00
de0f837cfd Optimierung der Projektstruktur: Entferne nicht mehr benötigte Skripte und Dateien, um die Wartbarkeit zu erhöhen und veraltete Komponenten zu beseitigen. 2025-04-30 15:51:07 +02:00
1c49ddfb19 chore: automatic commit 2025-04-30 15:49 2025-04-30 15:49:18 +02:00
46c16e5f01 chore: automatic commit 2025-04-30 15:44 2025-04-30 15:44:02 +02:00
84667bca00 chore: automatic commit 2025-04-30 15:41 2025-04-30 15:41:00 +02:00
779449559d chore: automatic commit 2025-04-30 15:38 2025-04-30 15:38:56 +02:00
721a10e861 Entferne nicht mehr benötigte Skripte: Lösche die Dateien check_schema.py, create_default_users.py, fix_user_table.py, test_app.py und windows_setup.bat, um die Projektstruktur zu optimieren und veraltete Komponenten zu entfernen. 2025-04-30 15:33:39 +02:00
a431873ca2 chore: automatic commit 2025-04-30 15:29 2025-04-30 15:29:23 +02:00
e4ab1e1bb5 chore: automatic commit 2025-04-30 12:48 2025-04-30 12:48:06 +02:00
f69356473b Entferne nicht mehr benötigte Dateien: Lösche docker-compose.yml, Dockerfile, README.md, requirements.txt, start_server.bat, start-flask-server.py, start.sh, test_server.py, sowie alle zugehörigen Datenbank- und Website-Dateien. Diese Bereinigung optimiert die Projektstruktur und entfernt veraltete Komponenten. 2025-04-30 12:34:06 +02:00
38ac13e87c chore: automatic commit 2025-04-30 12:32 2025-04-30 12:32:36 +02:00
0afb8cb6e2 Update neural network background configuration: reduce node count and connection distance, adjust glow and node colors, and modify shadow blur for improved visual clarity and performance. 2025-04-29 20:58:27 +01:00
5d282d2108 Refactor neural network background animation: streamline the code by consolidating node and connection logic, enhancing visual effects with improved glow and animation dynamics. Introduce responsive canvas resizing and optimize particle behavior for a smoother experience. 2025-04-29 20:54:24 +01:00
4aba72efa2 Merge branch 'main' of https://git.clickcandit.com/marwinm/website 2025-04-29 20:52:11 +01:00
89476d5353 w 2025-04-29 20:51:49 +01:00
0f7a33340a Update mindmap database: replace binary file with a new version to reflect recent changes in structure and data. 2025-04-27 08:56:56 +01:00
73501e7cda Add Flask server startup scripts: introduce start_server.bat for Windows and start-flask-server.py for enhanced server management. Update run.py to include logging and threaded request handling. Add test_server.py for server accessibility testing. 2025-04-25 17:09:09 +01:00
9f8eba6736 Refactor database initialization: streamline the process by removing the old init_database function, implementing a new structure for database setup, and ensuring the creation of a comprehensive mindmap hierarchy with an admin user. Update app.py to run on port 5000 instead of 6000. 2025-04-21 18:43:58 +01:00
b6bf9f387d Update mindmap database: replace binary file with a new version to incorporate recent structural and data changes. 2025-04-21 18:26:41 +01:00
d9fe1f8efc Update mindmap database: replace existing binary file with a new version, reflecting recent changes in mindmap structure and data. 2025-04-20 20:28:51 +01:00
fd7bc59851 Add user authentication routes: implement login, registration, and logout functionality, along with user profile and admin routes. Enhance mindmap API with error handling and default node creation. 2025-04-20 19:58:27 +01:00
55f1f87509 Refactor app initialization: encapsulate Flask app setup and database initialization within a create_app function, improving modularity and error handling during startup. 2025-04-20 19:54:07 +01:00
03f8761312 Update Docker configuration to change exposed port from 5000 to 6000 in Dockerfile and docker-compose.yml, ensuring consistency across the application. 2025-04-20 19:48:49 +01:00
506748fda7 Implement error handling and default node creation for mindmap routes; initialize database on first request to ensure root node exists. 2025-04-20 19:43:21 +01:00
6d069f68cd Update requirements.txt to include new dependencies for enhanced functionality and remove outdated packages for better compatibility. 2025-04-20 19:32:32 +01:00
4310239a7a Enhance Dockerfile: add system dependencies for building Python packages, update requirements.txt to remove specific version constraints, and verify installations with pip list. 2025-04-20 19:31:13 +01:00
e9fe907af0 Update requirements.txt to include email_validator==2.1.1 for improved email validation functionality. 2025-04-20 19:29:19 +01:00
0c69d9aba3 Remove unnecessary 'force' option from docker-compose.yml for cleaner configuration. 2025-04-20 19:26:44 +01:00
6da85cdece Refactor Docker setup: update docker-compose.yml to use a specific website directory for volumes, enable automatic restarts, and modify Dockerfile to clean up and copy application files more efficiently. 2025-04-20 19:25:08 +01:00
a073b09115 Update Docker configuration: change Docker Compose version to 3.8, enhance web service setup with context and dockerfile specifications, add volume and environment variables for Flask development, and modify Dockerfile to use Python 3.11 and improve file copying and command execution. 2025-04-20 19:22:08 +01:00
f1f4870989 Update dependencies in requirements.txt to specific versions for Flask, Flask-Login, Flask-SQLAlchemy, Werkzeug, and SQLAlchemy 2025-04-20 19:16:34 +01:00
5374 changed files with 882169 additions and 10065 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

View File

@@ -2,10 +2,12 @@
# Kopiere diese Datei zu .env und passe die Werte an
# Flask
SECRET_KEY=dein-geheimer-schluessel-hier
FLASK_APP=app.py
FLASK_ENV=development
SECRET_KEY=your-secret-key-replace-in-production
# OpenAI API
OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier
OPENAI_API_KEY=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
# Datenbank
# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
logs/app.log

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
}

1274
COMMON_ERRORS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,33 @@
FROM python:3.9-slim-buster
# Dockerfile
FROM python:3.11-slim
# Arbeitsverzeichnis in Container
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
# Systemabhängigkeiten installieren und Verzeichnisse anlegen
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /app/database
COPY website .
# pip auf den neuesten Stand bringen und Requirements installieren
COPY requirements.txt ./
RUN pip install --upgrade pip && \
pip install --no-cache-dir -U -r requirements.txt
CMD ["python", "app.py"]
# Anwendungscode kopieren
COPY . .
# Berechtigungen für database-Ordner
RUN chmod -R 777 /app/database
# Exponiere Port 5000 für Flask
EXPOSE 5000
# Setze Umgebungsvariablen
ENV FLASK_APP=app.py
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
# Startkommando mit spezifischen Flags für Produktion
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*

464
ROADMAP.md Normal file
View File

@@ -0,0 +1,464 @@
# 🚀 SysTades Social Network - Entwicklungsroadmap
## 📋 Überblick
SysTades ist jetzt ein vollwertiges Social Network für Wissensaustausch, Mindmapping und Community-Building.
## ✅ Abgeschlossene Phasen
### Phase 1: Basis Social Network ✅
- ✅ Erweiterte Benutzermodelle mit Social Features
- ✅ Posts, Kommentare, Likes, Follows System
- ✅ Benachrichtigungssystem
- ✅ Benutzerprofile mit Statistiken
- ✅ Erweiterte Navigation und UI
-**Verbessertes Logging-System mit visuellen Enhancements**
- ✅ Social Feed mit Filtering
- ✅ Mobile-responsive Design
### Phase 2: Core Features ✅
- ✅ Mindmap-Integration in Social Posts
- ✅ Gedanken-Sharing System
- ✅ Bookmark-System für Posts
- ✅ Analytics Dashboard für Benutzer
- ✅ Erweiterte Suche (Benutzer, Posts, Gedanken)
- ✅ Real-time Benachrichtigungen
- ✅ Post-Sharing und Engagement Metrics
### Phase 3: Erweiterte Social Features ✅
- ✅ Benutzerprofile mit Tabs (Posts, Gedanken, Mindmaps, Aktivität)
- ✅ Follow/Unfollow System mit UI
- ✅ Notification Center mit Filtering
- ✅ Post-Typen (Text, Gedanke, Frage, Erkenntnis)
- ✅ Sichtbarkeitseinstellungen (Öffentlich, Follower, Privat)
- ✅ Quick-Create Post Modal
### Phase 3.5: Logging & Monitoring System ✅ (NEU)
-**Erweiterte SocialNetworkLogger Klasse mit visuellen Features**
-**Farbige Konsolen-Ausgabe mit ANSI-Codes**
-**Emoji-basierte Kategorisierung für bessere Übersicht**
-**Component-spezifisches Logging (AUTH, API, DB, ERROR, etc.)**
-**Performance-Monitoring mit Zeitstempel**
-**Strukturierte JSON-Logs für externe Analyse**
-**Decorator-basierte Instrumentierung**
-**Vollständige Integration in alle App-Komponenten**
-**Ersetzung aller print-Statements durch strukturierte Logs**
## 🔄 Aktuelle Phase 4: UI/UX Verbesserungen (In Arbeit)
### UI/UX Komponenten
- ✅ Moderne Navigation mit Icons und Badges
- ✅ Dark/Light Mode Toggle
- ✅ Responsive Mobile Navigation
- ✅ Glassmorphism Design Elements
- ✅ Gradient Themes und Farbsystem
- ✅ Toast Notification System
- ⏳ Chat/Messaging System
- ⏳ Story/Status Features
- ⏳ Advanced Image/Video Upload
### Performance Optimierungen
- ⏳ Lazy Loading für Posts
- ⏳ Image Optimization
- ⏳ Caching System
- ⏳ API Rate Limiting
- ⏳ Database Indexing
## 📈 Kommende Phasen
### Phase 5: Community Features
- 🔲 Gruppen/Communities System
- 🔲 Events und Kalenderfunktion
- 🔲 Live Discussions/Chats
- 🔲 Trending Topics/Hashtags
- 🔲 User Verification System
- 🔲 Moderation Tools
### Phase 6: Advanced Features
- 🔲 AI-basierte Content Empfehlungen
- 🔲 Voice Notes und Audio Posts
- 🔲 Video Sharing und Streaming
- 🔲 Collaborative Mindmaps
- 🔲 Knowledge Graph Visualisierung
- 🔲 Advanced Analytics
### Phase 7: Monetarisierung & Skalierung
- 🔲 Premium Features
- 🔲 Creator Economy Tools
- 🔲 API für Drittanbieter
- 🔲 Mobile Apps (iOS/Android)
- 🔲 Enterprise Features
- 🔲 Advanced Security Features
### Phase 8: Integration & Ecosystem
- 🔲 External Tool Integrations
- 🔲 Learning Management System
- 🔲 Knowledge Base Integration
- 🔲 Research Tools
- 🔲 Publication System
- 🔲 Academic Collaboration Tools
## 🏗️ Technische Architektur
### Backend Stack ✅
- **Framework**: Flask mit SQLAlchemy
- **Datenbank**: SQLite (PostgreSQL für Produktion)
- **Authentifizierung**: Flask-Login
- **API**: RESTful JSON APIs
- **Logging**: **Erweiterte SocialNetworkLogger mit visuellen Features**
- **Farbige Konsolen-Ausgabe mit ANSI-Codes**
- **Emoji-basierte Kategorisierung (🔐 AUTH, 🌐 API, 💾 DB, etc.)**
- **Component-spezifisches Logging mit Performance-Monitoring**
- **JSON-strukturierte Logs für externe Analyse**
- **Decorator-basierte automatische Instrumentierung**
- **Performance**: Pagination, Caching
### Frontend Stack ✅
- **Styling**: TailwindCSS mit Custom Themes
- **JavaScript**: Vanilla JS mit ES6+ Features
- **Icons**: Font Awesome 6
- **Responsive**: Mobile-First Design
- **Interaktivität**: Alpine.js für reaktive Komponenten
### Database Schema ✅
```sql
-- Core Tables
users (erweitert mit Social Features)
social_posts (Posts System)
social_comments (Kommentar System)
notifications (Benachrichtigungssystem)
user_settings (Benutzereinstellungen)
activities (Aktivitätsverfolgung)
-- Relationship Tables
user_friendships (Freundschaftssystem)
user_follows (Follow System)
post_likes (Like System)
comment_likes (Comment Likes)
user_thought_bookmark (Bookmark System)
```
## 📊 API Endpunkte
### Social Feed APIs ✅
- `GET /api/social/posts` - Feed Posts abrufen
- `POST /api/social/posts` - Neuen Post erstellen
- `POST /api/social/posts/{id}/like` - Post liken/unliken
- `POST /api/social/posts/{id}/share` - Post teilen
- `POST /api/social/posts/{id}/bookmark` - Post bookmarken
### User Management APIs ✅
- `GET /api/social/users/{id}` - Benutzerprofil abrufen
- `GET /api/social/users/search` - Benutzer suchen
- `POST /api/social/users/{id}/follow` - Benutzer folgen/entfolgen
### Notification APIs ✅
- `GET /api/social/notifications` - Benachrichtigungen abrufen
- `POST /api/social/notifications/{id}/read` - Als gelesen markieren
- `POST /api/social/notifications/mark-all-read` - Alle als gelesen
- `DELETE /api/social/notifications/{id}` - Benachrichtigung löschen
### Analytics APIs ✅
- `GET /api/social/analytics/dashboard` - Benutzer-Analytics
- `GET /api/social/bookmarks` - Gebookmarkte Posts
## 🔒 Sicherheit & Datenschutz
### Implementierte Features ✅
- CSRF Protection
- SQL Injection Prevention
- Input Validation & Sanitization
- Session Management
- Password Hashing
- Privacy Controls (Post Visibility)
### Geplante Features
- 2FA Authentication
- Advanced Privacy Settings
- Data Export/Import
- GDPR Compliance Tools
- Content Moderation AI
## 📱 Mobile Support
### Aktuelle Features ✅
- Responsive Design
- Touch-Friendly Interface
- Mobile Navigation
- Optimized Loading
### Geplante Features
- PWA Support
- Offline Capabilities
- Push Notifications
- Native Mobile Apps
## 🎯 Leistungsziele
### Aktueller Status
- ✅ Grundlegende Performance
- ✅ Database Queries optimiert
- ✅ Frontend Responsiveness
- ✅ Strukturiertes Logging System
### Ziele für nächste Phase
- 🎯 < 200ms API Response Zeit
- 🎯 90+ Lighthouse Score
- 🎯 Skalierung auf 10k+ Benutzer
- 🎯 99.9% Uptime
## 🧪 Testing & Quality
### Implementiert
- ✅ Manuelle Testing
- ✅ Error Handling
-**Erweiterte Logging & Monitoring mit visuellen Features**
-**Farbige, kategorisierte Logs für bessere Debugging-Erfahrung**
-**Performance-Monitoring mit Zeitstempel**
-**Component-spezifische Fehlerbehandlung**
-**Strukturierte JSON-Logs für Analyse**
### Geplant
- 🔲 Automatisierte Unit Tests
- 🔲 Integration Tests
- 🔲 Performance Tests
- 🔲 Security Audits
- 🔲 Load Testing
- 🔲 **Log-basierte Alerting System**
- 🔲 **Automated Error Reporting**
## 📈 Metriken & Analytics
### User Engagement
- Posts pro Tag
- Kommentare und Likes
- Follow/Unfollow Raten
- Session Dauer
- Return User Rate
### System Performance
- API Response Zeiten
- Database Performance
- Error Rates
- User Activity Patterns
## 🛠️ Entwicklungsumgebung
### Setup Requirements ✅
```bash
# Virtual Environment
python3.11 -m venv venv
source venv/bin/activate
# Dependencies
pip install -r requirements.txt
# Database Migration
flask db upgrade
# Development Server
python3.11 app.py
```
### Development Tools ✅
- **IDE**: Cursor/VS Code
- **Version Control**: Git
- **Database**: SQLite (dev), PostgreSQL (prod)
- **Logging**: Colored Console + File Logging
- **Debug**: Flask Debug Mode
## 🌟 Innovation Features
### Einzigartige Aspekte
- 🧠 **Knowledge-First Design**: Fokus auf Wissensaustausch
- 🎨 **Mindmap Integration**: Visuelle Gedankenlandkarten
- 🔍 **Deep Search**: Semantic Search durch Inhalte
- 📊 **Learning Analytics**: Fortschritt und Erkenntnisse
- 🤝 **Collaborative Learning**: Gemeinsam Wissen erschaffen
### Zukünftige Innovationen
- 🤖 AI-Powered Knowledge Extraction
- 🎬 Interactive Learning Experiences
- 🌐 Cross-Platform Knowledge Sync
- 📚 Dynamic Knowledge Graphs
- 🧮 Algorithmic Learning Paths
---
## 📝 Aktuelle Tasks
### Hohe Priorität
1. ⏳ Chat/Messaging System implementieren
2. ⏳ Advanced Image Upload mit Preview
3. ⏳ Performance Optimierungen
4. ⏳ Mobile App Prototyp
### Mittlere Priorität
1. 🔲 Gruppen/Communities Feature
2. 🔲 Advanced Analytics Dashboard
3. 🔲 Content Moderation Tools
4. 🔲 API Rate Limiting
### Niedrige Priorität
1. 🔲 Email Benachrichtigungen
2. 🔲 Export/Import Features
3. 🔲 Advanced Search Filters
4. 🔲 Theming System
---
**Letzte Aktualisierung**: {{ current_date }}
**Version**: 2.0.0 - Social Network Release
**Status**: ✅ Fully Functional Social Platform
# 🗺️ SysTades Roadmap
## ✅ Abgeschlossen (v1.0 - v1.3)
### 🎯 Grundfunktionen
- [x] **Benutzerauthentifizierung** - Registrierung, Login, Logout
- [x] **Interaktive Mindmap** - Cytoscape.js-basierte Visualisierung
- [x] **Gedankenverwaltung** - CRUD-Operationen für Thoughts
- [x] **Kategoriesystem** - Hierarchische Wissensorganisation
- [x] **Responsive Design** - Mobile-first Ansatz
- [x] **Dark/Light Mode** - Benutzerfreundliche Themes
### 🤖 KI-Integration
- [x] **ChatGPT-Assistent** - Integrierter AI-Chat
- [x] **Intelligente Suche** - KI-gestützte Inhaltssuche
- [x] **Automatische Kategorisierung** - AI-basierte Thought-Klassifizierung
### 🎨 UI/UX Verbesserungen
- [x] **Moderne Navigation** - Glassmorphism-Design
- [x] **Animationen** - Smooth Transitions und Hover-Effekte
- [x] **Accessibility** - ARIA-Labels und Keyboard-Navigation
- [x] **Performance-Optimierung** - Lazy Loading und Caching
## 🚀 Neu implementiert (v1.4 - Social Network Update)
### 📱 Social Network Features
- [x] **Social Feed** - Instagram/Twitter-ähnlicher Feed
- [x] **Post-System** - Erstellen, Liken, Kommentieren von Posts
- [x] **Follow-System** - Benutzer folgen und entfolgen
- [x] **Discover-Seite** - Trending Posts und empfohlene Benutzer
- [x] **Benutzerprofile** - Erweiterte Profile mit Posts, Mindmaps, Gedanken
- [x] **Benachrichtigungssystem** - Likes, Kommentare, Follows
- [x] **Community-Statistiken** - Aktive Benutzer, Posts, Mindmaps
### 🧠 Erweiterte Mindmap-Features
- [x] **Kollaborative Bearbeitung** - Vorbereitung für Echtzeit-Kollaboration
- [x] **Mindmap-Export** - JSON-Export mit geplanten weiteren Formaten
- [x] **Mindmap-Sharing** - Teilen von Mindmaps in sozialen Netzwerken
- [x] **Erweiterte Toolbar** - Neue Bearbeitungsoptionen
- [x] **Vollbild-Modus** - Immersive Mindmap-Bearbeitung
- [x] **Schnelle Knoten-/Gedanken-Erstellung** - Direkt aus der Mindmap
### 🔗 Integration & Vernetzung
- [x] **Gedanken in Posts teilen** - Wissenschaftliche Inhalte im Feed
- [x] **Mindmap-Knoten teilen** - Wissensbausteine verbreiten
- [x] **Cross-Platform Navigation** - Nahtlose Übergänge zwischen Features
- [x] **Unified Search** - Suche über alle Inhaltstypen
## 🔄 In Entwicklung (v1.5)
### 🔄 Echtzeit-Features
- [ ] **Live-Kollaboration** - Mehrere Benutzer bearbeiten gleichzeitig Mindmaps
- [ ] **WebSocket-Integration** - Echtzeit-Updates für Feed und Benachrichtigungen
- [ ] **Live-Cursor** - Sehen wo andere Benutzer arbeiten
- [ ] **Änderungshistorie** - Versionskontrolle für Mindmaps
### 💬 Erweiterte Kommunikation
- [ ] **Direktnachrichten** - Private Nachrichten zwischen Benutzern
- [ ] **Gruppen-Chats** - Themenbasierte Diskussionsgruppen
- [ ] **Video-Calls** - Integrierte Videokonferenzen für Kollaboration
- [ ] **Screen-Sharing** - Bildschirm teilen während Kollaboration
## 📋 Geplant (v1.6 - v2.0)
### 📊 Analytics & Insights
- [ ] **Lernfortschritt-Tracking** - Persönliche Wissensstatistiken
- [ ] **Mindmap-Analytics** - Nutzungsstatistiken und Hotspots
- [ ] **Community-Insights** - Trending-Themen und beliebte Inhalte
- [ ] **Empfehlungsalgorithmus** - Personalisierte Inhaltsvorschläge
### 🎓 Bildungsfeatures
- [ ] **Kurssystem** - Strukturierte Lernpfade
- [ ] **Quizzes & Tests** - Wissensüberprüfung
- [ ] **Zertifikate** - Digitale Abschlüsse
- [ ] **Mentoring-System** - Experten-Schüler-Verbindungen
### 🔧 Erweiterte Tools
- [ ] **PDF-Import** - Automatische Mindmap-Generierung aus Dokumenten
- [ ] **LaTeX-Support** - Mathematische Formeln in Gedanken
- [ ] **Multimedia-Integration** - Videos, Audio, Bilder in Mindmaps
- [ ] **API für Drittanbieter** - Integration mit anderen Tools
### 🌐 Skalierung & Performance
- [ ] **Microservices-Architektur** - Bessere Skalierbarkeit
- [ ] **CDN-Integration** - Globale Content-Delivery
- [ ] **Caching-Optimierung** - Redis für bessere Performance
- [ ] **Load Balancing** - Hochverfügbarkeit
## 🔮 Vision (v2.0+)
### 🤖 Erweiterte KI
- [ ] **Personalisierte KI-Tutoren** - Individuelle Lernbegleitung
- [ ] **Automatische Mindmap-Generierung** - KI erstellt Mindmaps aus Text
- [ ] **Intelligente Verbindungen** - KI schlägt Gedankenverknüpfungen vor
- [ ] **Adaptive Lernpfade** - KI passt Inhalte an Lernstil an
### 🌍 Globale Community
- [ ] **Mehrsprachigkeit** - Internationale Benutzergemeinschaft
- [ ] **Kultureller Austausch** - Globale Wissensnetzwerke
- [ ] **Übersetzungsfeatures** - Automatische Inhaltsübersetzung
- [ ] **Regionale Communities** - Lokale Wissensgruppen
### 🔬 Forschungstools
- [ ] **Literaturverwaltung** - Integration mit wissenschaftlichen Datenbanken
- [ ] **Zitiersystem** - Automatische Quellenangaben
- [ ] **Peer-Review-System** - Wissenschaftliche Qualitätskontrolle
- [ ] **Publikationstools** - Direkte Veröffentlichung von Forschungsergebnissen
---
## 📈 Metriken & Ziele
### Technische Ziele
- **Performance**: < 2s Ladezeit für alle Seiten
- **Verfügbarkeit**: 99.9% Uptime
- **Skalierbarkeit**: 10.000+ gleichzeitige Benutzer
- **Sicherheit**: Zero-Trust-Architektur
### Community-Ziele
- **Benutzer**: 1.000+ aktive Benutzer bis Ende 2024
- **Inhalte**: 10.000+ Gedanken und 1.000+ Mindmaps
- **Engagement**: 70%+ monatliche Aktivitätsrate
- **Zufriedenheit**: 4.5+ Sterne Bewertung
---
## 🤝 Beitragen
Interessiert an der Mitarbeit? Hier sind die Bereiche, in denen wir Unterstützung suchen:
### 👨‍💻 Entwicklung
- **Frontend**: React/Vue.js Komponenten
- **Backend**: Python/Flask API-Entwicklung
- **Mobile**: React Native App
- **DevOps**: Docker, Kubernetes, CI/CD
### 🎨 Design
- **UI/UX**: Benutzeroberflächen-Design
- **Grafik**: Icons, Illustrationen, Branding
- **Animation**: Micro-Interactions und Transitions
- **Accessibility**: Barrierefreie Gestaltung
### 📝 Content
- **Dokumentation**: Technische und Benutzer-Dokumentation
- **Tutorials**: Video- und Text-Anleitungen
- **Übersetzungen**: Mehrsprachige Inhalte
- **Community**: Moderation und Support
---
*Letzte Aktualisierung: Januar 2024*
*Version: 1.4.0 - Social Network Update*

125
TOOLS.py Normal file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
TOOLS.py - Main utility script for the website application.
This script provides a command-line interface to all utilities
for database management, user management, and server administration.
Usage:
python3 TOOLS.py [command] [options]
Available commands:
- db:fix Fix database schema
- db:rebuild Completely rebuild the database
- db:test Test database connection and models
- db:stats Show database statistics
- user:list List all users
- user:create Create a new user
- user:admin Create admin user (username: admin, password: admin)
- user:reset-pw Reset user password
- user:delete Delete a user
- server:run Run the development server
Examples:
python3 TOOLS.py db:rebuild
python3 TOOLS.py user:admin
python3 TOOLS.py server:run --port 8080
"""
import os
import sys
import argparse
from utils import (
fix_database_schema, rebuild_database, run_all_tests, print_database_stats,
list_users, create_user, reset_password, delete_user, create_admin_user,
run_development_server
)
def parse_args():
parser = argparse.ArgumentParser(
description='Website Administration Tools',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
# Main command argument
parser.add_argument('command', help='Command to execute')
# Additional arguments
parser.add_argument('--username', '-u', help='Username for user commands')
parser.add_argument('--email', '-e', help='Email for user creation')
parser.add_argument('--password', '-p', help='Password for user creation/reset')
parser.add_argument('--admin', '-a', action='store_true', help='Make user an admin')
parser.add_argument('--host', help='Host for server (default: 127.0.0.1)')
parser.add_argument('--port', type=int, help='Port for server (default: 5000)')
parser.add_argument('--no-debug', action='store_true', help='Disable debug mode for server')
return parser.parse_args()
def main():
args = parse_args()
# Database commands
if args.command == 'db:fix':
fix_database_schema()
elif args.command == 'db:rebuild':
print("WARNING: This will delete all data in the database!")
confirm = input("Are you sure you want to continue? (y/n): ").lower()
if confirm == 'y':
rebuild_database()
else:
print("Aborted.")
elif args.command == 'db:test':
run_all_tests()
elif args.command == 'db:stats':
print_database_stats()
# User commands
elif args.command == 'user:list':
list_users()
elif args.command == 'user:create':
if not args.username or not args.email or not args.password:
print("Error: Username, email, and password are required.")
print("Example: python3 TOOLS.py user:create -u username -e email -p password [-a]")
sys.exit(1)
create_user(args.username, args.email, args.password, args.admin)
elif args.command == 'user:admin':
create_admin_user()
elif args.command == 'user:reset-pw':
if not args.username or not args.password:
print("Error: Username and password are required.")
print("Example: python3 TOOLS.py user:reset-pw -u username -p new_password")
sys.exit(1)
reset_password(args.username, args.password)
elif args.command == 'user:delete':
if not args.username:
print("Error: Username is required.")
print("Example: python3 TOOLS.py user:delete -u username")
sys.exit(1)
delete_user(args.username)
# Server commands
elif args.command == 'server:run':
host = args.host or '127.0.0.1'
port = args.port or 5000
debug = not args.no_debug
run_development_server(host=host, port=port, debug=debug)
else:
print(f"Unknown command: {args.command}")
print("Run 'python3 TOOLS.py -h' for usage information")
sys.exit(1)
if __name__ == '__main__':
main()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3350
app.py Normal file

File diff suppressed because it is too large Load Diff

2526
app.py.bak Normal file

File diff suppressed because it is too large Load Diff

BIN
backup/archiv_0.1.zip Normal file

Binary file not shown.

5
cookies.txt Normal file
View File

@@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_127.0.0.1 FALSE / FALSE 0 session .eJwlzjEOwjAMQNG7ZGaIYztJe5nKjm2BACG1dELcnUiMf3n6n7TF7sc1re_99EvabpbWZI7cikB4dsoylLrmcKXSormH-OhKoQRSAy0v3kEzDqJlSNFCg8NIW25sfYChAgryFIWxdqyskqFWtNIdiF3awiZRaq9TzGmOnIfv_xuYabLft-fLPK0hj8O_P-1dNpA.aDdoog.bmKi2y6o3HQgIk4gwDvhirnxuoM

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

Binary file not shown.

Binary file not shown.

BIN
database/systades.db Normal file

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 +1,17 @@
version: "3.9"
version: '3.9'
services:
web:
build: .
image: systades_app:latest
container_name: systades_app
restart: always
env_file:
- .env
ports:
- "5000:5000"
restart: always
volumes:
- ./database:/app/database
volumes:
db_data:

15
example.env Normal file
View File

@@ -0,0 +1,15 @@
# MindMap Umgebungsvariablen
# Kopiere diese Datei zu .env und passe die Werte an
# Flask
FLASK_APP=app.py
FLASK_ENV=development
SECRET_KEY=mein-sicherer-schluessel-fuer-entwicklung
# OpenAI API
OPENAI_API_KEY=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
# Datenbank
# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden
# Der Pfad wird relativ zum Projektverzeichnis angegeben
SQLALCHEMY_DATABASE_URI=sqlite:///database/systades.db

322
init_db.py Normal file
View File

@@ -0,0 +1,322 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sqlite3
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
# Pfad zur Datenbank
basedir = os.path.abspath(os.path.dirname(__file__))
db_path = os.path.join(basedir, 'database', 'systades.db')
# Stelle sicher, dass das Verzeichnis existiert
db_dir = os.path.dirname(db_path)
os.makedirs(db_dir, exist_ok=True)
# Erstelle eine temporäre Flask-App, um die Datenbank zu initialisieren
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Importiere die Modelle nach der App-Initialisierung
from models import db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating, RelationType
from models import Category, UserMindmap, UserMindmapNode, MindmapNote
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"""
# Hauptkategorien
main_categories = [
{
"name": "Philosophie",
"description": "Philosophisches Denken und Konzepte",
"color_code": "#9F7AEA",
"icon": "fa-brain"
},
{
"name": "Wissenschaft",
"description": "Wissenschaftliche Disziplinen und Erkenntnisse",
"color_code": "#60A5FA",
"icon": "fa-flask"
},
{
"name": "Technologie",
"description": "Technologische Entwicklungen und Anwendungen",
"color_code": "#10B981",
"icon": "fa-microchip"
},
{
"name": "Künste",
"description": "Künstlerische Ausdrucksformen und Werke",
"color_code": "#F59E0B",
"icon": "fa-palette"
},
{
"name": "Psychologie",
"description": "Mentale Prozesse und Verhaltensweisen",
"color_code": "#EF4444",
"icon": "fa-brain"
}
]
# Hauptkategorien erstellen
category_map = {}
for cat_data in main_categories:
category = Category(**cat_data)
db.session.add(category)
db.session.flush() # ID generieren
category_map[cat_data["name"]] = category
# Unterkategorien für Philosophie
philosophy_subcategories = [
{"name": "Ethik", "description": "Moralische Grundsätze", "icon": "fa-balance-scale", "color_code": "#8B5CF6"},
{"name": "Logik", "description": "Gesetze des Denkens", "icon": "fa-project-diagram", "color_code": "#8B5CF6"},
{"name": "Erkenntnistheorie", "description": "Natur des Wissens", "icon": "fa-lightbulb", "color_code": "#8B5CF6"}
]
# Unterkategorien für Wissenschaft
science_subcategories = [
{"name": "Physik", "description": "Studie der Materie und Energie", "icon": "fa-atom", "color_code": "#3B82F6"},
{"name": "Biologie", "description": "Studie des Lebens", "icon": "fa-dna", "color_code": "#3B82F6"},
{"name": "Mathematik", "description": "Studie der Zahlen und Strukturen", "icon": "fa-square-root-alt", "color_code": "#3B82F6"}
]
# Unterkategorien für Technologie
tech_subcategories = [
{"name": "Software", "description": "Computerprogramme und Anwendungen", "icon": "fa-code", "color_code": "#059669"},
{"name": "Hardware", "description": "Physische Komponenten der Technik", "icon": "fa-microchip", "color_code": "#059669"},
{"name": "Internet", "description": "Globales Netzwerk und Web", "icon": "fa-globe", "color_code": "#059669"}
]
# Unterkategorien für Künste
arts_subcategories = [
{"name": "Musik", "description": "Klangkunst", "icon": "fa-music", "color_code": "#D97706"},
{"name": "Literatur", "description": "Geschriebene Kunst", "icon": "fa-book", "color_code": "#D97706"},
{"name": "Bildende Kunst", "description": "Visuelle Kunst", "icon": "fa-paint-brush", "color_code": "#D97706"}
]
# Unterkategorien für Psychologie
psychology_subcategories = [
{"name": "Kognition", "description": "Gedächtnisprozesse und Denken", "icon": "fa-brain", "color_code": "#DC2626"},
{"name": "Emotionen", "description": "Gefühle und emotionale Prozesse", "icon": "fa-heart", "color_code": "#DC2626"},
{"name": "Verhalten", "description": "Beobachtbares Verhalten und Reaktionen", "icon": "fa-user", "color_code": "#DC2626"}
]
# Alle Unterkategorien zu ihren Hauptkategorien hinzufügen
for subcat_data in philosophy_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Philosophie"].id
db.session.add(subcat)
for subcat_data in science_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Wissenschaft"].id
db.session.add(subcat)
for subcat_data in tech_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Technologie"].id
db.session.add(subcat)
for subcat_data in arts_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Künste"].id
db.session.add(subcat)
for subcat_data in psychology_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Psychologie"].id
db.session.add(subcat)
db.session.commit()
print(f"{len(main_categories)} Hauptkategorien und {len(philosophy_subcategories + science_subcategories + tech_subcategories + arts_subcategories + psychology_subcategories)} Unterkategorien 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")

0
instance/logs/errors.log Normal file
View File

0
instance/logs/social.log Normal file
View File

0
logs/api.log Normal file
View File

2419
logs/app.log Normal file

File diff suppressed because it is too large Load Diff

424
logs/errors.log Normal file
View File

@@ -0,0 +1,424 @@
2025-05-28 21:29:08 | ERROR | SysTades | ERROR | Fehler 500: 405 Method Not Allowed: The method is not allowed for the requested URL.
Endpoint: /api/thoughts, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 619, in match
raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None
werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.
2025-05-28 21:43:40 | ERROR | SysTades | ERROR | Fehler in social_feed nach 2.83ms - Exception: AttributeError: followed_id
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1633, in __getattr__
return self._index[key][1]
~~~~~~~~~~~^^^^^
KeyError: 'followed_id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2774, in social_feed
followed_posts = current_user.get_feed_posts(limit=100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/models.py", line 193, in get_feed_posts
followed_users, SocialPost.user_id == followed_users.c.followed_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1635, in __getattr__
raise AttributeError(key) from err
AttributeError: followed_id
2025-05-28 21:43:40 | ERROR | SysTades | ERROR | Fehler 500: followed_id
Endpoint: /feed, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1633, in __getattr__
return self._index[key][1]
~~~~~~~~~~~^^^^^
KeyError: 'followed_id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2774, in social_feed
followed_posts = current_user.get_feed_posts(limit=100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/models.py", line 193, in get_feed_posts
followed_users, SocialPost.user_id == followed_users.c.followed_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1635, in __getattr__
raise AttributeError(key) from err
AttributeError: followed_id
2025-05-28 21:43:59 | ERROR | SysTades | ERROR | Fehler in discover nach 16.89ms - Exception: AttributeError: 'AppenderQuery' object has no attribute 'contains'
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2800, in discover
~current_user.following.contains(User.id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'AppenderQuery' object has no attribute 'contains'
2025-05-28 21:43:59 | ERROR | SysTades | ERROR | Fehler 500: 'AppenderQuery' object has no attribute 'contains'
Endpoint: /discover, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2800, in discover
~current_user.following.contains(User.id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'AppenderQuery' object has no attribute 'contains'
2025-05-28 21:46:15 | ERROR | SysTades | ERROR | Fehler in social_feed nach 54.92ms - Exception: OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: near "UNION": syntax error
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2782, in social_feed
posts = all_posts.paginate(
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/query.py", line 98, in paginate
return QueryPagination(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 72, in __init__
items = self._query_items()
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 358, in _query_items
out = query.limit(self.per_page).offset(self._query_offset).all()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2693, in all
return self._iter().all() # type: ignore
^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
return self._execute_internal(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
result: Result[Any] = compile_state_cls.orm_execute_statement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
result = conn.execute(
^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
return meth(
^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
return connection._execute_clauseelement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
ret = self._execute_context(
^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2025-05-28 21:46:15 | ERROR | SysTades | ERROR | Fehler 500: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Endpoint: /feed, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: near "UNION": syntax error
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2782, in social_feed
posts = all_posts.paginate(
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/query.py", line 98, in paginate
return QueryPagination(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 72, in __init__
items = self._query_items()
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 358, in _query_items
out = query.limit(self.per_page).offset(self._query_offset).all()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2693, in all
return self._iter().all() # type: ignore
^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
return self._execute_internal(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
result: Result[Any] = compile_state_cls.orm_execute_statement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
result = conn.execute(
^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
return meth(
^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
return connection._execute_clauseelement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
ret = self._execute_context(
^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2025-05-28 21:48:48 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /sw.js, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:48:54 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /static/fonts/inter-regular.woff2, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 257, in <lambda>
view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 305, in send_static_file
return send_from_directory(
^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/helpers.py", line 554, in send_from_directory
return werkzeug.utils.send_from_directory( # type: ignore[return-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/utils.py", line 574, in send_from_directory
raise NotFound()
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:49:17 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /sw.js, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:55:55 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:55:55 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:56:25 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /auth/login, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:56:41 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:57:25 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(user_follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:58:02 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
users = User.query.filter(
NameError: name 'follows' is not defined

1
migrations/README Normal file
View File

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

Binary file not shown.

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,38 @@
"""add mindmap shares table
Revision ID: add_mindmap_shares
Revises: add_missing_user_fields
Create Date: 2025-05-10 23:20:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import sqlite
# revision identifiers, used by Alembic.
revision = 'add_mindmap_shares'
down_revision = 'add_missing_user_fields'
branch_labels = None
depends_on = None
def upgrade():
# Erstelle PermissionType Enum
op.create_table('mindmap_share',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('mindmap_id', sa.Integer(), nullable=False),
sa.Column('shared_by_id', sa.Integer(), nullable=False),
sa.Column('shared_with_id', sa.Integer(), nullable=False),
sa.Column('permission_type', sa.String(20), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('last_accessed', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['mindmap_id'], ['user_mindmap.id'], ),
sa.ForeignKeyConstraint(['shared_by_id'], ['user.id'], ),
sa.ForeignKeyConstraint(['shared_with_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('mindmap_id', 'shared_with_id', name='unique_mindmap_share')
)
def downgrade():
op.drop_table('mindmap_share')

View File

@@ -0,0 +1,40 @@
"""Add missing user fields
Revision ID: 5a23f8c6db37
Revises: d4406f5b12f7
Create Date: 2025-05-02 10:45:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '5a23f8c6db37'
down_revision = 'd4406f5b12f7'
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('bio', sa.Text(), nullable=True))
batch_op.add_column(sa.Column('location', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('website', sa.String(length=200), nullable=True))
batch_op.add_column(sa.Column('avatar', sa.String(length=200), nullable=True))
batch_op.add_column(sa.Column('last_login', sa.DateTime(), nullable=True))
# ### 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.drop_column('last_login')
batch_op.drop_column('avatar')
batch_op.drop_column('website')
batch_op.drop_column('location')
batch_op.drop_column('bio')
# ### end Alembic commands ###

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

695
models.py Normal file
View File

@@ -0,0 +1,695 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask_sqlalchemy import SQLAlchemy
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()
# Beziehungstypen für Gedankenverknüpfungen
class RelationType(Enum):
SUPPORTS = "stützt"
CONTRADICTS = "widerspricht"
BUILDS_UPON = "baut auf auf"
GENERALIZES = "verallgemeinert"
SPECIFIES = "spezifiziert"
INSPIRES = "inspiriert"
# Beziehungstabelle für viele-zu-viele Beziehung zwischen MindMapNodes
node_relationship = db.Table('node_relationship',
db.Column('parent_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
db.Column('child_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True)
)
# Beziehungstabelle für öffentliche Knoten und Gedanken
node_thought_association = db.Table('node_thought_association',
db.Column('node_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
)
# Beziehungstabelle für Benutzer-spezifische Mindmap-Knoten und Gedanken
user_mindmap_thought_association = db.Table('user_mindmap_thought_association',
db.Column('user_mindmap_id', db.Integer, db.ForeignKey('user_mindmap.id'), primary_key=True),
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
)
# Beziehungstabelle für Benutzer-Bookmarks von Gedanken
user_thought_bookmark = db.Table('user_thought_bookmark',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
# Beziehungstabelle für Benutzer-Freundschaften
user_friendships = db.Table('user_friendships',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('friend_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow),
db.Column('status', db.String(20), default='pending') # pending, accepted, blocked
)
# Beziehungstabelle für Benutzer-Follows
user_follows = db.Table('user_follows',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
# Beziehungstabelle für Post-Likes
post_likes = db.Table('post_likes',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('post_id', db.Integer, db.ForeignKey('social_post.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
# Beziehungstabelle für Comment-Likes
comment_likes = db.Table('comment_likes',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('comment_id', db.Integer, db.ForeignKey('social_comment.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
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 = db.Column(db.String(512), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
is_active = db.Column(db.Boolean, default=True)
role = db.Column(db.String(20), default="user") # 'user', 'admin', 'moderator'
bio = db.Column(db.Text, nullable=True) # Profil-Bio
location = db.Column(db.String(100), nullable=True) # Standort
website = db.Column(db.String(200), nullable=True) # Website
avatar = db.Column(db.String(200), nullable=True) # Profilbild-URL
last_login = db.Column(db.DateTime, nullable=True) # Letzter Login
# Social Network Felder
display_name = db.Column(db.String(100), nullable=True) # Anzeigename
birth_date = db.Column(db.Date, nullable=True) # Geburtsdatum
gender = db.Column(db.String(20), nullable=True) # Geschlecht
phone = db.Column(db.String(20), nullable=True) # Telefonnummer
is_verified = db.Column(db.Boolean, default=False) # Verifizierter Account
is_private = db.Column(db.Boolean, default=False) # Privater Account
follower_count = db.Column(db.Integer, default=0) # Follower-Anzahl
following_count = db.Column(db.Integer, default=0) # Following-Anzahl
post_count = db.Column(db.Integer, default=0) # Post-Anzahl
online_status = db.Column(db.String(20), default='offline') # online, offline, away
last_seen = db.Column(db.DateTime, nullable=True) # Zuletzt gesehen
# Beziehungen
threads = db.relationship('Thread', backref='creator', lazy=True)
messages = db.relationship('Message', backref='author', lazy=True)
projects = db.relationship('Project', backref='owner', lazy=True)
mindmaps = db.relationship('UserMindmap', backref='user', lazy=True)
thoughts = db.relationship('Thought', backref='author', lazy=True)
bookmarked_thoughts = db.relationship('Thought', secondary=user_thought_bookmark,
lazy='dynamic', backref=db.backref('bookmarked_by', lazy='dynamic'))
# Social Network Beziehungen
posts = db.relationship('SocialPost', backref='author', lazy=True, cascade="all, delete-orphan")
comments = db.relationship('SocialComment', backref='author', lazy=True, cascade="all, delete-orphan")
notifications = db.relationship('Notification', foreign_keys='Notification.user_id', backref='user', lazy=True, cascade="all, delete-orphan")
# Freundschaften (bidirektional)
friends = db.relationship(
'User',
secondary=user_friendships,
primaryjoin=id == user_friendships.c.user_id,
secondaryjoin=id == user_friendships.c.friend_id,
backref='friend_of',
lazy='dynamic'
)
# Following/Followers
following = db.relationship(
'User',
secondary=user_follows,
primaryjoin=id == user_follows.c.follower_id,
secondaryjoin=id == user_follows.c.followed_id,
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
)
# Liked Posts und Comments
liked_posts = db.relationship('SocialPost', secondary=post_likes,
backref=db.backref('liked_by', lazy='dynamic'), lazy='dynamic')
liked_comments = db.relationship('SocialComment', secondary=comment_likes,
backref=db.backref('liked_by', lazy='dynamic'), lazy='dynamic')
def __repr__(self):
return f'<User {self.username}>'
def set_password(self, password):
self.password = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password, password)
@property
def is_admin(self):
return self.role == 'admin'
@is_admin.setter
def is_admin(self, value):
self.role = 'admin' if value else 'user'
# Social Network Methoden
def follow(self, user):
"""Folgt einem anderen Benutzer"""
if not self.is_following(user):
self.following.append(user)
user.follower_count += 1
user.following_count += 1
# Notification erstellen
notification = Notification(
user_id=user.id,
type='follow',
message=f'{self.username} folgt dir jetzt',
related_user_id=self.id
)
db.session.add(notification)
def unfollow(self, user):
"""Entfolgt einem Benutzer"""
if self.is_following(user):
self.following.remove(user)
user.follower_count -= 1
user.following_count -= 1
def is_following(self, user):
"""Prüft ob der Benutzer einem anderen folgt"""
return self.following.filter(user_follows.c.followed_id == user.id).count() > 0
def get_feed_posts(self, limit=20):
"""Holt Posts für den Feed (von gefolgten Benutzern)"""
# Hole alle User-IDs von Benutzern, denen ich folge + meine eigene
followed_user_ids = [user.id for user in self.following]
all_user_ids = followed_user_ids + [self.id]
# Hole Posts von diesen Benutzern
return SocialPost.query.filter(
SocialPost.user_id.in_(all_user_ids)
).order_by(SocialPost.created_at.desc()).limit(limit)
class Category(db.Model):
"""Wissenschaftliche Kategorien für die Gliederung der öffentlichen Mindmap"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
description = db.Column(db.Text)
color_code = db.Column(db.String(7)) # Hex color
icon = db.Column(db.String(50))
parent_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True)
# Beziehungen
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)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
color_code = db.Column(db.String(7))
icon = db.Column(db.String(50))
is_public = db.Column(db.Boolean, default=True)
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',
secondary=node_relationship,
primaryjoin=(node_relationship.c.child_id == id),
secondaryjoin=(node_relationship.c.parent_id == id),
backref=db.backref('children', lazy='dynamic'),
lazy='dynamic'
)
# Beziehungen zu Gedanken
thoughts = db.relationship('Thought',
secondary=node_thought_association,
backref=db.backref('nodes', lazy='dynamic'))
# 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)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
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)
is_private = db.Column(db.Boolean, default=True)
# Beziehungen zu öffentlichen Knoten
public_nodes = db.relationship('MindMapNode',
secondary='user_mindmap_node',
backref=db.backref('in_user_mindmaps', lazy='dynamic'))
# Beziehungen zu Gedanken
thoughts = db.relationship('Thought',
secondary=user_mindmap_thought_association,
backref=db.backref('in_user_mindmaps', lazy='dynamic'))
# Notizen zu dieser Mindmap
notes = db.relationship('MindmapNote', backref='mindmap', lazy=True)
# Beziehungstabelle für benutzerorientierte Mindmaps und öffentliche Knoten
class UserMindmapNode(db.Model):
"""Speichert die Beziehung zwischen Benutzer-Mindmaps und öffentlichen Knoten inkl. Position"""
user_mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), primary_key=True)
node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True)
x_position = db.Column(db.Float, default=0) # Position X auf der Mindmap
y_position = db.Column(db.Float, default=0) # Position Y auf der Mindmap
scale = db.Column(db.Float, default=1.0) # Größe des Knotens
added_at = db.Column(db.DateTime, default=datetime.utcnow)
class MindmapNote(db.Model):
"""Private Notizen der Benutzer zu ihrer Mindmap"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), nullable=False)
node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
color_code = db.Column(db.String(7), default="#FFF59D") # Farbe der Notiz
class Thought(db.Model):
"""Gedanken und Inhalte, die in der Mindmap verknüpft werden können"""
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
abstract = db.Column(db.Text)
keywords = db.Column(db.String(500))
color_code = db.Column(db.String(7)) # Hex color code
source_type = db.Column(db.String(50)) # PDF, Markdown, Text etc.
branch = db.Column(db.String(100), 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)
# Beziehungen
comments = db.relationship('Comment', backref='thought', lazy=True, cascade="all, delete-orphan")
ratings = db.relationship('ThoughtRating', backref='thought', lazy=True)
outgoing_relations = db.relationship(
'ThoughtRelation',
foreign_keys='ThoughtRelation.source_id',
backref='source_thought',
lazy=True
)
incoming_relations = db.relationship(
'ThoughtRelation',
foreign_keys='ThoughtRelation.target_id',
backref='target_thought',
lazy=True
)
@property
def average_rating(self):
if not self.ratings:
return 0
return sum(r.relevance_score for r in self.ratings) / len(self.ratings)
class ThoughtRelation(db.Model):
"""Beziehungen zwischen Gedanken"""
id = db.Column(db.Integer, primary_key=True)
source_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
target_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
relation_type = db.Column(db.Enum(RelationType), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Beziehung zum Ersteller
created_by = db.relationship('User', backref='created_relations')
class ThoughtRating(db.Model):
"""Bewertungen von Gedanken durch Benutzer"""
id = db.Column(db.Integer, primary_key=True)
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
relevance_score = db.Column(db.Integer, nullable=False) # 1-5
created_at = db.Column(db.DateTime, default=datetime.utcnow)
__table_args__ = (
db.UniqueConstraint('thought_id', 'user_id', name='unique_thought_rating'),
)
# Beziehung zum Benutzer
user = db.relationship('User', backref='ratings')
class Comment(db.Model):
"""Kommentare zu Gedanken"""
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
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)
# 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}>'
# Forum-Kategorie-Modell - entspricht den Hauptknotenpunkten der Mindmap
class ForumCategory(db.Model):
id = db.Column(db.Integer, primary_key=True)
node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=False)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
is_active = db.Column(db.Boolean, default=True)
# Beziehungen
node = db.relationship('MindMapNode', backref='forum_category')
posts = db.relationship('ForumPost', backref='category', lazy=True, cascade="all, delete-orphan")
def __repr__(self):
return f'<ForumCategory {self.title}>'
# Forum-Beitrag-Modell
class ForumPost(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
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('forum_category.id'), nullable=False)
parent_id = db.Column(db.Integer, db.ForeignKey('forum_post.id'), nullable=True)
is_pinned = db.Column(db.Boolean, default=False)
is_locked = db.Column(db.Boolean, default=False)
view_count = db.Column(db.Integer, default=0)
# Beziehungen
author = db.relationship('User', backref='forum_posts')
replies = db.relationship('ForumPost', backref=db.backref('parent', remote_side=[id]), lazy=True)
def __repr__(self):
return f'<ForumPost {self.title}>'
# Berechtigungstypen für Mindmap-Freigaben
class PermissionType(Enum):
READ = "Nur-Lesen"
EDIT = "Bearbeiten"
ADMIN = "Administrator"
# Freigabemodell für Mindmaps
class MindmapShare(db.Model):
"""Speichert Informationen über freigegebene Mindmaps und Berechtigungen"""
id = db.Column(db.Integer, primary_key=True)
mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), nullable=False)
shared_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
shared_with_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
permission_type = db.Column(db.Enum(PermissionType), nullable=False, default=PermissionType.READ)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_accessed = db.Column(db.DateTime, nullable=True)
# Beziehungen
mindmap = db.relationship('UserMindmap', backref=db.backref('shares', lazy='dynamic'))
shared_by = db.relationship('User', foreign_keys=[shared_by_id], backref=db.backref('shared_mindmaps', lazy='dynamic'))
shared_with = db.relationship('User', foreign_keys=[shared_with_id], backref=db.backref('accessible_mindmaps', lazy='dynamic'))
__table_args__ = (
db.UniqueConstraint('mindmap_id', 'shared_with_id', name='unique_mindmap_share'),
)
def __repr__(self):
return f'<MindmapShare: {self.mindmap_id} - {self.shared_with_id} - {self.permission_type.name}>'
class SocialPost(db.Model):
"""Posts im Social Network"""
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
image_url = db.Column(db.String(500), nullable=True) # Bild-URL
video_url = db.Column(db.String(500), nullable=True) # Video-URL
link_url = db.Column(db.String(500), nullable=True) # Link-URL
link_title = db.Column(db.String(200), nullable=True) # Link-Titel
link_description = db.Column(db.Text, nullable=True) # Link-Beschreibung
post_type = db.Column(db.String(20), default='text') # text, image, video, link, thought_share
visibility = db.Column(db.String(20), default='public') # public, friends, private
is_pinned = db.Column(db.Boolean, default=False)
like_count = db.Column(db.Integer, default=0)
comment_count = db.Column(db.Integer, default=0)
share_count = db.Column(db.Integer, default=0)
view_count = db.Column(db.Integer, default=0)
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)
# Verknüpfung zu Gedanken (falls der Post einen Gedanken teilt)
shared_thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
shared_thought = db.relationship('Thought', backref='shared_in_posts')
# Verknüpfung zu Mindmap-Knoten
shared_node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
shared_node = db.relationship('MindMapNode', backref='shared_in_posts')
# Kommentare zu diesem Post
comments = db.relationship('SocialComment', backref='post', lazy=True, cascade="all, delete-orphan")
def __repr__(self):
return f'<SocialPost {self.id} by {self.author.username}>'
def to_dict(self):
return {
'id': self.id,
'content': self.content,
'post_type': self.post_type,
'image_url': self.image_url,
'video_url': self.video_url,
'link_url': self.link_url,
'link_title': self.link_title,
'link_description': self.link_description,
'visibility': self.visibility,
'is_pinned': self.is_pinned,
'like_count': self.like_count,
'comment_count': self.comment_count,
'share_count': self.share_count,
'view_count': self.view_count,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat(),
'author': {
'id': self.author.id,
'username': self.author.username,
'display_name': self.author.display_name or self.author.username,
'avatar': self.author.avatar,
'is_verified': self.author.is_verified
},
'shared_thought': self.shared_thought.to_dict() if self.shared_thought else None,
'shared_node': self.shared_node.to_dict() if self.shared_node else None
}
class SocialComment(db.Model):
"""Kommentare zu Posts"""
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
like_count = db.Column(db.Integer, default=0)
reply_count = db.Column(db.Integer, default=0)
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)
post_id = db.Column(db.Integer, db.ForeignKey('social_post.id'), nullable=False)
parent_id = db.Column(db.Integer, db.ForeignKey('social_comment.id'), nullable=True)
# Antworten auf diesen Kommentar
replies = db.relationship('SocialComment', backref=db.backref('parent', remote_side=[id]), lazy=True)
def __repr__(self):
return f'<SocialComment {self.id} by {self.author.username}>'
def to_dict(self):
return {
'id': self.id,
'content': self.content,
'like_count': self.like_count,
'reply_count': self.reply_count,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat(),
'author': {
'id': self.author.id,
'username': self.author.username,
'display_name': self.author.display_name or self.author.username,
'avatar': self.author.avatar,
'is_verified': self.author.is_verified
},
'parent_id': self.parent_id
}
class Notification(db.Model):
"""Benachrichtigungen für Benutzer"""
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(50), nullable=False) # follow, like, comment, mention, friend_request, etc.
message = db.Column(db.String(500), nullable=False)
is_read = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Verknüpfungen zu anderen Entitäten
related_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
related_post_id = db.Column(db.Integer, db.ForeignKey('social_post.id'), nullable=True)
related_comment_id = db.Column(db.Integer, db.ForeignKey('social_comment.id'), nullable=True)
related_thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
# Beziehungen
related_user = db.relationship('User', foreign_keys=[related_user_id])
related_post = db.relationship('SocialPost', foreign_keys=[related_post_id])
related_comment = db.relationship('SocialComment', foreign_keys=[related_comment_id])
related_thought = db.relationship('Thought', foreign_keys=[related_thought_id])
def __repr__(self):
return f'<Notification {self.id} for {self.user.username}>'
def to_dict(self):
return {
'id': self.id,
'type': self.type,
'message': self.message,
'is_read': self.is_read,
'created_at': self.created_at.isoformat(),
'related_user': self.related_user.username if self.related_user else None,
'related_post_id': self.related_post_id,
'related_comment_id': self.related_comment_id,
'related_thought_id': self.related_thought_id
}
class UserSettings(db.Model):
"""Benutzereinstellungen"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=True)
# Datenschutz-Einstellungen
profile_visibility = db.Column(db.String(20), default='public') # public, friends, private
show_email = db.Column(db.Boolean, default=False)
show_birth_date = db.Column(db.Boolean, default=False)
show_location = db.Column(db.Boolean, default=True)
allow_friend_requests = db.Column(db.Boolean, default=True)
allow_messages = db.Column(db.String(20), default='everyone') # everyone, friends, none
# Benachrichtigungs-Einstellungen
email_notifications = db.Column(db.Boolean, default=True)
push_notifications = db.Column(db.Boolean, default=True)
notify_on_follow = db.Column(db.Boolean, default=True)
notify_on_like = db.Column(db.Boolean, default=True)
notify_on_comment = db.Column(db.Boolean, default=True)
notify_on_mention = db.Column(db.Boolean, default=True)
notify_on_friend_request = db.Column(db.Boolean, default=True)
# Interface-Einstellungen
dark_mode = db.Column(db.Boolean, default=False)
language = db.Column(db.String(10), default='de')
# Beziehung
user = db.relationship('User', backref=db.backref('settings', uselist=False))
def __repr__(self):
return f'<UserSettings for {self.user.username}>'
class Activity(db.Model):
"""Aktivitätsprotokoll für Benutzer"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
action = db.Column(db.String(100), nullable=False) # login, logout, post_created, thought_shared, etc.
description = db.Column(db.String(500), nullable=True)
ip_address = db.Column(db.String(45), nullable=True) # IPv4/IPv6
user_agent = db.Column(db.String(500), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Verknüpfungen zu anderen Entitäten
related_post_id = db.Column(db.Integer, db.ForeignKey('social_post.id'), nullable=True)
related_thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
related_mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), nullable=True)
# Beziehungen
user = db.relationship('User', backref='activities')
related_post = db.relationship('SocialPost')
related_thought = db.relationship('Thought')
related_mindmap = db.relationship('UserMindmap')
def __repr__(self):
return f'<Activity {self.action} by {self.user.username}>'

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

22
server.log Normal file
View File

@@ -0,0 +1,22 @@
⏰ 21:58:48.486 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet
⏰ 21:58:48.486 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
⏰ 21:58:49.951 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 OpenAI API-Verbindung erfolgreich hergestellt
⏰ 21:58:50.122 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbank erfolgreich initialisiert
⏰ 21:58:50.132 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbanktabellen erstellt/aktualisiert
⏰ 21:58:50.134 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 Starte Flask-Entwicklungsserver auf http://localhost:5000
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with watchdog (inotify)
⏰ 21:58:52.225 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet
⏰ 21:58:52.226 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
⏰ 21:58:53.848 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 OpenAI API-Verbindung erfolgreich hergestellt
⏰ 21:58:53.997 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbank erfolgreich initialisiert
⏰ 21:58:54.002 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbanktabellen erstellt/aktualisiert
⏰ 21:58:54.006 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 Starte Flask-Entwicklungsserver auf http://localhost:5000
* Debugger is active!
* Debugger PIN: 114-005-893

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

53
start.sh Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env powershell
# Windows PowerShell-Version des Start-Skripts
# Datum: 01.05.2025
# Docker-Status prüfen
Write-Host "Prüfe Docker-Status..." -ForegroundColor Cyan
try {
$status = docker ps -q
if ($LASTEXITCODE -ne 0) {
Write-Host "Docker ist nicht gestartet. Bitte starten Sie Docker Desktop." -ForegroundColor Red
exit 1
}
} catch {
Write-Host "Docker ist nicht verfügbar. Bitte installieren Sie Docker Desktop und starten Sie es." -ForegroundColor Red
Write-Host $_.Exception.Message
exit 1
}
# Alte Container stoppen und entfernen
$containerExists = docker ps -a --filter "name=systades_app" -q
if ($containerExists) {
Write-Host "Stoppe und entferne alten Container..." -ForegroundColor Yellow
docker rm -f systades_app
}
# Alte Images löschen
Write-Host "Entferne altes Image..." -ForegroundColor Yellow
docker rmi -f systades_app:latest
# Stelle sicher, dass das Datenbankverzeichnis existiert
if (-not (Test-Path "database")) {
New-Item -Path "database" -ItemType Directory -Force
}
# Docker-Compose Setup neu bauen
Write-Host "Baue Container neu..." -ForegroundColor Green
docker-compose build --no-cache
# Docker-Compose neu starten
Write-Host "Starte Container..." -ForegroundColor Green
docker-compose up -d --force-recreate
# Warte kurz und prüfe, ob der Container läuft
Write-Host "Prüfe Container-Status..." -ForegroundColor Cyan
Start-Sleep -Seconds 3
docker ps | Select-String "systades_app"
# Ausgabe
Write-Host "`nSystemstatus:" -ForegroundColor Cyan
Write-Host "----------------------------------------"
Write-Host "Systades-Anwendung ist jetzt unter http://localhost:5000 erreichbar." -ForegroundColor Green
Write-Host "Container-Logs können mit 'docker logs -f systades_app' angezeigt werden." -ForegroundColor Green
Write-Host "----------------------------------------"

View File

@@ -0,0 +1 @@

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

252
static/css/assistant.css Normal file
View File

@@ -0,0 +1,252 @@
/* ChatGPT Assistent Styles - Verbesserte Version */
#chatgpt-assistant {
font-family: 'Inter', sans-serif;
bottom: 5.5rem;
z-index: 100;
max-height: 85vh;
}
#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);
max-height: 80vh !important;
}
#assistant-history {
max-height: calc(80vh - 150px);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
padding-bottom: 2rem; /* Zusätzlicher Abstand unten */
}
#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::-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;
border-radius: 50%;
display: inline-block;
margin: 0 2px;
opacity: 0.6;
animation: bounce 1.4s infinite ease-in-out;
}
body.dark .typing-indicator span {
background-color: rgba(255, 255, 255, 0.7);
}
body:not(.dark) .typing-indicator span {
background-color: rgba(107, 114, 128, 0.8);
}
.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;
max-height: 65vh !important;
}
#chatgpt-assistant {
right: 1rem;
bottom: 6rem;
}
}
/* 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;
}
/* Verbesserte Farbkontraste für Nachrichtenblasen */
.user-message {
background-color: rgba(124, 58, 237, 0.1) !important;
color: #4B5563 !important;
}
body.dark .user-message {
background-color: rgba(124, 58, 237, 0.2) !important;
color: #F9FAFB !important;
}
.assistant-message {
background-color: #F3F4F6 !important;
color: #1F2937 !important;
border-left: 3px solid #8B5CF6;
}
body.dark .assistant-message {
background-color: rgba(31, 41, 55, 0.5) !important;
color: #F9FAFB !important;
border-left: 3px solid #8B5CF6;
}
/* Chat-Assistent-Position im Footer-Bereich anpassen */
.chat-assistant {
max-height: 75vh;
bottom: 1.5rem;
}
.chat-assistant .chat-messages {
max-height: calc(75vh - 180px);
overflow-y: auto;
}

1068
static/css/base-styles.css Normal file

File diff suppressed because it is too large Load Diff

437
static/css/mindmap.css Normal file
View File

@@ -0,0 +1,437 @@
/* Mindmap Container Styles */
.mindmap-container {
position: relative;
width: 100%;
height: 100%;
min-height: 600px;
background: var(--bg-primary);
border-radius: 12px;
overflow: hidden;
}
/* Cytoscape Container für die Hauptmindmap */
#cy {
width: 100%;
height: 100%;
background: transparent;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
/* Subpage Styles - Identisches Design wie Hauptmindmap */
.mindmap-subpage {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-primary);
display: flex;
flex-direction: column;
z-index: 10;
transition: opacity 0.3s ease, transform 0.3s ease;
opacity: 1;
transform: translateY(0);
}
/* Subpage Header */
.subpage-header {
display: flex;
align-items: center;
padding: 16px;
background: var(--bg-secondary);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
z-index: 2;
}
.dark .subpage-header {
background: rgba(30, 41, 59, 0.8);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
/* Zurück-Button */
.back-button {
background: rgba(255, 255, 255, 0.1);
border: none;
color: white;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
margin-right: 12px;
}
.back-button:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
/* Subpage Titel */
.subpage-title {
font-size: 1.5rem;
font-weight: 600;
color: white;
margin: 0;
background: linear-gradient(90deg, #60a5fa, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Subpage Cytoscape Container */
.subpage-cy-container {
position: relative;
flex: 1;
width: 100%;
height: calc(100% - 72px);
overflow: hidden;
z-index: 1;
}
/* Toolbar für Zoom-Kontrollen */
.mindmap-toolbar {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
padding: 8px;
background: rgba(30, 41, 59, 0.8);
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
z-index: 20;
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.mindmap-toolbar button {
width: 40px;
height: 40px;
border: none;
background: rgba(255, 255, 255, 0.1);
color: white;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.mindmap-toolbar button:hover {
background: rgba(139, 92, 246, 0.5);
transform: translateY(-2px);
}
.mindmap-toolbar button i {
font-size: 16px;
}
/* Mindmap Header */
.mindmap-header {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 1.5rem;
background: rgba(15, 23, 42, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
}
/* Dark Mode spezifische Stile */
.dark .mindmap-subpage {
background: linear-gradient(135deg, #0f172a 0%, #0c1221 100%);
}
/* Fix für Zoom-Buttons */
body.dark .mindmap-toolbar button {
background: rgba(255, 255, 255, 0.1);
color: white;
}
body:not(.dark) .mindmap-toolbar button {
background: rgba(30, 41, 59, 0.2);
color: #1e293b;
}
/* Kontext-Menü-Anpassungen */
.context-menu {
z-index: 1000;
}
/* Export Group Styles */
.export-group {
position: relative;
}
.export-options {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
padding: 8px;
background: var(--bg-secondary);
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
display: none;
flex-direction: column;
gap: 4px;
min-width: 160px;
}
.dark .export-options {
background: rgba(30, 41, 59, 0.9);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.export-group:hover .export-options {
display: flex;
}
.export-options button {
width: 100%;
height: auto;
padding: 8px 12px;
justify-content: flex-start;
font-size: 14px;
border-radius: 6px;
}
/* Context Menu Styles */
.mindmap-context-menu {
position: fixed;
background: var(--bg-secondary);
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 8px;
z-index: 1000;
min-width: 180px;
}
.dark .mindmap-context-menu {
background: rgba(30, 41, 59, 0.9);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.mindmap-context-menu button {
display: flex;
align-items: center;
width: 100%;
padding: 8px 12px;
border: none;
background: transparent;
color: var(--text-primary);
cursor: pointer;
font-size: 14px;
border-radius: 6px;
transition: all 0.2s ease;
}
.mindmap-context-menu button:hover {
background: var(--accent-primary);
color: white;
}
.mindmap-context-menu button i {
margin-right: 8px;
width: 16px;
}
/* Node Styles */
.mindmap-node {
background-color: var(--bg-secondary);
border: 2px solid var(--accent-primary);
border-radius: 8px;
padding: 8px 12px;
transition: all 0.3s ease;
}
.mindmap-node:hover {
box-shadow: 0 0 0 2px var(--accent-primary);
transform: scale(1.05);
}
.mindmap-node.selected {
border-color: var(--accent-secondary);
box-shadow: 0 0 0 3px var(--accent-secondary);
}
/* Edge Styles */
.mindmap-edge {
width: 2px;
transition: all 0.3s ease;
}
.dark .mindmap-edge {
background-color: rgba(255, 255, 255, 0.2);
}
.mindmap-edge:hover {
width: 3px;
background-color: var(--accent-primary);
}
/* Animation Styles */
@keyframes nodeAppear {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
.mindmap-node-new {
animation: nodeAppear 0.3s ease forwards;
}
/* Responsive Styles */
@media (max-width: 768px) {
.mindmap-toolbar {
flex-wrap: wrap;
width: calc(100% - 32px);
justify-content: center;
}
.export-options {
left: 0;
right: auto;
}
}
/* Loading State */
.mindmap-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.mindmap-loading-spinner {
width: 40px;
height: 40px;
border: 4px solid var(--bg-secondary);
border-top-color: var(--accent-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Tooltip Styles */
.mindmap-tooltip {
position: absolute;
background: var(--bg-secondary);
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
pointer-events: none;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.dark .mindmap-tooltip {
background: rgba(30, 41, 59, 0.9);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Kategorien-Panel */
.categories-panel {
position: absolute;
top: 80px;
left: 20px;
width: 300px;
max-height: calc(100vh - 120px);
background: rgba(15, 23, 42, 0.95);
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 1000;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
transform: translateX(-320px);
transition: transform 0.3s ease-in-out;
overflow-y: auto;
}
.categories-panel.visible {
transform: translateX(0);
}
.categories-panel h3 {
color: white;
font-size: 1.2rem;
margin: 0 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.category-list {
list-style: none;
padding: 0;
margin: 0;
}
.category-item {
display: flex;
align-items: center;
padding: 8px 12px;
margin: 4px 0;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
color: white;
}
.category-item:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateX(4px);
}
.category-color {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 12px;
flex-shrink: 0;
}
.category-name {
flex-grow: 1;
font-size: 0.95rem;
}
.category-count {
background: rgba(255, 255, 255, 0.1);
padding: 2px 8px;
border-radius: 12px;
font-size: 0.8rem;
margin-left: 8px;
}

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

915
static/css/social.css Normal file
View File

@@ -0,0 +1,915 @@
/* ================================
SysTades Social Network Styles
================================ */
:root {
/* Primary Colors */
--primary-50: #f0f9ff;
--primary-100: #e0f2fe;
--primary-200: #bae6fd;
--primary-300: #7dd3fc;
--primary-400: #38bdf8;
--primary-500: #0ea5e9;
--primary-600: #0284c7;
--primary-700: #0369a1;
--primary-800: #075985;
--primary-900: #0c4a6e;
/* Neutral Colors */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
/* Semantic Colors */
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--info: #3b82f6;
/* Social Media Colors */
--like-color: #ec4899;
--share-color: #8b5cf6;
--bookmark-color: #f59e0b;
--comment-color: var(--primary-500);
/* Glassmorphism */
--glass-bg: rgba(255, 255, 255, 0.1);
--glass-border: rgba(255, 255, 255, 0.2);
--glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
/* Animations */
--transition-fast: 0.15s ease-out;
--transition-normal: 0.3s ease-out;
--transition-slow: 0.6s ease-out;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Border Radius */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
}
/* Dark Mode Variables */
[data-theme="dark"] {
--glass-bg: rgba(0, 0, 0, 0.1);
--glass-border: rgba(255, 255, 255, 0.1);
--glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
/* ================================
Performance Optimizations
================================ */
* {
box-sizing: border-box;
}
img {
max-width: 100%;
height: auto;
}
/* GPU Acceleration for animations */
.accelerated {
transform: translateZ(0);
will-change: transform;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* ================================
Social Feed Styles
================================ */
.social-feed {
max-width: 600px;
margin: 0 auto;
padding: 1rem;
}
.post-card {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-xl);
margin-bottom: 1.5rem;
padding: 1.5rem;
box-shadow: var(--shadow-lg);
transition: all var(--transition-normal);
position: relative;
overflow: hidden;
}
.post-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-xl);
border-color: var(--primary-300);
}
.post-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary-500), var(--primary-600));
opacity: 0;
transition: opacity var(--transition-normal);
}
.post-card:hover::before {
opacity: 1;
}
/* Post Header */
.post-header {
display: flex;
align-items: center;
margin-bottom: 1rem;
gap: 0.75rem;
}
.post-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
border: 2px solid var(--primary-500);
transition: all var(--transition-fast);
cursor: pointer;
}
.post-avatar:hover {
transform: scale(1.1);
border-color: var(--primary-400);
}
.post-author {
flex: 1;
}
.post-author-name {
font-weight: 600;
color: var(--gray-800);
margin: 0;
font-size: 1rem;
}
.post-author-username {
color: var(--gray-500);
font-size: 0.875rem;
margin: 0;
}
.post-time {
color: var(--gray-400);
font-size: 0.875rem;
}
/* Post Content */
.post-content {
margin-bottom: 1rem;
line-height: 1.6;
color: var(--gray-700);
}
.post-type-badge {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: var(--radius-md);
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.post-type-text { background: var(--gray-100); color: var(--gray-600); }
.post-type-thought { background: var(--primary-100); color: var(--primary-600); }
.post-type-question { background: var(--warning); color: white; }
.post-type-insight { background: var(--success); color: white; }
/* Post Actions */
.post-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid var(--gray-200);
}
.action-group {
display: flex;
gap: 1rem;
}
.action-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border: none;
background: transparent;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 0.875rem;
color: var(--gray-500);
}
.action-btn:hover {
background: var(--gray-100);
color: var(--gray-700);
transform: translateY(-1px);
}
.action-btn.active {
color: var(--primary-600);
background: var(--primary-50);
}
.action-btn i {
font-size: 1rem;
}
/* Specific action colors */
.action-btn.like-btn.active {
color: var(--like-color);
background: rgba(236, 72, 153, 0.1);
}
.action-btn.share-btn:hover {
color: var(--share-color);
background: rgba(139, 92, 246, 0.1);
}
.action-btn.bookmark-btn.active {
color: var(--bookmark-color);
background: rgba(245, 158, 11, 0.1);
}
/* ================================
Comments Section
================================ */
.comments-section {
border-top: 1px solid var(--gray-200);
padding-top: 1rem;
margin-top: 1rem;
}
.comment-item {
display: flex;
gap: 0.75rem;
margin-bottom: 1rem;
padding: 0.75rem;
border-radius: var(--radius-lg);
background: var(--gray-50);
transition: background var(--transition-fast);
}
.comment-item:hover {
background: var(--gray-100);
}
.comment-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
border: 2px solid var(--primary-400);
}
.comment-content {
flex: 1;
}
.comment-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.comment-author {
font-weight: 600;
color: var(--gray-800);
font-size: 0.875rem;
}
.comment-time {
color: var(--gray-400);
font-size: 0.75rem;
}
.comment-text {
color: var(--gray-700);
font-size: 0.875rem;
line-height: 1.5;
margin-bottom: 0.5rem;
}
.comment-actions {
display: flex;
gap: 1rem;
}
.comment-action {
background: none;
border: none;
color: var(--gray-400);
font-size: 0.75rem;
cursor: pointer;
transition: color var(--transition-fast);
}
.comment-action:hover {
color: var(--primary-500);
}
/* Comment Form */
.comment-form {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
}
.comment-form textarea {
flex: 1;
padding: 0.75rem;
border: 1px solid var(--gray-300);
border-radius: var(--radius-lg);
resize: none;
min-height: 80px;
transition: border-color var(--transition-fast);
}
.comment-form textarea:focus {
outline: none;
border-color: var(--primary-500);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
.comment-submit {
padding: 0.75rem 1.5rem;
background: var(--primary-500);
color: white;
border: none;
border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
}
.comment-submit:hover {
background: var(--primary-600);
transform: translateY(-1px);
}
/* ================================
Create Post Form
================================ */
.create-post-form {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-xl);
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: var(--shadow-md);
}
.create-post-textarea {
width: 100%;
min-height: 120px;
padding: 1rem;
border: 1px solid var(--gray-300);
border-radius: var(--radius-lg);
resize: vertical;
font-family: inherit;
font-size: 1rem;
line-height: 1.5;
transition: all var(--transition-normal);
}
.create-post-textarea:focus {
outline: none;
border-color: var(--primary-500);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
.create-post-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
gap: 1rem;
}
.post-type-select,
.post-visibility-select {
padding: 0.5rem 1rem;
border: 1px solid var(--gray-300);
border-radius: var(--radius-md);
background: white;
cursor: pointer;
transition: border-color var(--transition-fast);
}
.post-type-select:focus,
.post-visibility-select:focus {
outline: none;
border-color: var(--primary-500);
}
.create-post-btn {
padding: 0.75rem 2rem;
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
border: none;
border-radius: var(--radius-lg);
cursor: pointer;
font-weight: 600;
transition: all var(--transition-normal);
box-shadow: var(--shadow-md);
}
.create-post-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.create-post-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* ================================
Filter Tabs
================================ */
.feed-filters {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
padding: 0.5rem;
background: var(--gray-100);
border-radius: var(--radius-xl);
overflow-x: auto;
}
.filter-tab {
padding: 0.75rem 1.5rem;
border: none;
background: transparent;
border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
white-space: nowrap;
color: var(--gray-600);
}
.filter-tab:hover {
background: var(--gray-200);
color: var(--gray-800);
}
.filter-tab.active {
background: var(--primary-500);
color: white;
box-shadow: var(--shadow-md);
}
/* ================================
Notifications
================================ */
.notification-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border-radius: var(--radius-lg);
transition: all var(--transition-fast);
cursor: pointer;
position: relative;
}
.notification-item:hover {
background: var(--gray-50);
transform: translateX(4px);
}
.notification-item.unread {
background: var(--primary-50);
border-left: 4px solid var(--primary-500);
}
.notification-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
color: white;
}
.notification-like { background: var(--like-color); }
.notification-comment { background: var(--comment-color); }
.notification-follow { background: var(--success); }
.notification-share { background: var(--share-color); }
.notification-content {
flex: 1;
}
.notification-text {
margin: 0 0 0.25rem 0;
color: var(--gray-800);
font-size: 0.875rem;
}
.notification-time {
color: var(--gray-500);
font-size: 0.75rem;
}
.notification-actions {
display: flex;
gap: 0.5rem;
}
.notification-delete {
background: none;
border: none;
color: var(--gray-400);
cursor: pointer;
padding: 0.5rem;
border-radius: var(--radius-md);
transition: all var(--transition-fast);
}
.notification-delete:hover {
background: var(--error);
color: white;
}
/* ================================
User Profile
================================ */
.profile-header {
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
padding: 2rem;
border-radius: var(--radius-2xl);
margin-bottom: 2rem;
position: relative;
overflow: hidden;
}
.profile-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
}
.profile-info {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 1.5rem;
}
.profile-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
border: 4px solid rgba(255, 255, 255, 0.3);
box-shadow: var(--shadow-xl);
}
.profile-details h1 {
margin: 0 0 0.5rem 0;
font-size: 2rem;
font-weight: 700;
}
.profile-username {
opacity: 0.9;
font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.profile-bio {
opacity: 0.8;
line-height: 1.5;
max-width: 500px;
}
.profile-stats {
display: flex;
gap: 2rem;
margin-top: 1.5rem;
}
.stat-item {
text-align: center;
}
.stat-number {
display: block;
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
opacity: 0.8;
}
.follow-btn {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 0.75rem 2rem;
border-radius: var(--radius-lg);
font-weight: 600;
cursor: pointer;
transition: all var(--transition-normal);
backdrop-filter: blur(10px);
}
.follow-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.follow-btn.following {
background: rgba(255, 255, 255, 0.9);
color: var(--primary-600);
}
/* Profile Tabs */
.profile-tabs {
display: flex;
border-bottom: 1px solid var(--gray-200);
margin-bottom: 2rem;
overflow-x: auto;
}
.profile-tab {
padding: 1rem 2rem;
border: none;
background: none;
cursor: pointer;
position: relative;
color: var(--gray-600);
font-weight: 500;
transition: all var(--transition-fast);
white-space: nowrap;
}
.profile-tab:hover {
color: var(--primary-600);
}
.profile-tab.active {
color: var(--primary-600);
}
.profile-tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
background: var(--primary-500);
border-radius: 2px 2px 0 0;
}
/* ================================
Responsive Design
================================ */
@media (max-width: 768px) {
.social-feed {
padding: 0.5rem;
}
.post-card {
padding: 1rem;
margin-bottom: 1rem;
}
.post-actions {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.action-group {
justify-content: space-around;
}
.create-post-options {
flex-direction: column;
align-items: stretch;
gap: 0.75rem;
}
.feed-filters {
padding: 0.25rem;
gap: 0.25rem;
}
.filter-tab {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.profile-header {
padding: 1.5rem;
}
.profile-info {
flex-direction: column;
text-align: center;
gap: 1rem;
}
.profile-avatar {
width: 80px;
height: 80px;
}
.profile-details h1 {
font-size: 1.5rem;
}
.profile-stats {
justify-content: center;
gap: 1.5rem;
}
.profile-tabs {
gap: 0;
}
.profile-tab {
flex: 1;
padding: 0.75rem 1rem;
text-align: center;
font-size: 0.875rem;
}
}
/* ================================
Loading & Animations
================================ */
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--gray-300);
border-radius: 50%;
border-top-color: var(--primary-500);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.slide-up {
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ================================
Toast Notifications
================================ */
.toast-container {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
pointer-events: none;
}
.toast {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-lg);
padding: 1rem 1.5rem;
margin-bottom: 0.5rem;
box-shadow: var(--shadow-lg);
pointer-events: all;
max-width: 400px;
animation: slideInRight 0.3s ease-out;
}
.toast.success {
border-left: 4px solid var(--success);
}
.toast.error {
border-left: 4px solid var(--error);
}
.toast.warning {
border-left: 4px solid var(--warning);
}
.toast.info {
border-left: 4px solid var(--info);
}
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* ================================
Utilities
================================ */
.text-gradient {
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.glass-effect {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
}
.shadow-glow {
box-shadow: 0 0 20px rgba(14, 165, 233, 0.3);
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}

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

@@ -0,0 +1,11 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="98" fill="url(#gradient)" stroke="#7C3AED" stroke-width="4"/>
<circle cx="100" cy="80" r="36" fill="white"/>
<path d="M100 140C77.9086 140 60 157.909 60 180H140C140 157.909 122.091 140 100 140Z" fill="white"/>
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="200" y2="200" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#8B5CF6"/>
<stop offset="1" stop-color="#3B82F6"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 583 B

54
static/img/favicon-gen.py Normal file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""
Generate favicon.ico from SVG using cairosvg and PIL
"""
import os
import io
from cairosvg import svg2png
from PIL import Image
# Verzeichnis dieses Skripts
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
def svg_to_ico(svg_path, ico_path, sizes=[16, 32, 48, 64, 128, 256]):
"""Convert SVG to multi-size ICO file"""
img_io = io.BytesIO()
# Höchste Auflösung für Zwischenspeicherung
max_size = max(sizes)
# SVG in PNG konvertieren
with open(svg_path, 'rb') as svg_file:
svg_data = svg_file.read()
svg2png(bytestring=svg_data, write_to=img_io, output_width=max_size, output_height=max_size)
# PNG in verschiedene Größen konvertieren
img = Image.open(img_io)
# Alle Größen für das ICO-Format vorbereiten
img_list = []
for size in sizes:
resized_img = img.resize((size, size), Image.LANCZOS)
img_list.append(resized_img)
# ICO-Datei speichern
img_list[0].save(
ico_path,
format='ICO',
sizes=[(img.width, img.height) for img in img_list],
append_images=img_list[1:]
)
print(f"Favicon {ico_path} wurde erstellt!")
# Ursprüngliches Favicon konvertieren
svg_to_ico(
os.path.join(CURRENT_DIR, 'favicon.svg'),
os.path.join(CURRENT_DIR, 'favicon.ico')
)
# Neues Neuron-Favicon konvertieren
svg_to_ico(
os.path.join(CURRENT_DIR, 'neuron-favicon.svg'),
os.path.join(CURRENT_DIR, 'neuron-favicon.ico')
)

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,29 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Hintergrund -->
<rect width="32" height="32" rx="8" fill="#6d28d9" />
<!-- Mindmap-Punkte -->
<!-- Zentraler Punkt -->
<circle cx="16" cy="16" r="3.5" fill="#a78bfa" />
<!-- Umgebende Punkte -->
<circle cx="8" cy="10" r="2.5" fill="#8b5cf6" />
<circle cx="24" cy="10" r="2.5" fill="#8b5cf6" />
<circle cx="16" cy="26" r="2.5" fill="#8b5cf6" />
<!-- Verbindende Linien -->
<path d="M16 16 L8 10" stroke="white" stroke-width="1" stroke-linecap="round" />
<path d="M16 16 L24 10" stroke="white" stroke-width="1" stroke-linecap="round" />
<path d="M16 16 L16 26" stroke="white" stroke-width="1" stroke-linecap="round" />
<!-- Weitere Verbindungslinien für mehr Komplexität -->
<path d="M8 10 L16 26" stroke="#c4b5fd" stroke-width="0.8" stroke-linecap="round" stroke-dasharray="2 1" />
<path d="M24 10 L16 26" stroke="#c4b5fd" stroke-width="0.8" stroke-linecap="round" stroke-dasharray="2 1" />
<path d="M8 10 L24 10" stroke="#c4b5fd" stroke-width="0.8" stroke-linecap="round" stroke-dasharray="2 1" />
<!-- Kleine Dekoration-Punkte für Hintergrund-Ähnlichkeit -->
<circle cx="5" cy="20" r="0.8" fill="#ddd6fe" opacity="0.7" />
<circle cx="27" cy="20" r="0.8" fill="#ddd6fe" opacity="0.7" />
<circle cx="20" cy="5" r="0.8" fill="#ddd6fe" opacity="0.7" />
<circle cx="12" cy="5" r="0.8" fill="#ddd6fe" opacity="0.7" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,59 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Hintergrund mit Farbverlauf -->
<rect width="64" height="64" rx="16" fill="url(#paint0_linear)" />
<!-- Mindmap-Punkte -->
<!-- Zentraler Punkt -->
<circle cx="32" cy="32" r="8" fill="url(#glow_gradient)" filter="url(#glow)" />
<!-- Umgebende Punkte -->
<circle cx="16" cy="20" r="6" fill="#8b5cf6" />
<circle cx="48" cy="20" r="6" fill="#8b5cf6" />
<circle cx="32" cy="52" r="6" fill="#8b5cf6" />
<circle cx="16" cy="48" r="4" fill="#a78bfa" />
<circle cx="48" cy="48" r="4" fill="#a78bfa" />
<!-- Verbindende Linien (Hauptpfade) -->
<path d="M32 32 L16 20" stroke="white" stroke-width="2" stroke-linecap="round" />
<path d="M32 32 L48 20" stroke="white" stroke-width="2" stroke-linecap="round" />
<path d="M32 32 L32 52" stroke="white" stroke-width="2" stroke-linecap="round" />
<path d="M32 32 L16 48" stroke="white" stroke-width="2" stroke-linecap="round" />
<path d="M32 32 L48 48" stroke="white" stroke-width="2" stroke-linecap="round" />
<!-- Zusätzliche Verbindungslinien -->
<path d="M16 20 L16 48" stroke="#c4b5fd" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 2" />
<path d="M48 20 L48 48" stroke="#c4b5fd" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 2" />
<path d="M16 20 L48 20" stroke="#c4b5fd" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 2" />
<path d="M16 48 L32 52" stroke="#c4b5fd" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 2" />
<path d="M48 48 L32 52" stroke="#c4b5fd" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="3 2" />
<!-- Kleine Dekoration-Punkte für Hintergrund-Ähnlichkeit -->
<circle cx="10" cy="36" r="1.5" fill="#ddd6fe" opacity="0.7" />
<circle cx="54" cy="36" r="1.5" fill="#ddd6fe" opacity="0.7" />
<circle cx="40" cy="10" r="1.5" fill="#ddd6fe" opacity="0.7" />
<circle cx="24" cy="10" r="1.5" fill="#ddd6fe" opacity="0.7" />
<circle cx="20" cy="36" r="1.2" fill="#ddd6fe" opacity="0.5" />
<circle cx="44" cy="36" r="1.2" fill="#ddd6fe" opacity="0.5" />
<circle cx="32" cy="16" r="1.2" fill="#ddd6fe" opacity="0.5" />
<!-- Definitionen für Farbverläufe und Effekte -->
<defs>
<!-- Haupthintergrund-Farbverlauf -->
<linearGradient id="paint0_linear" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
<stop stop-color="#6d28d9" />
<stop offset="1" stop-color="#4c1d95" />
</linearGradient>
<!-- Glüheffekt für den zentralen Punkt -->
<filter id="glow" x="20" y="20" width="24" height="24" filterUnits="userSpaceOnUse">
<feGaussianBlur stdDeviation="2" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- Farbverlauf für den zentralen Punkt -->
<linearGradient id="glow_gradient" x1="24" y1="24" x2="40" y2="40" gradientUnits="userSpaceOnUse">
<stop stop-color="#a78bfa" />
<stop offset="1" stop-color="#8b5cf6" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.0 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,8 +23,19 @@ 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
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]) {
this.pageInitializers[this.currentPage]();
@@ -68,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,
@@ -218,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);
});

214
static/js/mindmap-init.js Normal file
View File

@@ -0,0 +1,214 @@
/**
* Mindmap Initialisierung und Event-Handling
*/
// Warte auf die Cytoscape-Instanz
document.addEventListener('mindmap-loaded', function() {
const cy = window.cy;
if (!cy) return;
// Event-Listener für Knoten-Klicks
cy.on('tap', 'node', function(evt) {
const node = evt.target;
// Alle vorherigen Hervorhebungen zurücksetzen
cy.nodes().forEach(n => {
n.removeStyle();
n.connectedEdges().removeStyle();
});
// Speichere ausgewählten Knoten
window.mindmapInstance.selectedNode = node;
// Aktiviere leuchtenden Effekt statt Umkreisung
node.style({
'background-opacity': 1,
'background-color': node.data('color'),
'shadow-color': node.data('color'),
'shadow-opacity': 1,
'shadow-blur': 15,
'shadow-offset-x': 0,
'shadow-offset-y': 0
});
// Verbundene Kanten und Knoten hervorheben
const connectedEdges = node.connectedEdges();
const connectedNodes = node.neighborhood('node');
connectedEdges.style({
'line-color': '#a78bfa',
'target-arrow-color': '#a78bfa',
'source-arrow-color': '#a78bfa',
'line-opacity': 0.8,
'width': 2
});
connectedNodes.style({
'shadow-opacity': 0.7,
'shadow-blur': 10,
'shadow-color': '#a78bfa'
});
// Info-Panel aktualisieren
updateInfoPanel(node);
// Seitenleiste aktualisieren
updateSidebar(node);
});
// Klick auf Hintergrund - Auswahl zurücksetzen
cy.on('tap', function(evt) {
if (evt.target === cy) {
resetSelection(cy);
}
});
// Zoom-Controls
document.getElementById('zoomIn')?.addEventListener('click', () => {
cy.zoom({
level: cy.zoom() * 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
});
document.getElementById('zoomOut')?.addEventListener('click', () => {
cy.zoom({
level: cy.zoom() / 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
});
document.getElementById('resetView')?.addEventListener('click', () => {
cy.fit();
resetSelection(cy);
});
// Legend-Toggle
document.getElementById('toggleLegend')?.addEventListener('click', () => {
const legend = document.getElementById('categoryLegend');
if (legend) {
isLegendVisible = !isLegendVisible;
legend.style.display = isLegendVisible ? 'block' : 'none';
}
});
// Keyboard-Controls
document.addEventListener('keydown', (e) => {
if (e.key === '+' || e.key === '=') {
cy.zoom({
level: cy.zoom() * 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
} else if (e.key === '-' || e.key === '_') {
cy.zoom({
level: cy.zoom() / 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
} else if (e.key === 'Escape') {
resetSelection(cy);
}
});
});
/**
* Aktualisiert das Info-Panel mit Knoteninformationen
* @param {Object} node - Der ausgewählte Knoten
*/
function updateInfoPanel(node) {
const infoPanel = document.getElementById('infoPanel');
if (!infoPanel) return;
const data = node.data();
const connectedNodes = node.neighborhood('node');
let html = `
<h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li>
`;
});
html += `
</ul>
</div>
`;
infoPanel.innerHTML = html;
infoPanel.style.display = 'block';
}
/**
* Aktualisiert die Seitenleiste mit Knoteninformationen
* @param {Object} node - Der ausgewählte Knoten
*/
function updateSidebar(node) {
const sidebar = document.getElementById('sidebar');
if (!sidebar) return;
const data = node.data();
const connectedNodes = node.neighborhood('node');
let html = `
<div class="node-details">
<h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li>
`;
});
html += `
</ul>
</div>
</div>
`;
sidebar.innerHTML = html;
}
/**
* Setzt die Auswahl zurück
* @param {Object} cy - Cytoscape-Instanz
*/
function resetSelection(cy) {
window.mindmapInstance.selectedNode = null;
// Alle Hervorhebungen zurücksetzen
cy.nodes().forEach(node => {
node.removeStyle();
node.connectedEdges().removeStyle();
});
// Info-Panel ausblenden
const infoPanel = document.getElementById('infoPanel');
if (infoPanel) {
infoPanel.style.display = 'none';
}
// Seitenleiste leeren
const sidebar = document.getElementById('sidebar');
if (sidebar) {
sidebar.innerHTML = '';
}
}

View File

@@ -0,0 +1,546 @@
/**
* 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'
? 'user-message rounded-lg py-2 px-3 max-w-[85%]'
: 'assistant-message rounded-lg py-2 px-3 max-w-[85%]';
// Nachrichtentext einfügen, falls Markdown-Parser verfügbar, nutzen
if (this.markdownParser) {
bubble.innerHTML = this.markdownParser.parse(text);
} else {
bubble.textContent = text;
}
// Links in der Nachricht klickbar machen
const links = bubble.querySelectorAll('a');
links.forEach(link => {
link.target = '_blank';
link.rel = 'noopener noreferrer';
link.className = 'text-primary-600 dark:text-primary-400 underline';
});
// Code-Blöcke stylen
const codeBlocks = bubble.querySelectorAll('pre');
codeBlocks.forEach(block => {
block.className = 'bg-gray-100 dark:bg-dark-900 p-2 rounded my-2 overflow-x-auto';
});
const inlineCode = bubble.querySelectorAll('code:not(pre code)');
inlineCode.forEach(code => {
code.className = 'bg-gray-100 dark:bg-dark-900 px-1 rounded font-mono text-sm';
});
messageEl.appendChild(bubble);
this.chatHistory.appendChild(messageEl);
// Scrolle zum Ende des Chat-Verlaufs
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
}
/**
* Zeigt Vorschläge für mögliche Fragen an
* @param {Array} suggestions - Array von Vorschlägen
*/
showSuggestions(suggestions) {
if (!this.suggestionArea || !suggestions || !suggestions.length) return;
// Vorherige Vorschläge entfernen
this.suggestionArea.innerHTML = '';
// Neue Vorschläge hinzufügen
suggestions.forEach((text, index) => {
const pill = document.createElement('button');
pill.className = 'suggestion-pill text-sm px-3 py-1.5 rounded-full bg-primary-100 dark:bg-primary-900 text-primary-800 dark:text-primary-200 hover:bg-primary-200 dark:hover:bg-primary-800 transition-all duration-200';
pill.style.animationDelay = `${index * 0.1}s`;
pill.textContent = text;
this.suggestionArea.appendChild(pill);
});
// Vorschlagsbereich anzeigen
this.suggestionArea.classList.remove('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
timeout: 60000 // 60 Sekunden Timeout
});
// Ladeindikator entfernen
this.removeLoadingIndicator();
if (!response.ok) {
const errorText = await response.text();
let errorMessage;
try {
// Versuche, die Fehlermeldung zu parsen
const errorData = JSON.parse(errorText);
errorMessage = errorData.error || `Serverfehler: ${response.status} ${response.statusText}`;
} catch {
// Bei Parsing-Fehler verwende Standardfehlermeldung
errorMessage = `Serverfehler: ${response.status} ${response.statusText}`;
}
throw new Error(errorMessage);
}
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();
// Spezielle Fehlermeldungen für bestimmte Fehlertypen
const errorMessage = error.message || '';
let userFriendlyMessage = 'Es gab ein Problem mit der Anfrage.';
if (errorMessage.includes('timeout') || errorMessage.includes('Zeitüberschreitung')) {
userFriendlyMessage = 'Die Antwort hat zu lange gedauert. Der Server ist möglicherweise überlastet.';
} else if (errorMessage.includes('500') || errorMessage.includes('Internal Server Error')) {
userFriendlyMessage = 'Ein Serverfehler ist aufgetreten. Wir arbeiten an einer Lösung.';
} else if (errorMessage.includes('429') || errorMessage.includes('rate limit')) {
userFriendlyMessage = 'Die API-Anfragelimits wurden erreicht. Bitte warte einen Moment.';
}
// Fehlermeldung anzeigen oder Wiederholungsversuch starten
if (this.retryCount < this.maxRetries) {
this.retryCount++;
this.addMessage('assistant', `${userFriendlyMessage} Ich versuche es erneut... (Versuch ${this.retryCount}/${this.maxRetries})`);
// Letzte Benutzernachricht speichern für den Wiederholungsversuch
const lastUserMessageIndex = this.messages.findLastIndex(msg => msg.role === 'user');
if (lastUserMessageIndex >= 0) {
const lastUserMessage = this.messages[lastUserMessageIndex].content;
// Kurze Verzögerung vor dem erneuten Versuch mit exponentieller Backoff-Strategie
const retryDelay = 1500 * Math.pow(2, this.retryCount - 1); // 1.5s, 3s, 6s, ...
setTimeout(() => {
// Entferne Fehlermeldung aus dem Messages-Array, behalte aber die Benutzernachricht
this.messages = this.messages.filter(msg =>
!(msg.role === 'assistant' && msg.content.includes('versuche es erneut'))
);
// Erneuter Versand mit gleicher Nachricht
this.inputField.value = lastUserMessage;
this.sendMessage();
}, retryDelay);
}
} 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 oder kontaktiere den Support, falls das Problem weiterhin besteht.');
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 eine Ladeanimation an
*/
showLoadingIndicator() {
if (!this.chatHistory) return;
// Prüfen, ob bereits ein Ladeindikator angezeigt wird
if (document.getElementById('assistant-loading-indicator')) return;
const loadingEl = document.createElement('div');
loadingEl.className = 'flex justify-start';
loadingEl.id = 'assistant-loading-indicator';
const bubble = document.createElement('div');
bubble.className = 'assistant-message rounded-lg py-3 px-4 max-w-[85%] flex items-center';
const typingIndicator = document.createElement('div');
typingIndicator.className = 'typing-indicator';
typingIndicator.innerHTML = `
<span></span>
<span></span>
<span></span>
`;
bubble.appendChild(typingIndicator);
loadingEl.appendChild(bubble);
this.chatHistory.appendChild(loadingEl);
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
}
/**
* Entfernt den Ladeindikator aus dem Chat
*/
removeLoadingIndicator() {
const loadingIndicator = document.getElementById('assistant-loading-indicator');
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

@@ -0,0 +1,551 @@
/**
* Mindmap-Seite JavaScript
* Spezifische Funktionen für die Mindmap-Seite
*/
// Füge das Modul zum globalen MindMap-Objekt hinzu
if (!window.MindMap) {
window.MindMap = {};
}
// Registriere den Initialisierer im MindMap-Objekt
window.MindMap.pageInitializers = window.MindMap.pageInitializers || {};
window.MindMap.pageInitializers.mindmap = initMindmapPage;
// Event-Listener für DOMContentLoaded
document.addEventListener('DOMContentLoaded', function() {
// Prüfe, ob wir auf der Mindmap-Seite sind
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
initMindmapPage();
}
});
/**
* Initialisiert die Mindmap-Seite
*/
function initMindmapPage() {
console.log('Mindmap-Seite wird initialisiert...');
// Warte auf die Cytoscape-Instanz
document.addEventListener('mindmap-loaded', function() {
const cy = window.cy;
if (!cy) return;
// Event-Listener für Knoten-Klicks
cy.on('tap', 'node', function(evt) {
const node = evt.target;
// Alle vorherigen Hervorhebungen zurücksetzen
cy.nodes().forEach(n => {
n.removeStyle();
n.connectedEdges().removeStyle();
});
// Speichere ausgewählten Knoten
window.mindmapInstance.selectedNode = node;
// Aktiviere leuchtenden Effekt statt Umkreisung
node.style({
'background-opacity': 1,
'background-color': node.data('color'),
'shadow-color': node.data('color'),
'shadow-opacity': 1,
'shadow-blur': 15,
'shadow-offset-x': 0,
'shadow-offset-y': 0
});
// Verbundene Kanten und Knoten hervorheben
const connectedEdges = node.connectedEdges();
const connectedNodes = node.neighborhood('node');
connectedEdges.style({
'line-color': '#a78bfa',
'target-arrow-color': '#a78bfa',
'source-arrow-color': '#a78bfa',
'line-opacity': 0.8,
'width': 2
});
connectedNodes.style({
'shadow-opacity': 0.7,
'shadow-blur': 10,
'shadow-color': '#a78bfa'
});
// Info-Panel aktualisieren
updateInfoPanel(node);
// Seitenleiste aktualisieren
updateSidebar(node);
});
// Klick auf Hintergrund - Auswahl zurücksetzen
cy.on('tap', function(evt) {
if (evt.target === cy) {
resetSelection(cy);
}
});
// Zoom-Controls
document.getElementById('zoomIn')?.addEventListener('click', () => {
cy.zoom({
level: cy.zoom() * 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
});
document.getElementById('zoomOut')?.addEventListener('click', () => {
cy.zoom({
level: cy.zoom() / 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
});
document.getElementById('resetView')?.addEventListener('click', () => {
cy.fit();
resetSelection(cy);
});
// Legend-Toggle
document.getElementById('toggleLegend')?.addEventListener('click', () => {
const legend = document.getElementById('categoryLegend');
if (legend) {
isLegendVisible = !isLegendVisible;
legend.style.display = isLegendVisible ? 'block' : 'none';
}
});
// Keyboard-Controls
document.addEventListener('keydown', (e) => {
if (e.key === '+' || e.key === '=') {
cy.zoom({
level: cy.zoom() * 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
} else if (e.key === '-' || e.key === '_') {
cy.zoom({
level: cy.zoom() / 1.2,
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
});
} else if (e.key === 'Escape') {
resetSelection(cy);
}
});
});
}
/**
* Aktualisiert das Info-Panel mit Knoteninformationen
* @param {Object} node - Der ausgewählte Knoten
*/
function updateInfoPanel(node) {
const infoPanel = document.getElementById('infoPanel');
if (!infoPanel) return;
const data = node.data();
const connectedNodes = node.neighborhood('node');
let html = `
<h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li>
`;
});
html += `
</ul>
</div>
`;
infoPanel.innerHTML = html;
infoPanel.style.display = 'block';
}
/**
* Aktualisiert die Seitenleiste mit Knoteninformationen
* @param {Object} node - Der ausgewählte Knoten
*/
function updateSidebar(node) {
const sidebar = document.getElementById('sidebar');
if (!sidebar) return;
const data = node.data();
const connectedNodes = node.neighborhood('node');
let html = `
<div class="node-details">
<h3>${data.label || data.name}</h3>
<p class="category">${data.category || 'Keine Kategorie'}</p>
${data.description ? `<p class="description">${data.description}</p>` : ''}
<div class="connections">
<h4>Verbindungen (${connectedNodes.length})</h4>
<ul>
`;
connectedNodes.forEach(connectedNode => {
const connectedData = connectedNode.data();
html += `
<li style="color: ${connectedData.color || '#60a5fa'}">
${connectedData.label || connectedData.name}
</li>
`;
});
html += `
</ul>
</div>
</div>
`;
sidebar.innerHTML = html;
}
/**
* Setzt die Auswahl zurück
* @param {Object} cy - Cytoscape-Instanz
*/
function resetSelection(cy) {
window.mindmapInstance.selectedNode = null;
// Alle Hervorhebungen zurücksetzen
cy.nodes().forEach(node => {
node.removeStyle();
node.connectedEdges().removeStyle();
});
// Info-Panel ausblenden
const infoPanel = document.getElementById('infoPanel');
if (infoPanel) {
infoPanel.style.display = 'none';
}
// Seitenleiste leeren
const sidebar = document.getElementById('sidebar');
if (sidebar) {
sidebar.innerHTML = '';
}
}
/**
* Generiert Standarddaten für die Mindmap als Fallback
*/
function generateDefaultData() {
return {
nodes: [
{ id: 'root', name: 'Wissen', description: 'Zentrale Wissensbasis', category: 'Zentral', color_code: '#4299E1' },
{ id: 'philosophy', name: 'Philosophie', description: 'Philosophisches Denken', category: 'Philosophie', color_code: '#9F7AEA', parent_id: 'root' },
{ id: 'science', name: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', category: 'Wissenschaft', color_code: '#48BB78', parent_id: 'root' },
{ id: 'technology', name: 'Technologie', description: 'Technologische Entwicklungen', category: 'Technologie', color_code: '#ED8936', parent_id: 'root' },
{ id: 'arts', name: 'Künste', description: 'Künstlerische Ausdrucksformen', category: 'Künste', color_code: '#ED64A6', parent_id: 'root' },
{ id: 'psychology', name: 'Psychologie', description: 'Menschliches Verhalten und Geist', category: 'Psychologie', color_code: '#4299E1', parent_id: 'root' }
]
};
}
/**
* Rendert die Mindmap mit Cytoscape.js
*/
function renderMindmap(data) {
console.log('Rendere Mindmap mit Daten:', data);
// Konvertiere Backend-Daten in Cytoscape-Format
const elements = convertToCytoscapeFormat(data);
// Leere den Container
cyContainer.innerHTML = '';
// Erstelle Cytoscape-Instanz
mindmap = cytoscape({
container: cyContainer,
elements: elements,
style: [
{
selector: 'node',
style: {
'background-color': 'data(color)',
'label': 'data(name)',
'width': 30,
'height': 30,
'font-size': 12,
'text-valign': 'bottom',
'text-halign': 'center',
'text-margin-y': 8,
'color': document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#334155',
'text-background-color': document.documentElement.classList.contains('dark') ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)',
'text-background-opacity': 0.8,
'text-background-padding': '2px',
'text-background-shape': 'roundrectangle',
'text-wrap': 'ellipsis',
'text-max-width': '100px'
}
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'target-arrow-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'curve-style': 'bezier'
}
},
{
selector: 'node:selected',
style: {
'background-color': 'data(color)',
'border-width': 3,
'border-color': '#8b5cf6',
'width': 40,
'height': 40,
'font-size': 14,
'font-weight': 'bold',
'text-background-color': '#8b5cf6',
'text-background-opacity': 0.9
}
}
],
layout: {
name: 'cose',
animate: true,
animationDuration: 800,
nodeDimensionsIncludeLabels: true,
refresh: 30,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
nodeOverlap: 20,
idealEdgeLength: 200,
edgeElasticity: 100,
nestingFactor: 1.2,
gravity: 80,
fit: true,
padding: 30
}
});
// Event-Listener für Knoteninteraktionen
mindmap.on('tap', 'node', function(evt) {
const node = evt.target;
const nodeData = node.data();
// Update Info-Panel
updateNodeInfoPanel(nodeData);
// Lade verbundene Knoten
updateConnectedNodes(node);
});
// Toolbar-Buttons aktivieren
if (fitButton) {
fitButton.addEventListener('click', () => {
mindmap.fit();
mindmap.center();
});
}
if (resetButton) {
resetButton.addEventListener('click', () => {
mindmap.layout({
name: 'cose',
animate: true,
randomize: true,
fit: true
}).run();
});
}
if (toggleLabelsButton) {
let labelsVisible = true;
toggleLabelsButton.addEventListener('click', () => {
labelsVisible = !labelsVisible;
if (labelsVisible) {
mindmap.style()
.selector('node')
.style('label', 'data(name)')
.update();
} else {
mindmap.style()
.selector('node')
.style('label', '')
.update();
}
});
}
// Dark Mode-Änderungen überwachen
document.addEventListener('darkModeToggled', function(event) {
updateDarkModeStyles(event.detail.isDark);
});
// Initial fit und center
setTimeout(() => {
mindmap.fit();
mindmap.center();
}, 100);
}
/**
* Konvertiert die Backend-Daten ins Cytoscape-Format
*/
function convertToCytoscapeFormat(data) {
const elements = {
nodes: [],
edges: []
};
// Nodes hinzufügen
if (data.nodes && data.nodes.length > 0) {
data.nodes.forEach(node => {
elements.nodes.push({
data: {
id: String(node.id),
name: node.name,
description: node.description || 'Keine Beschreibung verfügbar',
category: node.category || 'Allgemein',
color: node.color_code || getRandomColor()
}
});
// Kante zum Elternknoten hinzufügen (falls vorhanden)
if (node.parent_id) {
elements.edges.push({
data: {
id: `edge-${node.parent_id}-${node.id}`,
source: String(node.parent_id),
target: String(node.id)
}
});
}
});
// Zusätzliche Kanten zwischen Knoten hinzufügen (falls in den Daten vorhanden)
if (data.edges && data.edges.length > 0) {
data.edges.forEach(edge => {
elements.edges.push({
data: {
id: `edge-${edge.source}-${edge.target}`,
source: String(edge.source),
target: String(edge.target)
}
});
});
}
}
return elements;
}
/**
* Aktualisiert das Informations-Panel mit den Knotendaten
*/
function updateNodeInfoPanel(nodeData) {
if (nodeInfoPanel && nodeDescription) {
// Panel anzeigen
nodeInfoPanel.style.display = 'block';
// Titel und Beschreibung aktualisieren
const titleElement = nodeInfoPanel.querySelector('.info-panel-title');
if (titleElement) {
titleElement.textContent = nodeData.name;
}
if (nodeDescription) {
nodeDescription.textContent = nodeData.description || 'Keine Beschreibung verfügbar';
}
}
}
/**
* Aktualisiert die Liste der verbundenen Knoten
*/
function updateConnectedNodes(node) {
if (connectedNodes) {
// Leere den Container
connectedNodes.innerHTML = '';
// Hole verbundene Knoten
const connectedEdges = node.connectedEdges();
if (connectedEdges.length === 0) {
connectedNodes.innerHTML = '<div class="text-sm italic">Keine verbundenen Knoten</div>';
return;
}
// Füge alle verbundenen Knoten hinzu
connectedEdges.forEach(edge => {
const targetNode = edge.target().id() === node.id() ? edge.source() : edge.target();
const targetData = targetNode.data();
const nodeLink = document.createElement('div');
nodeLink.className = 'node-link';
nodeLink.innerHTML = `
<span class="w-2 h-2 rounded-full" style="background-color: ${targetData.color}"></span>
${targetData.name}
`;
// Klick-Event zum Fokussieren des Knotens
nodeLink.addEventListener('click', () => {
mindmap.center(targetNode);
targetNode.select();
updateNodeInfoPanel(targetData);
updateConnectedNodes(targetNode);
});
connectedNodes.appendChild(nodeLink);
});
}
}
/**
* Aktualisiert die Styles bei Dark Mode-Änderungen
*/
function updateDarkModeStyles(isDark) {
if (!mindmap) return;
const textColor = isDark ? '#f1f5f9' : '#334155';
const textBgColor = isDark ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)';
const edgeColor = isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)';
mindmap.style()
.selector('node')
.style({
'color': textColor,
'text-background-color': textBgColor
})
.selector('edge')
.style({
'line-color': edgeColor,
'target-arrow-color': edgeColor
})
.update();
}
/**
* Generiert eine zufällige Farbe
*/
function getRandomColor() {
const colors = [
'#4299E1', // Blau
'#9F7AEA', // Lila
'#48BB78', // Grün
'#ED8936', // Orange
'#ED64A6', // Pink
'#F56565' // Rot
];
return colors[Math.floor(Math.random() * colors.length)];
}
// Initialisiere die Mindmap-Seite
initMindmapPage();

1133
static/js/social.js Normal file

File diff suppressed because it is too large Load Diff

2724
static/js/update_mindmap.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,415 @@
/**
* Vereinfachter Neuronales Netzwerk Hintergrund
* Verwendet Canvas 2D anstelle von WebGL für bessere Leistung
*/
class NeuralNetworkBackground {
constructor() {
// Canvas einrichten
this.canvas = document.createElement('canvas');
this.canvas.id = 'neural-network-background';
this.canvas.style.position = 'fixed';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvas.style.zIndex = '-10';
this.canvas.style.pointerEvents = 'none';
this.canvas.style.opacity = '1';
this.canvas.style.transition = 'opacity 3.5s ease-in-out';
// Falls Canvas bereits existiert, entfernen
const existingCanvas = document.getElementById('neural-network-background');
if (existingCanvas) {
existingCanvas.remove();
}
// An body anhängen als erstes Kind
if (document.body.firstChild) {
document.body.insertBefore(this.canvas, document.body.firstChild);
} else {
document.body.appendChild(this.canvas);
}
// 2D Context
this.ctx = this.canvas.getContext('2d');
// Eigenschaften
this.nodes = [];
this.connections = [];
this.activeConnections = new Set();
this.animationFrameId = null;
this.isDestroying = false;
// Farben für Dark/Light Mode
this.colors = {
dark: {
background: '#040215',
nodeColor: '#6a5498',
nodePulse: '#9c7fe0',
connectionColor: '#4a3870',
flowColor: '#b47fea'
},
light: {
background: '#f8f9fc',
nodeColor: '#8c6db5',
nodePulse: '#b094dd',
connectionColor: '#9882bd',
flowColor: '#7d5bb5'
}
};
// Aktuelle Farbpalette basierend auf Theme
this.currentColors = document.documentElement.classList.contains('dark')
? this.colors.dark
: this.colors.light;
// Konfiguration
this.config = {
nodeCount: 80, // Anzahl der Knoten
nodeSize: 2.5, // Größe der Knoten
connectionDistance: 150, // Maximale Verbindungsdistanz
connectionOpacity: 0.5, // Erhöht von 0.3 auf 0.5 - Deckkraft der ständigen Verbindungen
animationSpeed: 0.15, // Geschwindigkeit der Animation
flowDensity: 2, // Anzahl aktiver Verbindungen
maxFlowsPerNode: 2, // Maximale Anzahl aktiver Verbindungen pro Knoten
flowDuration: [2000, 5000], // Min/Max Dauer des Flows in ms
nodePulseFrequency: 0.01 // Wie oft Knoten pulsieren
};
// Initialisieren
this.init();
// Event-Listener
window.addEventListener('resize', this.resizeCanvas.bind(this));
console.log('Vereinfachter Neural Network Background initialized');
}
init() {
this.resizeCanvas();
this.createNodes();
this.createConnections();
this.startAnimation();
}
resizeCanvas() {
const pixelRatio = window.devicePixelRatio || 1;
const width = window.innerWidth;
const height = window.innerHeight;
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
this.canvas.width = width * pixelRatio;
this.canvas.height = height * pixelRatio;
if (this.ctx) {
this.ctx.scale(pixelRatio, pixelRatio);
}
// Neuberechnung der Knotenpositionen nach Größenänderung
if (this.nodes.length) {
this.createNodes();
this.createConnections();
}
}
createNodes() {
this.nodes = [];
const width = this.canvas.width / (window.devicePixelRatio || 1);
const height = this.canvas.height / (window.devicePixelRatio || 1);
// Cluster-Zentren für realistisches neuronales Netzwerk
const clusterCount = Math.floor(6 + Math.random() * 4);
const clusters = [];
for (let i = 0; i < clusterCount; i++) {
clusters.push({
x: Math.random() * width,
y: Math.random() * height,
radius: 100 + Math.random() * 150
});
}
// Knoten erstellen
for (let i = 0; i < this.config.nodeCount; i++) {
// Wähle zufällig ein Cluster
const cluster = clusters[Math.floor(Math.random() * clusters.length)];
// Erstelle einen Knoten innerhalb des Clusters mit zufälligem Offset
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * cluster.radius;
const node = {
id: i,
x: cluster.x + Math.cos(angle) * distance,
y: cluster.y + Math.sin(angle) * distance,
size: this.config.nodeSize * (0.8 + Math.random() * 0.4),
speed: {
x: (Math.random() - 0.5) * 0.2,
y: (Math.random() - 0.5) * 0.2
},
lastPulse: 0,
pulseInterval: 5000 + Math.random() * 10000, // Zufälliges Pulsieren
connections: []
};
this.nodes.push(node);
}
}
createConnections() {
this.connections = [];
// Verbindungen zwischen Knoten erstellen
for (let i = 0; i < this.nodes.length; i++) {
const nodeA = this.nodes[i];
for (let j = i + 1; j < this.nodes.length; j++) {
const nodeB = this.nodes[j];
const dx = nodeA.x - nodeB.x;
const dy = nodeA.y - nodeB.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.config.connectionDistance) {
const connection = {
id: `${i}-${j}`,
from: i,
to: j,
distance: distance,
opacity: Math.max(0.05, 1 - (distance / this.config.connectionDistance)),
active: false,
flowProgress: 0,
flowDuration: 0,
flowStart: 0
};
this.connections.push(connection);
nodeA.connections.push(connection);
nodeB.connections.push(connection);
}
}
}
}
startAnimation() {
this.animate();
}
animate() {
this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
const now = Date.now();
this.updateNodes(now);
this.updateConnections(now);
this.render(now);
}
updateNodes(now) {
const width = this.canvas.width / (window.devicePixelRatio || 1);
const height = this.canvas.height / (window.devicePixelRatio || 1);
// Knoten bewegen
for (let i = 0; i < this.nodes.length; i++) {
const node = this.nodes[i];
node.x += node.speed.x;
node.y += node.speed.y;
// Begrenzung am Rand
if (node.x < 0 || node.x > width) {
node.speed.x *= -1;
}
if (node.y < 0 || node.y > height) {
node.speed.y *= -1;
}
// Zufällig Richtung ändern
if (Math.random() < 0.01) {
node.speed.x = (Math.random() - 0.5) * 0.2;
node.speed.y = (Math.random() - 0.5) * 0.2;
}
// Zufälliges Pulsieren
if (Math.random() < this.config.nodePulseFrequency && now - node.lastPulse > node.pulseInterval) {
node.lastPulse = now;
}
}
}
updateConnections(now) {
// Update aktive Verbindungen
for (const connectionId of this.activeConnections) {
const connection = this.connections.find(c => c.id === connectionId);
if (!connection) continue;
// Aktualisiere den Flow-Fortschritt
const elapsed = now - connection.flowStart;
const progress = elapsed / connection.flowDuration;
if (progress >= 1) {
// Flow beenden
connection.active = false;
this.activeConnections.delete(connectionId);
} else {
connection.flowProgress = progress;
}
}
// Neue Flows starten, wenn unter dem Limit
if (this.activeConnections.size < this.config.flowDensity) {
// Wähle eine zufällige Verbindung
const availableConnections = this.connections.filter(c => !c.active);
if (availableConnections.length > 0) {
const randomIndex = Math.floor(Math.random() * availableConnections.length);
const connection = availableConnections[randomIndex];
// Aktiviere die Verbindung
connection.active = true;
connection.flowProgress = 0;
connection.flowStart = now;
connection.flowDuration = this.config.flowDuration[0] +
Math.random() * (this.config.flowDuration[1] - this.config.flowDuration[0]);
this.activeConnections.add(connection.id);
}
}
}
render(now) {
// Aktualisiere Farben basierend auf aktuellem Theme
this.currentColors = document.documentElement.classList.contains('dark')
? this.colors.dark
: this.colors.light;
const colors = this.currentColors;
const width = this.canvas.width / (window.devicePixelRatio || 1);
const height = this.canvas.height / (window.devicePixelRatio || 1);
// Hintergrund löschen
this.ctx.fillStyle = colors.background;
this.ctx.fillRect(0, 0, width, height);
// Verbindungen zeichnen (statisch)
this.ctx.strokeStyle = colors.connectionColor;
this.ctx.lineWidth = 1.2;
for (const connection of this.connections) {
const fromNode = this.nodes[connection.from];
const toNode = this.nodes[connection.to];
this.ctx.globalAlpha = connection.opacity * 0.5;
this.ctx.beginPath();
this.ctx.moveTo(fromNode.x, fromNode.y);
this.ctx.lineTo(toNode.x, toNode.y);
this.ctx.stroke();
}
// Aktive Verbindungen zeichnen (Flows)
this.ctx.strokeStyle = colors.flowColor;
this.ctx.lineWidth = 2.5;
for (const connectionId of this.activeConnections) {
const connection = this.connections.find(c => c.id === connectionId);
if (!connection) continue;
const fromNode = this.nodes[connection.from];
const toNode = this.nodes[connection.to];
// Glühen-Effekt
this.ctx.globalAlpha = Math.sin(connection.flowProgress * Math.PI) * 0.8;
// Linie zeichnen
this.ctx.beginPath();
this.ctx.moveTo(fromNode.x, fromNode.y);
this.ctx.lineTo(toNode.x, toNode.y);
this.ctx.stroke();
// Fließendes Partikel
const progress = connection.flowProgress;
const x = fromNode.x + (toNode.x - fromNode.x) * progress;
const y = fromNode.y + (toNode.y - fromNode.y) * progress;
this.ctx.globalAlpha = 0.9;
this.ctx.fillStyle = colors.flowColor;
this.ctx.beginPath();
this.ctx.arc(x, y, 2, 0, Math.PI * 2);
this.ctx.fill();
}
// Knoten zeichnen
for (const node of this.nodes) {
// Pulsierende Knoten
const timeSinceLastPulse = now - node.lastPulse;
const isPulsing = timeSinceLastPulse < 800;
const pulseProgress = isPulsing ? timeSinceLastPulse / 800 : 0;
// Knoten selbst
this.ctx.globalAlpha = 1;
this.ctx.fillStyle = isPulsing
? colors.nodePulse
: colors.nodeColor;
this.ctx.beginPath();
this.ctx.arc(node.x, node.y, node.size + (isPulsing ? 1 * Math.sin(pulseProgress * Math.PI) : 0), 0, Math.PI * 2);
this.ctx.fill();
// Wenn pulsierend, füge einen Glow-Effekt hinzu
if (isPulsing) {
this.ctx.globalAlpha = 0.5 * (1 - pulseProgress);
this.ctx.beginPath();
this.ctx.arc(node.x, node.y, node.size + 5 * pulseProgress, 0, Math.PI * 2);
this.ctx.fill();
}
}
this.ctx.globalAlpha = 1;
}
destroy() {
if (this.isDestroying) return;
this.isDestroying = true;
// Animation stoppen
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
// Canvas ausblenden
this.canvas.style.opacity = '0';
// Nach Übergang entfernen
setTimeout(() => {
if (this.canvas && this.canvas.parentNode) {
this.canvas.parentNode.removeChild(this.canvas);
}
}, 3500);
}
hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
}
// Initialisiert den Hintergrund, sobald die Seite geladen ist
document.addEventListener('DOMContentLoaded', () => {
window.neuralBackground = new NeuralNetworkBackground();
// Theme-Wechsel-Event-Listener
document.addEventListener('theme-changed', () => {
if (window.neuralBackground) {
window.neuralBackground.currentColors = document.documentElement.classList.contains('dark')
? window.neuralBackground.colors.dark
: window.neuralBackground.colors.light;
}
});
});

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

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Datenbank aktualisieren{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-10">
<div class="bg-gray-800 bg-opacity-70 rounded-lg p-6 mb-6">
<h1 class="text-2xl font-bold text-purple-400 mb-4">Datenbank aktualisieren</h1>
{% if message %}
<div class="mb-6 p-4 rounded-lg {{ 'bg-green-800 bg-opacity-50' if success else 'bg-red-800 bg-opacity-50' }}">
<p class="text-white">{{ message }}</p>
</div>
{% endif %}
<div class="mb-6">
<p class="text-gray-300 mb-4">
Diese Funktion aktualisiert die Datenbankstruktur, um mit dem aktuellen Datenmodell kompatibel zu sein.
Dabei werden folgende Änderungen vorgenommen:
</p>
<ul class="list-disc pl-6 text-gray-300 mb-6">
<li>Hinzufügen von <code>bio</code>, <code>location</code>, <code>website</code>, <code>avatar</code> und <code>last_login</code> zur Benutzer-Tabelle</li>
</ul>
<div class="bg-yellow-800 bg-opacity-30 p-4 rounded-lg mb-6">
<p class="text-yellow-200">
<i class="fas fa-exclamation-triangle mr-2"></i>
<strong>Warnung:</strong> Bitte stelle sicher, dass du ein Backup der Datenbank erstellt hast, bevor du fortfährst.
</p>
</div>
</div>
<form method="POST" action="{{ url_for('admin_update_database') }}">
<div class="flex justify-between">
<a href="{{ url_for('index') }}" class="px-4 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600">
Zurück zur Startseite
</a>
<button type="submit" class="px-4 py-2 bg-purple-700 text-white rounded-lg hover:bg-purple-600">
Datenbank aktualisieren
</button>
</div>
</form>
</div>
</div>
{% endblock %}

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