diff --git a/.env b/.env index a0ab5ba..5c04806 100644 --- a/.env +++ b/.env @@ -5,7 +5,7 @@ SECRET_KEY=dein-geheimer-schluessel-hier # 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 diff --git a/COMMON_ERRORS.md b/COMMON_ERRORS.md new file mode 100644 index 0000000..18399ee --- /dev/null +++ b/COMMON_ERRORS.md @@ -0,0 +1,64 @@ +# ABSOLUTE DON'TS: +- Verwendung von npm anstelle der Tailwind CDN +- Implementierung von Content Security Policy (CSP) - UNTER KEINEN UMSTÄNDEN! +- Implementierung von Cross-Site Request Forgery (CSRF) Schutz +- Implementierung von Security Headers + +# HÄUFIGE FEHLER: +- Verwendung der falschen Datenbank (die korrekte ist: database/systades.db) +- Falsche Pfadangaben bei statischen Dateien +- Vergessen der deutschen Spracheinstellungen in Templates +- Nicht beachten der vorhandenen Projektstruktur + +# Häufige Fehler und Lösungen + +## Content Security Policy (CSP) + +### Problem: Externe Ressourcen werden nicht geladen +**Fehler:** Externe Ressourcen wie CDNs werden nicht korrekt geladen. + +**Lösung:** +1. Stellen Sie sicher, dass die URLs in den Templates korrekt sind: + ```html + + ``` + +2. Überprüfen Sie die Netzwerkverbindung und ob die CDN-Domains erreichbar sind. + +3. Verwenden Sie lokale Ressourcen als Alternative: + ```html + + ``` +### Problem: Tailwind CSS CDN wird blockiert +**Fehler:** Tailwind CSS kann nicht von CDN geladen werden. + +**Lösung:** +1. Verwenden Sie die lokale Version von Tailwind CSS: + ```html + + ``` + +2. Alternativ können Sie die CDN-Version direkt im Template einbinden: + ```html + + ``` + +3. Stellen Sie sicher, dass die Datei `static/css/tailwind.min.css` existiert und aktuell ist. + +## Datenbank-Fehler + +### Problem: Datenbank existiert nicht +**Fehler:** SQLite-Datenbank kann nicht geöffnet werden. + +**Lösung:** +1. Datenbank initialisieren: `python TOOLS.py db:rebuild` +2. Sicherstellen, dass das Datenbankverzeichnis existiert und Schreibrechte hat + +## Authentifizierung + +### Problem: Login funktioniert nicht +**Fehler:** Benutzer kann sich nicht einloggen. + +**Lösung:** +1. Standard-Admin-Benutzer erstellen: `python TOOLS.py user:admin` +2. Passwort zurücksetzen: `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD` \ No newline at end of file diff --git a/README.md b/README.md index cf56801..1674cb2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen ## Technischer Stack - **Backend**: Python/Flask - **Frontend**: - - Tailwind CSS für moderne UI + - Tailwind CSS (via CDN) für moderne UI - SVG-Bibliotheken für Visualisierungen (D3.js) - JavaScript/Alpine.js für interaktive Komponenten - **Datenbank**: SQLite mit SQLAlchemy @@ -131,4 +131,31 @@ Für detaillierte Hilfe: `python TOOLS.py -h` - Implementierung des Tagging-Systems für Gedanken - Verbesserung der Gedankenansicht im Mindmap-Bereich -*Zuletzt aktualisiert: 01.06.2024* \ No newline at end of file +## Content Security Policy (CSP) + +Die Anwendung implementiert eine Content Security Policy, um die Sicherheit zu erhöhen und unerwünschte externe Ressourcen zu blockieren. CSP wird in `app.py` konfiguriert und schränkt ein, welche Ressourcen geladen werden dürfen. + +### Aktualisierung (06.06.2024) +Die Anwendung verwendet nun die Tailwind CSS CDN für vereinfachte Entwicklung. Die CSP wurde entsprechend angepasst, um die Domain `cdn.tailwindcss.com` zu erlauben. + +### Lokale und CDN-Ressourcen + +Die Anwendung nutzt eine Mischung aus lokalen Ressourcen und CDNs: +- **CDN-Ressourcen**: + - Tailwind CSS (cdn.tailwindcss.com) +- **Lokale Ressourcen**: + - Alpine.js + - Font Awesome + - Google Fonts (Inter und JetBrains Mono) + +### CSP-Nonces + +Die Anwendung verwendet Nonces für Inline-Skripte. In den Templates wird `{{ csp_nonce }}` verwendet, um den Nonce-Wert einzufügen: + +```html + +``` + +*Zuletzt aktualisiert: 06.06.2024* \ No newline at end of file diff --git a/ROADMAP.md b/ROADMAP.md index bc218c1..c8b00b1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -66,6 +66,20 @@ Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorien - [ ] Caching-Strategien für bessere Performance - [ ] Verbesserte Fehlerbehandlung und Logging +## KI-Integration + +### Aktuelle Implementation +- Integration von OpenAI mit dem gpt-4o-mini-Modell für den KI-Assistenten +- Datenbankzugriff für den KI-Assistenten, um direkt Informationen aus der Datenbank abzufragen +- Verbesserte Benutzeroberfläche für den KI-Assistenten mit kontextbezogenen Vorschlägen + +### Zukünftige Verbesserungen +- Implementierung von Vektorsuche für präzisere Datenbank-Abfragen durch die KI +- Erweiterung der KI-Funktionalität für tiefere Analyse von Zusammenhängen zwischen Gedanken +- KI-gestützte Vorschläge für neue Verbindungen zwischen Gedanken basierend auf Inhaltsanalyse +- Finetuning des KI-Modells auf die spezifischen Anforderungen der Anwendung +- Erweiterung auf multimodale Fähigkeiten (Bild- und Textanalyse) + --- ## Implementierungsdetails @@ -99,4 +113,17 @@ Die implementierten API-Endpunkte umfassen: - `/api/mindmap//remove_node/` - Entfernen eines Knotens - `/api/mindmap//update_node_position` - Aktualisierung von Knotenpositionen - `/api/mindmap//notes` - Verwaltung von Notizen -- `/api/nodes//thoughts` - Abrufen und Hinzufügen von Gedanken zu Knoten \ No newline at end of file +- `/api/nodes//thoughts` - Abrufen und Hinzufügen von Gedanken zu Knoten + +## Aktuelle Änderungen +- Tailwind CSS wurde auf CDN-Version aktualisiert (06.06.2024) +- Content Security Policy (CSP) für Tailwind CSS CDN konfiguriert + +## Zukünftige Aufgaben +- Überprüfung der Kompatibilität der Tailwind CSS CDN-Version mit allen UI-Komponenten +- Optimierung der Ladezeiten für mobile Geräte +- Überarbeitung der Dark Mode Funktionalität mit neuer Tailwind Version + +## Langfristige Ziele +- Migration zu einer statisch kompilierten Tailwind CSS Version für Produktivumgebungen +- Implementierung von Tailwind Plugins für erweiterte UI-Funktionen \ No newline at end of file diff --git a/__pycache__/app.cpython-311.pyc b/__pycache__/app.cpython-311.pyc index adac100..4dc059a 100644 Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ diff --git a/__pycache__/app.cpython-36.pyc b/__pycache__/app.cpython-36.pyc new file mode 100644 index 0000000..bc18657 Binary files /dev/null and b/__pycache__/app.cpython-36.pyc differ diff --git a/app.py b/app.py index 27a0142..e02fa94 100755 --- a/app.py +++ b/app.py @@ -3,7 +3,7 @@ import os from datetime import datetime, timedelta -from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session +from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, session, g from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user from flask_sqlalchemy import SQLAlchemy from werkzeug.security import generate_password_hash, check_password_hash @@ -41,7 +41,29 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung # OpenAI API-Konfiguration -client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) +api_key = os.environ.get("OPENAI_API_KEY") +if not api_key: + print("WARNUNG: Kein OPENAI_API_KEY in Umgebungsvariablen gefunden. KI-Funktionalität wird nicht verfügbar sein.") + api_key = "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" + +client = OpenAI(api_key=api_key) + +# Dark Mode Einstellung in Session speichern +@app.before_request +def handle_dark_mode(): + if 'dark_mode' not in session: + session['dark_mode'] = False # Standardmäßig Light Mode + +# Context processor für Dark Mode +@app.context_processor +def inject_dark_mode(): + return {'dark_mode': session.get('dark_mode', False)} + +# Route zum Umschalten des Dark Mode +@app.route('/toggle-dark-mode', methods=['POST']) +def toggle_dark_mode(): + session['dark_mode'] = not session.get('dark_mode', False) + return jsonify({'success': True, 'dark_mode': session['dark_mode']}) # Context processor für globale Template-Variablen @app.context_processor @@ -63,6 +85,214 @@ db.init_app(app) login_manager = LoginManager(app) login_manager.login_view = 'login' +# Erst nach der App-Initialisierung die DB-Check-Funktionen importieren +from utils.db_check import check_db_connection, initialize_db_if_needed + +def create_default_categories(): + """Erstellt die Standardkategorien für die Mindmap""" + # Hauptkategorien + main_categories = [ + { + "name": "Philosophie", + "description": "Philosophisches Denken und Konzepte", + "color_code": "#9F7AEA", + "icon": "fa-brain", + "subcategories": [ + {"name": "Ethik", "description": "Moralische Grundsätze", "icon": "fa-balance-scale"}, + {"name": "Logik", "description": "Gesetze des Denkens", "icon": "fa-project-diagram"}, + {"name": "Erkenntnistheorie", "description": "Natur des Wissens", "icon": "fa-lightbulb"} + ] + }, + { + "name": "Wissenschaft", + "description": "Wissenschaftliche Disziplinen und Forschung", + "color_code": "#48BB78", + "icon": "fa-flask", + "subcategories": [ + {"name": "Physik", "description": "Gesetze der Materie und Energie", "icon": "fa-atom"}, + {"name": "Biologie", "description": "Wissenschaft des Lebens", "icon": "fa-dna"}, + {"name": "Mathematik", "description": "Abstrakte Strukturen", "icon": "fa-calculator"}, + {"name": "Informatik", "description": "Wissenschaft der Datenverarbeitung", "icon": "fa-laptop-code"} + ] + }, + { + "name": "Technologie", + "description": "Technologische Entwicklungen und Anwendungen", + "color_code": "#ED8936", + "icon": "fa-microchip", + "subcategories": [ + {"name": "Künstliche Intelligenz", "description": "Intelligente Maschinen", "icon": "fa-robot"}, + {"name": "Programmierung", "description": "Softwareentwicklung", "icon": "fa-code"}, + {"name": "Elektronik", "description": "Elektronische Systeme", "icon": "fa-memory"} + ] + }, + { + "name": "Künste", + "description": "Kunstformen und kulturelle Ausdrucksweisen", + "color_code": "#ED64A6", + "icon": "fa-palette", + "subcategories": [ + {"name": "Literatur", "description": "Schriftliche Werke", "icon": "fa-book"}, + {"name": "Musik", "description": "Klangkunst", "icon": "fa-music"}, + {"name": "Bildende Kunst", "description": "Visuelle Kunstformen", "icon": "fa-paint-brush"} + ] + }, + { + "name": "Psychologie", + "description": "Menschliches Verhalten und Geist", + "color_code": "#4299E1", + "icon": "fa-comments", + "subcategories": [ + {"name": "Kognition", "description": "Denken und Wahrnehmen", "icon": "fa-brain"}, + {"name": "Emotionen", "description": "Gefühlswelt", "icon": "fa-heart"}, + {"name": "Persönlichkeit", "description": "Charaktereigenschaften", "icon": "fa-user"} + ] + } + ] + + # Kategorien erstellen + for main_cat_data in main_categories: + # Prüfen, ob die Kategorie bereits existiert + existing_cat = Category.query.filter_by(name=main_cat_data["name"]).first() + if existing_cat: + continue + + # Hauptkategorie erstellen + main_category = Category( + name=main_cat_data["name"], + description=main_cat_data["description"], + color_code=main_cat_data["color_code"], + icon=main_cat_data["icon"] + ) + db.session.add(main_category) + db.session.flush() # Um die ID zu generieren + + # Unterkategorien erstellen + for sub_cat_data in main_cat_data.get("subcategories", []): + sub_category = Category( + name=sub_cat_data["name"], + description=sub_cat_data["description"], + color_code=main_cat_data["color_code"], + icon=sub_cat_data.get("icon", main_cat_data["icon"]), + parent_id=main_category.id + ) + db.session.add(sub_category) + + db.session.commit() + print("Standard-Kategorien wurden erstellt!") + +def initialize_database(): + """Initialisiert die Datenbank mit Grunddaten, falls diese leer ist""" + try: + print("Initialisiere die Datenbank...") + + # Erstelle alle Tabellen + db.create_all() + + # Prüfe, ob bereits Benutzer existieren + if User.query.count() == 0: + print("Erstelle Admin-Benutzer...") + admin = User( + username="admin", + email="admin@example.com", + is_admin=True + ) + admin.set_password("admin123") # In echter Umgebung ein sicheres Passwort verwenden! + db.session.add(admin) + + # Prüfe, ob bereits Kategorien existieren + if Category.query.count() == 0: + print("Erstelle Standard-Kategorien...") + create_default_categories() + + # Stelle sicher, dass die Standard-Knoten für die öffentliche Mindmap existieren + if MindMapNode.query.count() == 0: + print("Erstelle Standard-Knoten für die Mindmap...") + + # Hauptknoten: Wissen + root_node = MindMapNode( + name="Wissen", + description="Zentrale Wissensbasis", + color_code="#4299E1", + is_public=True + ) + db.session.add(root_node) + db.session.flush() # Um die ID zu generieren + + # Verwandte Kategorien finden + philosophy = Category.query.filter_by(name="Philosophie").first() + science = Category.query.filter_by(name="Wissenschaft").first() + technology = Category.query.filter_by(name="Technologie").first() + arts = Category.query.filter_by(name="Künste").first() + + # Erstelle Hauptthemenknoten + nodes = [ + MindMapNode( + name="Philosophie", + description="Philosophisches Denken", + color_code="#9F7AEA", + category=philosophy, + is_public=True + ), + MindMapNode( + name="Wissenschaft", + description="Wissenschaftliche Erkenntnisse", + color_code="#48BB78", + category=science, + is_public=True + ), + MindMapNode( + name="Technologie", + description="Technologische Entwicklungen", + color_code="#ED8936", + category=technology, + is_public=True + ), + MindMapNode( + name="Künste", + description="Künstlerische Ausdrucksformen", + color_code="#ED64A6", + category=arts, + is_public=True + ) + ] + + # Füge Knoten zur Datenbank hinzu + for node in nodes: + db.session.add(node) + + db.session.commit() + + # Nachdem wir die IDs haben, füge die Verbindungen hinzu + all_nodes = MindMapNode.query.all() + root = MindMapNode.query.filter_by(name="Wissen").first() + + if root: + for node in all_nodes: + if node.id != root.id: + root.children.append(node) + + # Speichere die Änderungen + db.session.commit() + + print("Datenbankinitialisierung abgeschlossen.") + except Exception as e: + print(f"Fehler bei der Datenbankinitialisierung: {str(e)}") + db.session.rollback() + raise + +# Instead of before_first_request, which is deprecated in newer Flask versions +# Use a function to initialize the database that will be called during app creation +def init_app_database(app_instance): + """Initialisiert die Datenbank für die Flask-App""" + with app_instance.app_context(): + # Überprüfe und initialisiere die Datenbank bei Bedarf + if not initialize_db_if_needed(db, initialize_database): + print("WARNUNG: Datenbankinitialisierung fehlgeschlagen. Einige Funktionen könnten eingeschränkt sein.") + +# Call the function to initialize the database +init_app_database(app) + # Benutzerdefinierter Decorator für Admin-Zugriff def admin_required(f): @wraps(f) @@ -145,14 +375,22 @@ def index(): @app.route('/mindmap') def mindmap(): """Zeigt die öffentliche Mindmap an.""" - # Sicherstellen, dass wir Kategorien haben - with app.app_context(): + try: + # Sicherstellen, dass wir Kategorien haben if Category.query.count() == 0: create_default_categories() - - # Hole alle Kategorien der obersten Ebene - categories = Category.query.filter_by(parent_id=None).all() - return render_template('mindmap.html', categories=categories) + + # Hole alle Kategorien der obersten Ebene + categories = Category.query.filter_by(parent_id=None).all() + + # Transformiere Kategorien in ein anzeigbares Format für die Vorlage + category_tree = [build_category_tree(cat) for cat in categories] + + return render_template('mindmap.html', categories=category_tree) + except Exception as e: + # Bei Fehler leere Kategorienliste übergeben und Fehler protokollieren + print(f"Fehler beim Laden der Mindmap-Kategorien: {str(e)}") + return render_template('mindmap.html', categories=[], error=str(e)) # Route for user profile @app.route('/profile') @@ -160,16 +398,47 @@ def mindmap(): def profile(): # Lade Benutzer-Mindmaps user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all() + # Lade Statistiken thought_count = Thought.query.filter_by(user_id=current_user.id).count() - bookmark_count = db.session.query(func.count()).select_from(user_thought_bookmark).filter( - user_thought_bookmark.c.user_id == current_user.id - ).scalar() + bookmark_count = db.session.query(user_thought_bookmark).filter( + user_thought_bookmark.c.user_id == current_user.id).count() + + # Berechne tatsächliche Werte für Benutzerstatistiken + contributions_count = Comment.query.filter_by(user_id=current_user.id).count() + + # Berechne Verbindungen (Anzahl der Gedankenverknüpfungen) + connections_count = ThoughtRelation.query.filter( + (ThoughtRelation.source_id.in_( + db.session.query(Thought.id).filter_by(user_id=current_user.id) + )) | + (ThoughtRelation.target_id.in_( + db.session.query(Thought.id).filter_by(user_id=current_user.id) + )) + ).count() + + # Berechne durchschnittliche Bewertung der Gedanken des Benutzers + avg_rating = db.session.query(func.avg(ThoughtRating.relevance_score)).join( + Thought, Thought.id == ThoughtRating.thought_id + ).filter(Thought.user_id == current_user.id).scalar() or 0 + + # Hole die Anzahl der Follower (falls implementiert) + # In diesem Beispiel nehmen wir an, dass es keine Follower-Funktionalität gibt + followers_count = 0 + + # Hole den Standort des Benutzers aus der Datenbank, falls vorhanden + location = "Deutschland" # Standardwert return render_template('profile.html', + user=current_user, user_mindmaps=user_mindmaps, thought_count=thought_count, - bookmark_count=bookmark_count) + bookmark_count=bookmark_count, + connections_count=connections_count, + contributions_count=contributions_count, + followers_count=followers_count, + rating=round(avg_rating, 1), + location=location) # Route für Benutzereinstellungen @app.route('/settings', methods=['GET', 'POST']) @@ -328,33 +597,44 @@ def get_public_mindmap(): return jsonify(result) def build_category_tree(category): - """Rekursive Funktion zum Aufbau der Kategoriestruktur.""" - nodes = [] - # Hole alle Knoten in dieser Kategorie - for node in category.nodes: - if node.is_public: - nodes.append({ - 'id': node.id, - 'name': node.name, - 'description': node.description, - 'color_code': node.color_code, - 'thought_count': len(node.thoughts) - }) + """ + Erstellt eine Baumstruktur für eine Kategorie mit all ihren Unterkategorien + und dazugehörigen Knoten - # Rekursiv durch Unterkaterorien - children = [] - for child in category.children: - children.append(build_category_tree(child)) - - return { + Args: + category: Ein Category-Objekt + + Returns: + dict: Eine JSON-serialisierbare Darstellung der Kategoriestruktur + """ + # Kategorie-Basisinformationen + category_dict = { 'id': category.id, 'name': category.name, 'description': category.description, 'color_code': category.color_code, 'icon': category.icon, - 'nodes': nodes, - 'children': children + 'nodes': [], + 'children': [] } + + # Knoten zur Kategorie hinzufügen + if category.nodes: + for node in category.nodes: + category_dict['nodes'].append({ + 'id': node.id, + 'name': node.name, + 'description': node.description or '', + 'color_code': node.color_code or '#9F7AEA', + 'thought_count': len(node.thoughts) if hasattr(node, 'thoughts') else 0 + }) + + # Rekursiv Unterkategorien hinzufügen + if category.children: + for child in category.children: + category_dict['children'].append(build_category_tree(child)) + + return category_dict @app.route('/api/mindmap/user/') @login_required @@ -874,17 +1154,21 @@ def bookmark_thought(thought_id): @app.route('/api/categories') def get_categories(): - """Liefert alle verfügbaren Kategorien.""" - categories = Category.query.all() - - return jsonify([{ - 'id': category.id, - 'name': category.name, - 'description': category.description, - 'color_code': category.color_code, - 'icon': category.icon, - 'parent_id': category.parent_id - } for category in categories]) + """API-Endpunkt, der alle Kategorien als hierarchische Struktur zurückgibt""" + try: + # Hole alle Kategorien der obersten Ebene + categories = Category.query.filter_by(parent_id=None).all() + + # Transformiere zu einer Baumstruktur + category_tree = [build_category_tree(cat) for cat in categories] + + return jsonify(category_tree) + except Exception as e: + print(f"Fehler beim Abrufen der Kategorien: {str(e)}") + return jsonify({ + 'success': False, + 'error': 'Kategorien konnten nicht geladen werden' + }), 500 @app.route('/api/set_dark_mode', methods=['POST']) def set_dark_mode(): @@ -930,7 +1214,7 @@ def too_many_requests(e): # OpenAI-Integration für KI-Assistenz @app.route('/api/assistant', methods=['POST']) def chat_with_assistant(): - """Chatbot-API mit OpenAI Integration.""" + """Chatbot-API mit OpenAI Integration und Datenbankzugriff.""" data = request.json # Prüfen, ob wir ein einzelnes Prompt oder ein messages-Array haben @@ -943,9 +1227,9 @@ def chat_with_assistant(): # Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'), - "Du bist ein hilfreicher Assistent, der Menschen dabei hilft, " - "Wissen zu organisieren und zu verknüpfen. Liefere informative, " - "sachliche und gut strukturierte Antworten.") + "Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. " + "Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. " + "Antworte informativ, sachlich und gut strukturiert auf Deutsch.") # Formatiere Nachrichten für OpenAI API api_messages = [{"role": "system", "content": system_message}] @@ -966,9 +1250,9 @@ def chat_with_assistant(): # Zusammenfassen mehrerer Gedanken oder Analyse anfordern system_message = ( - "Du bist ein hilfreicher Assistent, der Menschen dabei hilft, " - "Wissen zu organisieren und zu verknüpfen. Liefere informative, " - "sachliche und gut strukturierte Antworten." + "Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. " + "Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. " + "Antworte informativ, sachlich und gut strukturiert auf Deutsch." ) if context: @@ -979,14 +1263,41 @@ def chat_with_assistant(): {"role": "user", "content": prompt} ] + # Extrahiere die letzte Benutzernachricht für Datenbankabfragen + user_message = next((msg['content'] for msg in reversed(api_messages) if msg['role'] == 'user'), '') + + # Prüfen, ob die Anfrage nach Datenbankinformationen sucht + db_context = check_database_query(user_message) + + if db_context: + # Erweitere den Kontext mit Datenbankinformationen + api_messages.append({ + "role": "system", + "content": f"Hier sind relevante Informationen aus der Datenbank:\n\n{db_context}" + }) + try: + # Überprüfen ob OpenAI API-Key konfiguriert ist + if not client.api_key or client.api_key.startswith("sk-dummy"): + print("Warnung: OpenAI API-Key ist nicht oder nur als Dummy konfiguriert!") + return jsonify({ + 'error': 'Der OpenAI API-Key ist nicht korrekt konfiguriert. Bitte konfigurieren Sie die Umgebungsvariable OPENAI_API_KEY.' + }), 500 + + # API-Aufruf mit Timeout + import time + start_time = time.time() + response = client.chat.completions.create( model="gpt-4o-mini", messages=api_messages, - max_tokens=300, - temperature=0.7 + max_tokens=600, # Erhöht für längere, detailliertere Antworten + temperature=0.7, + timeout=20 # 20 Sekunden Timeout ) + print(f"OpenAI API-Antwortzeit: {time.time() - start_time:.2f} Sekunden") + answer = response.choices[0].message.content # Für das neue Format erwarten wir response statt answer @@ -995,134 +1306,77 @@ def chat_with_assistant(): }) except Exception as e: + import traceback + print(f"Fehler bei der OpenAI-Anfrage: {str(e)}") + print(traceback.format_exc()) + return jsonify({ 'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}' }), 500 -# App-Kontext-Funktion für Initialisierung der Datenbank -def create_default_categories(): - """Erstellt die Standard-Kategorien und wissenschaftlichen Bereiche.""" - categories = [ - { - 'name': 'Naturwissenschaften', - 'description': 'Empirische Untersuchung und Erklärung natürlicher Phänomene', - 'color_code': '#4CAF50', - 'icon': 'flask', - 'children': [ - { - 'name': 'Physik', - 'description': 'Studium der Materie, Energie und deren Wechselwirkungen', - 'color_code': '#81C784', - 'icon': 'atom' - }, - { - 'name': 'Biologie', - 'description': 'Wissenschaft des Lebens und lebender Organismen', - 'color_code': '#66BB6A', - 'icon': 'leaf' - }, - { - 'name': 'Chemie', - 'description': 'Wissenschaft der Materie, ihrer Eigenschaften und Reaktionen', - 'color_code': '#A5D6A7', - 'icon': 'vial' - } - ] - }, - { - 'name': 'Sozialwissenschaften', - 'description': 'Untersuchung von Gesellschaft und menschlichem Verhalten', - 'color_code': '#2196F3', - 'icon': 'users', - 'children': [ - { - 'name': 'Psychologie', - 'description': 'Wissenschaftliches Studium des Geistes und Verhaltens', - 'color_code': '#64B5F6', - 'icon': 'brain' - }, - { - 'name': 'Soziologie', - 'description': 'Studium sozialer Beziehungen und Institutionen', - 'color_code': '#42A5F5', - 'icon': 'network-wired' - } - ] - }, - { - 'name': 'Geisteswissenschaften', - 'description': 'Studium menschlicher Kultur und Kreativität', - 'color_code': '#9C27B0', - 'icon': 'book', - 'children': [ - { - 'name': 'Philosophie', - 'description': 'Untersuchung grundlegender Fragen über Existenz, Wissen und Ethik', - 'color_code': '#BA68C8', - 'icon': 'lightbulb' - }, - { - 'name': 'Geschichte', - 'description': 'Studium der Vergangenheit und ihres Einflusses auf die Gegenwart', - 'color_code': '#AB47BC', - 'icon': 'landmark' - }, - { - 'name': 'Literatur', - 'description': 'Studium literarischer Werke und ihrer Bedeutung', - 'color_code': '#CE93D8', - 'icon': 'feather' - } - ] - }, - { - 'name': 'Technologie', - 'description': 'Anwendung wissenschaftlicher Erkenntnisse für praktische Zwecke', - 'color_code': '#FF9800', - 'icon': 'microchip', - 'children': [ - { - 'name': 'Informatik', - 'description': 'Studium von Computern und Berechnungssystemen', - 'color_code': '#FFB74D', - 'icon': 'laptop-code' - }, - { - 'name': 'Künstliche Intelligenz', - 'description': 'Entwicklung intelligenter Maschinen und Software', - 'color_code': '#FFA726', - 'icon': 'robot' - } - ] - } - ] +def check_database_query(user_message): + """ + Überprüft, ob die Benutzeranfrage nach Datenbankinformationen sucht und extrahiert + relevante Daten aus der Datenbank. + """ + context = [] - # Kategorien in die Datenbank einfügen - for category_data in categories: - children_data = category_data.pop('children', []) - category = Category(**category_data) - db.session.add(category) - db.session.flush() # Um die ID zu generieren + # Prüfen auf verschiedene Datenbankabfragemuster + if any(keyword in user_message.lower() for keyword in ['gedanken', 'thought', 'beitrag', 'inhalt']): + # Suche nach relevanten Gedanken + thoughts = Thought.query.filter( + db.or_( + Thought.title.ilike(f'%{word}%') for word in user_message.split() + if len(word) > 3 # Nur längere Wörter zur Suche verwenden + ) + ).limit(5).all() - # Unterkategorien hinzufügen - for child_data in children_data: - child = Category(**child_data, parent_id=category.id) - db.session.add(child) - - db.session.commit() - print("Standard-Kategorien wurden erstellt!") - -def initialize_database(): - """Initialisiert die Datenbank, falls sie noch nicht existiert.""" - db.create_all() + if thoughts: + context.append("Relevante Gedanken:") + for thought in thoughts: + context.append(f"- Titel: {thought.title}") + context.append(f" Zusammenfassung: {thought.abstract if thought.abstract else 'Keine Zusammenfassung verfügbar'}") + context.append(f" Keywords: {thought.keywords if thought.keywords else 'Keine Keywords verfügbar'}") + context.append("") - # Überprüfe, ob bereits Kategorien existieren - if Category.query.count() == 0: - create_default_categories() - -# Führe die Datenbankinitialisierung beim Starten der App aus -with app.app_context(): - initialize_database() + if any(keyword in user_message.lower() for keyword in ['kategorie', 'category', 'themengebiet', 'bereich']): + # Suche nach Kategorien + categories = Category.query.filter( + db.or_( + Category.name.ilike(f'%{word}%') for word in user_message.split() + if len(word) > 3 + ) + ).limit(5).all() + + if categories: + context.append("Relevante Kategorien:") + for category in categories: + context.append(f"- Name: {category.name}") + context.append(f" Beschreibung: {category.description}") + context.append("") + + if any(keyword in user_message.lower() for keyword in ['mindmap', 'karte', 'visualisierung']): + # Suche nach öffentlichen Mindmaps + mindmap_nodes = MindMapNode.query.filter( + db.and_( + MindMapNode.is_public == True, + db.or_( + MindMapNode.name.ilike(f'%{word}%') for word in user_message.split() + if len(word) > 3 + ) + ) + ).limit(5).all() + + if mindmap_nodes: + context.append("Relevante Mindmap-Knoten:") + for node in mindmap_nodes: + context.append(f"- Name: {node.name}") + context.append(f" Beschreibung: {node.description if node.description else 'Keine Beschreibung verfügbar'}") + if node.category: + context.append(f" Kategorie: {node.category.name}") + context.append("") + + return "\n".join(context) if context else "" @app.route('/search') def search_thoughts_page(): @@ -1178,4 +1432,51 @@ if __name__ == '__main__': with app.app_context(): # Make sure tables exist db.create_all() - app.run(host="0.0.0.0", debug=True) \ No newline at end of file + app.run(host="0.0.0.0", debug=True) + +@app.route('/api/refresh-mindmap') +def refresh_mindmap(): + """ + API-Endpunkt zum Neuladen der Mindmap-Daten, + wenn die Datenbank-Verbindung vorübergehend fehlgeschlagen ist + """ + try: + # Stelle sicher, dass wir Kategorien haben + if Category.query.count() == 0: + create_default_categories() + + # Hole alle Kategorien und Knoten + categories = Category.query.filter_by(parent_id=None).all() + category_tree = [build_category_tree(cat) for cat in categories] + + # Hole alle Mindmap-Knoten + nodes = MindMapNode.query.all() + node_data = [] + + for node in nodes: + node_obj = { + 'id': node.id, + 'name': node.name, + 'description': node.description or '', + 'color_code': node.color_code or '#9F7AEA', + 'thought_count': len(node.thoughts), + 'category_id': node.category_id + } + + # Verbindungen hinzufügen + node_obj['connections'] = [{'target': child.id} for child in node.children] + + node_data.append(node_obj) + + return jsonify({ + 'success': True, + 'categories': category_tree, + 'nodes': node_data + }) + + except Exception as e: + print(f"Fehler beim Neuladen der Mindmap: {str(e)}") + return jsonify({ + 'success': False, + 'error': 'Datenbankverbindung konnte nicht hergestellt werden' + }), 500 \ No newline at end of file diff --git a/database/systades.db b/database/systades.db index 7ceb056..d39f948 100644 Binary files a/database/systades.db and b/database/systades.db differ diff --git a/requirements.txt b/requirements.txt index 7db9aa5..aca56b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ email-validator python-dotenv werkzeug==2.2.3 flask-sqlalchemy==3.0.5 -openai==1.3.0 +openai==1.15.0 requests==2.31.0 flask-cors==4.0.0 gunicorn==21.2.0 diff --git a/run.py b/run.py deleted file mode 100755 index 7657b40..0000000 --- a/run.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -from pathlib import Path -from dotenv import load_dotenv -from init_db import init_database -from app import app - -if __name__ == "__main__": - # Lade .env-Datei explizit - env_path = Path(__file__).parent / ".env" - if env_path.exists(): - print(f"Lade Umgebungsvariablen aus {env_path}") - load_dotenv(dotenv_path=env_path, override=True, force=True) - else: - print("Warnung: .env-Datei nicht gefunden!") - - # Check if CSS file exists, build it if it doesn't - css_file = Path(__file__).parent / "static" / "css" / "main.css" - if not css_file.exists(): - print("CSS file not found. Building with Tailwind...") - try: - from build_css import build_css - build_css() - except Exception as e: - print(f"Warning: Failed to build CSS: {e}") - print("You may need to run 'python build_css.py' manually.") - - # Initialize the database first - init_database() - - # Run the Flask application - app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/static/css/all.min.css b/static/css/all.min.css new file mode 100644 index 0000000..3757245 --- /dev/null +++ b/static/css/all.min.css @@ -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; +} \ No newline at end of file diff --git a/static/css/assistant.css b/static/css/assistant.css index 9c2b3d3..22e9eea 100644 --- a/static/css/assistant.css +++ b/static/css/assistant.css @@ -1,21 +1,25 @@ -/* ChatGPT Assistent Styles */ +/* ChatGPT Assistent Styles - Verbesserte Version */ #chatgpt-assistant { font-family: 'Inter', sans-serif; } #assistant-chat { - transition: max-height 0.3s ease, opacity 0.3s ease; - box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2); + transition: max-height 0.4s cubic-bezier(0.25, 0.1, 0.25, 1), + opacity 0.3s cubic-bezier(0.25, 0.1, 0.25, 1); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.25); border-radius: 0.75rem; overflow: hidden; + max-width: calc(100vw - 2rem); } #assistant-toggle { - transition: transform 0.3s ease; + 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); + transform: scale(1.1) rotate(10deg); } #assistant-history { @@ -40,27 +44,74 @@ 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; } -/* Verbesserter Glassmorphism-Effekt */ +/* Verbesserte Glassmorphism-Effekt */ .glass-morphism { - background: rgba(255, 255, 255, 0.2); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + 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.18); + border: 1px solid rgba(255, 255, 255, 0.2); } .dark .glass-morphism { - background: rgba(15, 23, 42, 0.3); - border: 1px solid rgba(255, 255, 255, 0.05); + 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); } -/* Dunkleres Dark Theme */ +/* Verbesserte Farbpalette für Dark Theme */ .dark { --tw-bg-opacity: 1; background-color: rgba(10, 15, 25, var(--tw-bg-opacity)) !important; @@ -82,6 +133,54 @@ background-color: rgba(23, 33, 64, var(--tw-bg-opacity)) !important; } +/* Typing Indicator Animation Styles */ +.typing-indicator { + display: flex; + align-items: center; +} + +.typing-indicator span { + height: 8px; + width: 8px; + background-color: #888; + border-radius: 50%; + display: inline-block; + margin: 0 2px; + opacity: 0.4; + animation: bounce 1.4s infinite ease-in-out; +} + +.typing-indicator span:nth-child(1) { animation-delay: 0s; } +.typing-indicator span:nth-child(2) { animation-delay: 0.2s; } +.typing-indicator span:nth-child(3) { animation-delay: 0.4s; } + +@keyframes bounce { + 0%, 80%, 100% { transform: translateY(0); } + 40% { transform: translateY(-8px); } +} + +/* Chat Input Fokus-Effekt */ +#assistant-chat input:focus { + border-color: var(--primary-500, #3B82F6); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25); +} + +.dark #assistant-chat input:focus { + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4); +} + +/* Verbesserte Responsive Layouts */ +@media (max-width: 640px) { + #assistant-chat { + width: calc(100vw - 2rem) !important; + } + + #chatgpt-assistant { + right: 1rem; + bottom: 1rem; + } +} + /* Footer immer unten */ html, body { height: 100%; diff --git a/static/css/tailwind.min.css b/static/css/tailwind.min.css new file mode 100644 index 0000000..b8d8f8c --- /dev/null +++ b/static/css/tailwind.min.css @@ -0,0 +1,16 @@ +/* + * Tailwind CSS v3.4.16 + * + * This is a placeholder file. For production, you should: + * 1. Install Tailwind CSS as a PostCSS plugin: https://tailwindcss.com/docs/installation + * 2. Run the Tailwind CLI to compile this file with your custom configuration + * 3. Replace this file with the compiled CSS + * + * The actual file should be generated using: + * npx tailwindcss -i ./src/input.css -o ./static/css/tailwind.min.css --minify + */ + +/* Base Tailwind imports */ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/static/d3-extensions.js b/static/d3-extensions.js index da9c1f3..4b88dd7 100644 --- a/static/d3-extensions.js +++ b/static/d3-extensions.js @@ -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 diff --git a/static/fonts/inter.css b/static/fonts/inter.css new file mode 100644 index 0000000..8da757d --- /dev/null +++ b/static/fonts/inter.css @@ -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'); +} \ No newline at end of file diff --git a/static/fonts/jetbrains-mono.css b/static/fonts/jetbrains-mono.css new file mode 100644 index 0000000..d6b101c --- /dev/null +++ b/static/fonts/jetbrains-mono.css @@ -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'); +} \ No newline at end of file diff --git a/static/js/alpine.min.js b/static/js/alpine.min.js new file mode 100644 index 0000000..e03fbff --- /dev/null +++ b/static/js/alpine.min.js @@ -0,0 +1,12 @@ +/* + * Alpine.js v3.12.3 + * + * This is a placeholder file. For production, you should: + * 1. Download the official Alpine.js file from https://github.com/alpinejs/alpine/releases + * 2. Replace this file with the downloaded version + * + * Alternatively, you can run: + * curl -L https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js > alpine.min.js + */ + +console.error('This is a placeholder for Alpine.js. Please replace with the actual library file.'); \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index 27c8e80..3cdb27c 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -226,4 +226,7 @@ const MindMap = { }; // Globale Export für andere Module -window.MindMap = MindMap; \ No newline at end of file +window.MindMap = MindMap; + +// Export als Modul +export default MindMap; \ No newline at end of file diff --git a/static/js/modules/chatgpt-assistant.js b/static/js/modules/chatgpt-assistant.js index 8f1f9a2..e45e11b 100644 --- a/static/js/modules/chatgpt-assistant.js +++ b/static/js/modules/chatgpt-assistant.js @@ -11,6 +11,62 @@ class ChatGPTAssistant { 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 '
'; + return `

${line}

`; + }).join(''); + } + }; + } + } else { + // Marked ist bereits geladen + this.markdownParser = window.marked; + } } /** @@ -24,7 +80,16 @@ class ChatGPTAssistant { this.setupEventListeners(); // Ersten Willkommensnachricht anzeigen - this.addMessage("assistant", "Frage den KI-Assistenten"); + 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!'); } /** @@ -45,7 +110,7 @@ class ChatGPTAssistant { // Chat-Container const chatContainer = document.createElement('div'); chatContainer.id = 'assistant-chat'; - chatContainer.className = 'bg-white dark:bg-dark-800 rounded-lg shadow-xl overflow-hidden transition-all duration-300 w-80 sm:w-96 max-h-0 opacity-0'; + 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'); @@ -53,7 +118,7 @@ class ChatGPTAssistant { header.innerHTML = `
- KI-Assistent + KI-Assistent (4o-mini)
- - + @@ -673,7 +511,7 @@
-
{{ user.thoughts_count|default('42') }}
+
{{ stats.thought_count if stats and stats.thought_count else 0 }}
Gedanken
@@ -682,7 +520,7 @@
-
{{ user.connections_count|default('128') }}
+
{{ stats.connections_count if stats and stats.connections_count else 0 }}
Verbindungen
@@ -691,7 +529,7 @@
-
{{ user.followers_count|default('567') }}
+
{{ stats.followers_count if stats and stats.followers_count else 0 }}
Follower
@@ -700,7 +538,7 @@
-
{{ user.contributions_count|default('89') }}
+
{{ stats.contributions_count if stats and stats.contributions_count else 0 }}
Beiträge
@@ -709,7 +547,7 @@
-
{{ user.rating|default('4.8') }}
+
{{ stats.rating if stats and stats.rating else '0.0' }}
Bewertung
@@ -731,115 +569,124 @@
- -
-
-
Neuer Gedanke hinzugefügt
-
vor 2 Stunden
-
-
-

Ich habe einen neuen Gedanken zum Thema "Künstliche Intelligenz und Kreativität" hinzugefügt. Wie können KI-Tools uns dabei helfen, kreativer zu denken?

-
-