1535 lines
54 KiB
Python
1535 lines
54 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
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
|
|
import json
|
|
from enum import Enum
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import StringField, PasswordField, BooleanField, TextAreaField, SelectField, HiddenField
|
|
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
|
|
from functools import wraps
|
|
import secrets
|
|
from sqlalchemy.sql import func
|
|
from openai import OpenAI
|
|
from dotenv import load_dotenv
|
|
from flask_socketio import SocketIO, emit
|
|
from flask_migrate import Migrate
|
|
|
|
# Modelle importieren
|
|
from models import (
|
|
db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating,
|
|
RelationType, Category, UserMindmap, UserMindmapNode, MindmapNote,
|
|
node_thought_association, user_thought_bookmark, node_relationship
|
|
)
|
|
|
|
# Lade .env-Datei
|
|
load_dotenv() # force=True erzwingt die Synchronisierung
|
|
|
|
# Bestimme den absoluten Pfad zur Datenbank
|
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
|
db_path = os.path.join(basedir, 'database', 'systades.db')
|
|
# Stellen Sie sicher, dass das Verzeichnis existiert
|
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
|
|
app = Flask(__name__)
|
|
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key')
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
|
|
app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', os.path.join(os.getcwd(), 'uploads'))
|
|
app.config['WTF_CSRF_ENABLED'] = False
|
|
|
|
# OpenAI API-Konfiguration
|
|
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
|
|
def inject_globals():
|
|
"""Inject global variables into all templates."""
|
|
return {
|
|
'current_year': datetime.now().year
|
|
}
|
|
|
|
# Context-Prozessor für alle Templates
|
|
@app.context_processor
|
|
def inject_current_year():
|
|
return {'current_year': datetime.now().year}
|
|
|
|
# Initialisiere die Datenbank
|
|
db.init_app(app)
|
|
|
|
# Initialisiere den Login-Manager
|
|
login_manager = LoginManager(app)
|
|
login_manager.login_view = 'login'
|
|
|
|
# Erst nach der App-Initialisierung die DB-Check-Funktionen importieren
|
|
from utils.db_check import check_db_connection, initialize_db_if_needed
|
|
|
|
# SocketIO initialisieren
|
|
socketio = SocketIO(app)
|
|
|
|
migrate = Migrate(app, db)
|
|
|
|
def create_default_categories():
|
|
"""Erstellt die Standardkategorien für die Mindmap"""
|
|
# Hauptkategorien
|
|
main_categories = [
|
|
{
|
|
"name": "Philosophie",
|
|
"description": "Philosophisches Denken und Konzepte",
|
|
"color_code": "#9F7AEA",
|
|
"icon": "fa-brain",
|
|
"subcategories": [
|
|
{"name": "Ethik", "description": "Moralische Grundsätze", "icon": "fa-balance-scale"},
|
|
{"name": "Logik", "description": "Gesetze des Denkens", "icon": "fa-project-diagram"},
|
|
{"name": "Erkenntnistheorie", "description": "Natur des Wissens", "icon": "fa-lightbulb"}
|
|
]
|
|
},
|
|
{
|
|
"name": "Wissenschaft",
|
|
"description": "Wissenschaftliche Disziplinen und Forschung",
|
|
"color_code": "#48BB78",
|
|
"icon": "fa-flask",
|
|
"subcategories": [
|
|
{"name": "Physik", "description": "Gesetze der Materie und Energie", "icon": "fa-atom"},
|
|
{"name": "Biologie", "description": "Wissenschaft des Lebens", "icon": "fa-dna"},
|
|
{"name": "Mathematik", "description": "Abstrakte Strukturen", "icon": "fa-calculator"},
|
|
{"name": "Informatik", "description": "Wissenschaft der Datenverarbeitung", "icon": "fa-laptop-code"}
|
|
]
|
|
},
|
|
{
|
|
"name": "Technologie",
|
|
"description": "Technologische Entwicklungen und Anwendungen",
|
|
"color_code": "#ED8936",
|
|
"icon": "fa-microchip",
|
|
"subcategories": [
|
|
{"name": "Künstliche Intelligenz", "description": "Intelligente Maschinen", "icon": "fa-robot"},
|
|
{"name": "Programmierung", "description": "Softwareentwicklung", "icon": "fa-code"},
|
|
{"name": "Elektronik", "description": "Elektronische Systeme", "icon": "fa-memory"}
|
|
]
|
|
},
|
|
{
|
|
"name": "Künste",
|
|
"description": "Kunstformen und kulturelle Ausdrucksweisen",
|
|
"color_code": "#ED64A6",
|
|
"icon": "fa-palette",
|
|
"subcategories": [
|
|
{"name": "Literatur", "description": "Schriftliche Werke", "icon": "fa-book"},
|
|
{"name": "Musik", "description": "Klangkunst", "icon": "fa-music"},
|
|
{"name": "Bildende Kunst", "description": "Visuelle Kunstformen", "icon": "fa-paint-brush"}
|
|
]
|
|
},
|
|
{
|
|
"name": "Psychologie",
|
|
"description": "Menschliches Verhalten und Geist",
|
|
"color_code": "#4299E1",
|
|
"icon": "fa-comments",
|
|
"subcategories": [
|
|
{"name": "Kognition", "description": "Denken und Wahrnehmen", "icon": "fa-brain"},
|
|
{"name": "Emotionen", "description": "Gefühlswelt", "icon": "fa-heart"},
|
|
{"name": "Persönlichkeit", "description": "Charaktereigenschaften", "icon": "fa-user"}
|
|
]
|
|
}
|
|
]
|
|
|
|
# Kategorien erstellen
|
|
for main_cat_data in main_categories:
|
|
# Prüfen, ob die Kategorie bereits existiert
|
|
existing_cat = Category.query.filter_by(name=main_cat_data["name"]).first()
|
|
if existing_cat:
|
|
continue
|
|
|
|
# Hauptkategorie erstellen
|
|
main_category = Category(
|
|
name=main_cat_data["name"],
|
|
description=main_cat_data["description"],
|
|
color_code=main_cat_data["color_code"],
|
|
icon=main_cat_data["icon"]
|
|
)
|
|
db.session.add(main_category)
|
|
db.session.flush() # Um die ID zu generieren
|
|
|
|
# Unterkategorien erstellen
|
|
for sub_cat_data in main_cat_data.get("subcategories", []):
|
|
sub_category = Category(
|
|
name=sub_cat_data["name"],
|
|
description=sub_cat_data["description"],
|
|
color_code=main_cat_data["color_code"],
|
|
icon=sub_cat_data.get("icon", main_cat_data["icon"]),
|
|
parent_id=main_category.id
|
|
)
|
|
db.session.add(sub_category)
|
|
|
|
db.session.commit()
|
|
print("Standard-Kategorien wurden erstellt!")
|
|
|
|
def initialize_database():
|
|
"""Initialisiert die Datenbank mit Grunddaten, falls diese leer ist"""
|
|
try:
|
|
print("Initialisiere die Datenbank...")
|
|
|
|
# Erstelle alle Tabellen
|
|
db.create_all()
|
|
|
|
# Prüfe, ob bereits Benutzer existieren
|
|
if User.query.count() == 0:
|
|
print("Erstelle Admin-Benutzer...")
|
|
admin = User(
|
|
username="admin",
|
|
email="admin@example.com",
|
|
is_admin=True
|
|
)
|
|
admin.set_password("admin123") # In echter Umgebung ein sicheres Passwort verwenden!
|
|
db.session.add(admin)
|
|
|
|
# Prüfe, ob bereits Kategorien existieren
|
|
if Category.query.count() == 0:
|
|
print("Erstelle Standard-Kategorien...")
|
|
create_default_categories()
|
|
|
|
# Stelle sicher, dass die Standard-Knoten für die öffentliche Mindmap existieren
|
|
if MindMapNode.query.count() == 0:
|
|
print("Erstelle Standard-Knoten für die Mindmap...")
|
|
|
|
# Hauptknoten: Wissen
|
|
root_node = MindMapNode(
|
|
name="Wissen",
|
|
description="Zentrale Wissensbasis",
|
|
color_code="#4299E1",
|
|
is_public=True
|
|
)
|
|
db.session.add(root_node)
|
|
db.session.flush() # Um die ID zu generieren
|
|
|
|
# Verwandte Kategorien finden
|
|
philosophy = Category.query.filter_by(name="Philosophie").first()
|
|
science = Category.query.filter_by(name="Wissenschaft").first()
|
|
technology = Category.query.filter_by(name="Technologie").first()
|
|
arts = Category.query.filter_by(name="Künste").first()
|
|
|
|
# Erstelle Hauptthemenknoten
|
|
nodes = [
|
|
MindMapNode(
|
|
name="Philosophie",
|
|
description="Philosophisches Denken",
|
|
color_code="#9F7AEA",
|
|
category=philosophy,
|
|
is_public=True
|
|
),
|
|
MindMapNode(
|
|
name="Wissenschaft",
|
|
description="Wissenschaftliche Erkenntnisse",
|
|
color_code="#48BB78",
|
|
category=science,
|
|
is_public=True
|
|
),
|
|
MindMapNode(
|
|
name="Technologie",
|
|
description="Technologische Entwicklungen",
|
|
color_code="#ED8936",
|
|
category=technology,
|
|
is_public=True
|
|
),
|
|
MindMapNode(
|
|
name="Künste",
|
|
description="Künstlerische Ausdrucksformen",
|
|
color_code="#ED64A6",
|
|
category=arts,
|
|
is_public=True
|
|
)
|
|
]
|
|
|
|
# Füge Knoten zur Datenbank hinzu
|
|
for node in nodes:
|
|
db.session.add(node)
|
|
|
|
db.session.commit()
|
|
|
|
# Nachdem wir die IDs haben, füge die Verbindungen hinzu
|
|
all_nodes = MindMapNode.query.all()
|
|
root = MindMapNode.query.filter_by(name="Wissen").first()
|
|
|
|
if root:
|
|
for node in all_nodes:
|
|
if node.id != root.id:
|
|
root.children.append(node)
|
|
|
|
# Speichere die Änderungen
|
|
db.session.commit()
|
|
|
|
print("Datenbankinitialisierung abgeschlossen.")
|
|
except Exception as e:
|
|
print(f"Fehler bei der Datenbankinitialisierung: {str(e)}")
|
|
db.session.rollback()
|
|
raise
|
|
|
|
# Instead of before_first_request, which is deprecated in newer Flask versions
|
|
# Use a function to initialize the database that will be called during app creation
|
|
def init_app_database(app_instance):
|
|
"""Initialisiert die Datenbank für die Flask-App"""
|
|
with app_instance.app_context():
|
|
# Überprüfe und initialisiere die Datenbank bei Bedarf
|
|
if not initialize_db_if_needed(db, initialize_database):
|
|
print("WARNUNG: Datenbankinitialisierung fehlgeschlagen. Einige Funktionen könnten eingeschränkt sein.")
|
|
|
|
# Call the function to initialize the database
|
|
init_app_database(app)
|
|
|
|
# Benutzerdefinierter Decorator für Admin-Zugriff
|
|
def admin_required(f):
|
|
@wraps(f)
|
|
@login_required
|
|
def decorated_function(*args, **kwargs):
|
|
if not current_user.is_admin:
|
|
flash('Zugriff verweigert. Nur Administratoren dürfen diese Seite aufrufen.', 'error')
|
|
return redirect(url_for('index'))
|
|
return f(*args, **kwargs)
|
|
return decorated_function
|
|
|
|
@login_manager.user_loader
|
|
def load_user(id):
|
|
return User.query.get(int(id))
|
|
|
|
# Routes for authentication
|
|
@app.route('/login', methods=['GET', 'POST'])
|
|
def login():
|
|
if request.method == 'POST':
|
|
username = request.form.get('username')
|
|
password = request.form.get('password')
|
|
|
|
user = User.query.filter_by(username=username).first()
|
|
if user and user.check_password(password):
|
|
login_user(user)
|
|
# Aktualisiere letzten Login-Zeitpunkt
|
|
user.last_login = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
next_page = request.args.get('next')
|
|
return redirect(next_page or url_for('index'))
|
|
flash('Ungültiger Benutzername oder Passwort')
|
|
return render_template('login.html')
|
|
|
|
@app.route('/register', methods=['GET', 'POST'])
|
|
def register():
|
|
if request.method == 'POST':
|
|
username = request.form.get('username')
|
|
email = request.form.get('email')
|
|
password = request.form.get('password')
|
|
|
|
if User.query.filter_by(username=username).first():
|
|
flash('Benutzername existiert bereits')
|
|
return redirect(url_for('register'))
|
|
|
|
if User.query.filter_by(email=email).first():
|
|
flash('E-Mail ist bereits registriert')
|
|
return redirect(url_for('register'))
|
|
|
|
user = User(username=username, email=email)
|
|
user.set_password(password)
|
|
db.session.add(user)
|
|
|
|
# Erstelle eine Standard-Mindmap für den neuen Benutzer
|
|
default_mindmap = UserMindmap(
|
|
name='Meine Mindmap',
|
|
description='Meine persönliche Wissenslandschaft',
|
|
user=user
|
|
)
|
|
db.session.add(default_mindmap)
|
|
db.session.commit()
|
|
|
|
login_user(user)
|
|
flash('Dein Konto wurde erfolgreich erstellt!', 'success')
|
|
return redirect(url_for('index'))
|
|
return render_template('register.html')
|
|
|
|
@app.route('/logout')
|
|
@login_required
|
|
def logout():
|
|
logout_user()
|
|
return redirect(url_for('index'))
|
|
|
|
# Route for the homepage
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
# Route for the mindmap page
|
|
@app.route('/mindmap')
|
|
def mindmap():
|
|
"""Zeigt die öffentliche Mindmap an."""
|
|
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()
|
|
|
|
# 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')
|
|
@login_required
|
|
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(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,
|
|
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'])
|
|
@login_required
|
|
def settings():
|
|
if request.method == 'POST':
|
|
action = request.form.get('action')
|
|
|
|
if action == 'update_profile':
|
|
current_user.bio = request.form.get('bio')
|
|
|
|
# Update avatar if provided
|
|
avatar_url = request.form.get('avatar_url')
|
|
if avatar_url:
|
|
current_user.avatar = avatar_url
|
|
|
|
db.session.commit()
|
|
flash('Profil erfolgreich aktualisiert!', 'success')
|
|
|
|
elif action == 'update_password':
|
|
current_password = request.form.get('current_password')
|
|
new_password = request.form.get('new_password')
|
|
confirm_password = request.form.get('confirm_password')
|
|
|
|
if not current_user.check_password(current_password):
|
|
flash('Aktuelles Passwort ist nicht korrekt', 'error')
|
|
elif new_password != confirm_password:
|
|
flash('Neue Passwörter stimmen nicht überein', 'error')
|
|
else:
|
|
current_user.set_password(new_password)
|
|
db.session.commit()
|
|
flash('Passwort erfolgreich aktualisiert!', 'success')
|
|
|
|
return redirect(url_for('settings'))
|
|
|
|
return render_template('settings.html')
|
|
|
|
# API-Endpunkt für Flash-Nachrichten
|
|
@app.route('/api/get_flash_messages')
|
|
def get_flash_messages():
|
|
"""Liefert aktuelle Flash-Nachrichten für API/JS-Clients."""
|
|
# Hole alle gespeicherten Flash-Nachrichten
|
|
messages = []
|
|
flashed_messages = session.get('_flashes', [])
|
|
|
|
# Formatierung der Nachrichten für die API-Antwort
|
|
for category, message in flashed_messages:
|
|
messages.append({
|
|
'category': category,
|
|
'message': message
|
|
})
|
|
|
|
# Lösche die Nachrichten aus der Session, nachdem sie abgerufen wurden
|
|
session.pop('_flashes', None)
|
|
|
|
return jsonify(messages)
|
|
|
|
# Routes für rechtliche Seiten
|
|
@app.route('/impressum/')
|
|
def impressum():
|
|
return render_template('impressum.html')
|
|
|
|
@app.route('/datenschutz/')
|
|
def datenschutz():
|
|
return render_template('datenschutz.html')
|
|
|
|
@app.route('/agb/')
|
|
def agb():
|
|
return render_template('agb.html')
|
|
|
|
@app.route('/ueber-uns/')
|
|
def ueber_uns():
|
|
return render_template('ueber_uns.html')
|
|
|
|
# Benutzer-Mindmap-Funktionalität
|
|
@app.route('/my-mindmap/<int:mindmap_id>')
|
|
@login_required
|
|
def user_mindmap(mindmap_id):
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck: Nur eigene Mindmaps oder öffentliche Mindmaps
|
|
if mindmap.user_id != current_user.id and mindmap.is_private:
|
|
flash("Du hast keinen Zugriff auf diese Mindmap.", "error")
|
|
return redirect(url_for('profile'))
|
|
|
|
return render_template('user_mindmap.html', mindmap=mindmap)
|
|
|
|
@app.route('/mindmap/create', methods=['GET', 'POST'])
|
|
@login_required
|
|
def create_mindmap():
|
|
if request.method == 'POST':
|
|
name = request.form.get('name')
|
|
description = request.form.get('description')
|
|
is_private = request.form.get('is_private') == 'on'
|
|
|
|
new_mindmap = UserMindmap(
|
|
name=name,
|
|
description=description,
|
|
user_id=current_user.id,
|
|
is_private=is_private
|
|
)
|
|
|
|
db.session.add(new_mindmap)
|
|
db.session.commit()
|
|
|
|
flash('Neue Mindmap erfolgreich erstellt!', 'success')
|
|
return redirect(url_for('user_mindmap', mindmap_id=new_mindmap.id))
|
|
|
|
return render_template('create_mindmap.html')
|
|
|
|
@app.route('/mindmap/<int:mindmap_id>/edit', methods=['GET', 'POST'])
|
|
@login_required
|
|
def edit_mindmap(mindmap_id):
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id:
|
|
flash("Du kannst nur deine eigenen Mindmaps bearbeiten.", "error")
|
|
return redirect(url_for('profile'))
|
|
|
|
if request.method == 'POST':
|
|
mindmap.name = request.form.get('name')
|
|
mindmap.description = request.form.get('description')
|
|
mindmap.is_private = request.form.get('is_private') == 'on'
|
|
|
|
db.session.commit()
|
|
flash('Mindmap erfolgreich aktualisiert!', 'success')
|
|
return redirect(url_for('user_mindmap', mindmap_id=mindmap.id))
|
|
|
|
return render_template('edit_mindmap.html', mindmap=mindmap)
|
|
|
|
@app.route('/mindmap/<int:mindmap_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def delete_mindmap(mindmap_id):
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id:
|
|
flash("Du kannst nur deine eigenen Mindmaps löschen.", "error")
|
|
return redirect(url_for('profile'))
|
|
|
|
db.session.delete(mindmap)
|
|
db.session.commit()
|
|
|
|
flash('Mindmap erfolgreich gelöscht!', 'success')
|
|
return redirect(url_for('profile'))
|
|
|
|
# API-Endpunkte für Mindmap-Daten
|
|
@app.route('/api/mindmap/public')
|
|
def get_public_mindmap():
|
|
"""Liefert die öffentliche Mindmap-Struktur."""
|
|
# Hole alle Kategorien der obersten Ebene
|
|
root_categories = Category.query.filter_by(parent_id=None).all()
|
|
|
|
# Baue Baumstruktur auf
|
|
result = []
|
|
for category in root_categories:
|
|
result.append(build_category_tree(category))
|
|
|
|
return jsonify(result)
|
|
|
|
def build_category_tree(category):
|
|
"""
|
|
Erstellt eine Baumstruktur für eine Kategorie mit all ihren Unterkategorien
|
|
und dazugehörigen Knoten
|
|
|
|
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': [],
|
|
'children': []
|
|
}
|
|
|
|
# Knoten zur Kategorie hinzufügen
|
|
if category.nodes:
|
|
for node in category.nodes:
|
|
category_dict['nodes'].append({
|
|
'id': node.id,
|
|
'name': node.name,
|
|
'description': node.description or '',
|
|
'color_code': node.color_code or '#9F7AEA',
|
|
'thought_count': len(node.thoughts) if hasattr(node, 'thoughts') else 0
|
|
})
|
|
|
|
# Rekursiv Unterkategorien hinzufügen
|
|
if category.children:
|
|
for child in category.children:
|
|
category_dict['children'].append(build_category_tree(child))
|
|
|
|
return category_dict
|
|
|
|
@app.route('/api/mindmap/user/<int:mindmap_id>')
|
|
@login_required
|
|
def get_user_mindmap(mindmap_id):
|
|
"""Liefert die benutzerdefinierte Mindmap-Struktur."""
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id and mindmap.is_private:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
# Hole alle verknüpften Knoten mit Position
|
|
nodes_data = db.session.query(
|
|
MindMapNode, UserMindmapNode
|
|
).join(
|
|
UserMindmapNode, UserMindmapNode.node_id == MindMapNode.id
|
|
).filter(
|
|
UserMindmapNode.user_mindmap_id == mindmap_id
|
|
).all()
|
|
|
|
# Knoten formatieren
|
|
nodes = []
|
|
for node, user_node in nodes_data:
|
|
nodes.append({
|
|
'id': node.id,
|
|
'name': node.name,
|
|
'description': node.description,
|
|
'color_code': node.color_code,
|
|
'x': user_node.x_position,
|
|
'y': user_node.y_position,
|
|
'scale': user_node.scale,
|
|
'thought_count': len(node.thoughts)
|
|
})
|
|
|
|
# Hole Notizen zu dieser Mindmap
|
|
notes = MindmapNote.query.filter_by(
|
|
mindmap_id=mindmap_id,
|
|
user_id=current_user.id
|
|
).all()
|
|
|
|
notes_data = [{
|
|
'id': note.id,
|
|
'content': note.content,
|
|
'node_id': note.node_id,
|
|
'thought_id': note.thought_id,
|
|
'color_code': note.color_code,
|
|
'created_at': note.created_at.isoformat()
|
|
} for note in notes]
|
|
|
|
return jsonify({
|
|
'id': mindmap.id,
|
|
'name': mindmap.name,
|
|
'description': mindmap.description,
|
|
'is_private': mindmap.is_private,
|
|
'nodes': nodes,
|
|
'notes': notes_data
|
|
})
|
|
|
|
@app.route('/api/mindmap/<int:mindmap_id>/add_node', methods=['POST'])
|
|
@login_required
|
|
def add_node_to_mindmap(mindmap_id):
|
|
"""Fügt einen öffentlichen Knoten zur Benutzer-Mindmap hinzu."""
|
|
data = request.json
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
node_id = data.get('node_id')
|
|
x_pos = data.get('x', 0)
|
|
y_pos = data.get('y', 0)
|
|
|
|
node = MindMapNode.query.get_or_404(node_id)
|
|
|
|
# Prüfen, ob der Knoten bereits in der Mindmap existiert
|
|
existing = UserMindmapNode.query.filter_by(
|
|
user_mindmap_id=mindmap_id,
|
|
node_id=node_id
|
|
).first()
|
|
|
|
if existing:
|
|
# Update Position
|
|
existing.x_position = x_pos
|
|
existing.y_position = y_pos
|
|
else:
|
|
# Neuen Knoten hinzufügen
|
|
user_node = UserMindmapNode(
|
|
user_mindmap_id=mindmap_id,
|
|
node_id=node_id,
|
|
x_position=x_pos,
|
|
y_position=y_pos
|
|
)
|
|
db.session.add(user_node)
|
|
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'node_id': node_id,
|
|
'x': x_pos,
|
|
'y': y_pos
|
|
})
|
|
|
|
@app.route('/api/mindmap/<int:mindmap_id>/remove_node/<int:node_id>', methods=['DELETE'])
|
|
@login_required
|
|
def remove_node_from_mindmap(mindmap_id, node_id):
|
|
"""Entfernt einen Knoten aus der Benutzer-Mindmap."""
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
user_node = UserMindmapNode.query.filter_by(
|
|
user_mindmap_id=mindmap_id,
|
|
node_id=node_id
|
|
).first_or_404()
|
|
|
|
db.session.delete(user_node)
|
|
db.session.commit()
|
|
|
|
return jsonify({'success': True})
|
|
|
|
@app.route('/api/mindmap/<int:mindmap_id>/update_node_position', methods=['POST'])
|
|
@login_required
|
|
def update_node_position(mindmap_id):
|
|
"""Aktualisiert die Position eines Knotens in der Benutzer-Mindmap."""
|
|
data = request.json
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
node_id = data.get('node_id')
|
|
x_pos = data.get('x')
|
|
y_pos = data.get('y')
|
|
scale = data.get('scale')
|
|
|
|
user_node = UserMindmapNode.query.filter_by(
|
|
user_mindmap_id=mindmap_id,
|
|
node_id=node_id
|
|
).first_or_404()
|
|
|
|
if x_pos is not None:
|
|
user_node.x_position = x_pos
|
|
if y_pos is not None:
|
|
user_node.y_position = y_pos
|
|
if scale is not None:
|
|
user_node.scale = scale
|
|
|
|
db.session.commit()
|
|
|
|
return jsonify({'success': True})
|
|
|
|
# Notizen-Funktionalität
|
|
@app.route('/api/mindmap/<int:mindmap_id>/notes', methods=['GET'])
|
|
@login_required
|
|
def get_mindmap_notes(mindmap_id):
|
|
"""Liefert alle Notizen zu einer Mindmap."""
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
notes = MindmapNote.query.filter_by(
|
|
mindmap_id=mindmap_id,
|
|
user_id=current_user.id
|
|
).all()
|
|
|
|
return jsonify([{
|
|
'id': note.id,
|
|
'content': note.content,
|
|
'node_id': note.node_id,
|
|
'thought_id': note.thought_id,
|
|
'color_code': note.color_code,
|
|
'created_at': note.created_at.isoformat(),
|
|
'last_modified': note.last_modified.isoformat()
|
|
} for note in notes])
|
|
|
|
@app.route('/api/mindmap/<int:mindmap_id>/notes', methods=['POST'])
|
|
@login_required
|
|
def add_mindmap_note(mindmap_id):
|
|
"""Fügt eine neue Notiz zur Mindmap hinzu."""
|
|
data = request.json
|
|
mindmap = UserMindmap.query.get_or_404(mindmap_id)
|
|
|
|
# Sicherheitscheck
|
|
if mindmap.user_id != current_user.id:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
content = data.get('content')
|
|
node_id = data.get('node_id')
|
|
thought_id = data.get('thought_id')
|
|
color_code = data.get('color_code', "#FFF59D") # Gelber Standard
|
|
|
|
note = MindmapNote(
|
|
user_id=current_user.id,
|
|
mindmap_id=mindmap_id,
|
|
node_id=node_id,
|
|
thought_id=thought_id,
|
|
content=content,
|
|
color_code=color_code
|
|
)
|
|
|
|
db.session.add(note)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'id': note.id,
|
|
'content': note.content,
|
|
'node_id': note.node_id,
|
|
'thought_id': note.thought_id,
|
|
'color_code': note.color_code,
|
|
'created_at': note.created_at.isoformat(),
|
|
'last_modified': note.last_modified.isoformat()
|
|
})
|
|
|
|
@app.route('/api/notes/<int:note_id>', methods=['PUT'])
|
|
@login_required
|
|
def update_note(note_id):
|
|
"""Aktualisiert eine bestehende Notiz."""
|
|
data = request.json
|
|
note = MindmapNote.query.get_or_404(note_id)
|
|
|
|
# Sicherheitscheck
|
|
if note.user_id != current_user.id:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
content = data.get('content')
|
|
color_code = data.get('color_code')
|
|
|
|
if content:
|
|
note.content = content
|
|
if color_code:
|
|
note.color_code = color_code
|
|
|
|
note.last_modified = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'id': note.id,
|
|
'content': note.content,
|
|
'color_code': note.color_code,
|
|
'last_modified': note.last_modified.isoformat()
|
|
})
|
|
|
|
@app.route('/api/notes/<int:note_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_note(note_id):
|
|
"""Löscht eine Notiz."""
|
|
note = MindmapNote.query.get_or_404(note_id)
|
|
|
|
# Sicherheitscheck
|
|
if note.user_id != current_user.id:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
db.session.delete(note)
|
|
db.session.commit()
|
|
|
|
return jsonify({'success': True})
|
|
|
|
# API routes for mindmap and thoughts
|
|
@app.route('/api/mindmap')
|
|
def get_mindmap():
|
|
"""API-Endpunkt zur Bereitstellung der Mindmap-Daten in hierarchischer Form."""
|
|
# Root-Knoten: Knoten ohne Eltern
|
|
root_nodes = MindMapNode.query.\
|
|
outerjoin(node_relationship, MindMapNode.id == node_relationship.c.child_id).\
|
|
filter(node_relationship.c.parent_id == None).all()
|
|
|
|
result = []
|
|
for node in root_nodes:
|
|
node_data = build_node_tree(node)
|
|
result.append(node_data)
|
|
return jsonify({"nodes": result})
|
|
|
|
def build_node_tree(node):
|
|
"""Erzeugt eine hierarchische Darstellung eines Knotens inkl. seiner Kindknoten."""
|
|
thought_count = len(node.thoughts)
|
|
node_data = {
|
|
"id": node.id,
|
|
"name": node.name,
|
|
"description": node.description or "",
|
|
"thought_count": thought_count,
|
|
"children": []
|
|
}
|
|
for child in node.children:
|
|
child_data = build_node_tree(child)
|
|
node_data["children"].append(child_data)
|
|
return node_data
|
|
|
|
@app.route('/api/nodes/<int:node_id>/thoughts')
|
|
def get_node_thoughts(node_id):
|
|
"""Liefert alle Gedanken, die mit einem Knoten verknüpft sind."""
|
|
node = MindMapNode.query.get_or_404(node_id)
|
|
|
|
thoughts = []
|
|
for thought in node.thoughts:
|
|
author = thought.author
|
|
thoughts.append({
|
|
'id': thought.id,
|
|
'title': thought.title,
|
|
'abstract': thought.abstract,
|
|
'content': thought.content[:200] + '...' if len(thought.content) > 200 else thought.content,
|
|
'keywords': thought.keywords,
|
|
'author': {
|
|
'id': author.id,
|
|
'username': author.username
|
|
},
|
|
'created_at': thought.created_at.isoformat(),
|
|
'color_code': thought.color_code,
|
|
'avg_rating': thought.average_rating,
|
|
'bookmarked': current_user.is_authenticated and thought in current_user.bookmarked_thoughts
|
|
})
|
|
|
|
return jsonify(thoughts)
|
|
|
|
@app.route('/api/nodes/<int:node_id>/thoughts', methods=['POST'])
|
|
@login_required
|
|
def add_node_thought(node_id):
|
|
"""Fügt einen neuen Gedanken zu einem Knoten hinzu."""
|
|
data = request.json
|
|
node = MindMapNode.query.get_or_404(node_id)
|
|
|
|
title = data.get('title')
|
|
content = data.get('content')
|
|
abstract = data.get('abstract', '')
|
|
keywords = data.get('keywords', '')
|
|
color_code = data.get('color_code', '#B39DDB') # Standard-Lila
|
|
|
|
# Kategorie des Knotens bestimmen
|
|
category_name = node.category.name if node.category else "Allgemein"
|
|
|
|
thought = Thought(
|
|
title=title,
|
|
content=content,
|
|
abstract=abstract,
|
|
keywords=keywords,
|
|
color_code=color_code,
|
|
branch=category_name,
|
|
user_id=current_user.id,
|
|
source_type='User Input'
|
|
)
|
|
|
|
node.thoughts.append(thought)
|
|
db.session.add(thought)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'id': thought.id,
|
|
'title': thought.title,
|
|
'success': True
|
|
})
|
|
|
|
@app.route('/api/thoughts/<int:thought_id>', methods=['GET'])
|
|
def get_thought(thought_id):
|
|
"""Liefert Details zu einem Gedanken."""
|
|
thought = Thought.query.get_or_404(thought_id)
|
|
|
|
author = thought.author
|
|
is_bookmarked = False
|
|
if current_user.is_authenticated:
|
|
is_bookmarked = thought in current_user.bookmarked_thoughts
|
|
|
|
# Verknüpfte Knoten abrufen
|
|
nodes = [{
|
|
'id': node.id,
|
|
'name': node.name,
|
|
'category': node.category.name if node.category else None
|
|
} for node in thought.nodes]
|
|
|
|
return jsonify({
|
|
'id': thought.id,
|
|
'title': thought.title,
|
|
'content': thought.content,
|
|
'abstract': thought.abstract,
|
|
'keywords': thought.keywords,
|
|
'branch': thought.branch,
|
|
'color_code': thought.color_code,
|
|
'created_at': thought.created_at.isoformat(),
|
|
'author': {
|
|
'id': author.id,
|
|
'username': author.username,
|
|
'avatar': author.avatar
|
|
},
|
|
'avg_rating': thought.average_rating,
|
|
'bookmarked': is_bookmarked,
|
|
'nodes': nodes,
|
|
'source_type': thought.source_type
|
|
})
|
|
|
|
@app.route('/api/thoughts', methods=['POST'])
|
|
@login_required
|
|
def add_thought():
|
|
"""Erstellt einen neuen Gedanken."""
|
|
data = request.json
|
|
|
|
title = data.get('title')
|
|
content = data.get('content')
|
|
abstract = data.get('abstract', '')
|
|
keywords = data.get('keywords', '')
|
|
branch = data.get('branch', 'Allgemein')
|
|
color_code = data.get('color_code', '#B39DDB')
|
|
|
|
thought = Thought(
|
|
title=title,
|
|
content=content,
|
|
abstract=abstract,
|
|
keywords=keywords,
|
|
branch=branch,
|
|
color_code=color_code,
|
|
user_id=current_user.id,
|
|
source_type='User Input'
|
|
)
|
|
|
|
# Knoten-IDs, falls vorhanden
|
|
node_ids = data.get('node_ids', [])
|
|
for node_id in node_ids:
|
|
node = MindMapNode.query.get(node_id)
|
|
if node:
|
|
thought.nodes.append(node)
|
|
|
|
db.session.add(thought)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'id': thought.id,
|
|
'title': thought.title,
|
|
'success': True
|
|
})
|
|
|
|
@app.route('/api/thoughts/<int:thought_id>', methods=['PUT'])
|
|
@login_required
|
|
def update_thought(thought_id):
|
|
"""Aktualisiert einen bestehenden Gedanken."""
|
|
thought = Thought.query.get_or_404(thought_id)
|
|
|
|
# Sicherheitscheck
|
|
if thought.user_id != current_user.id and not current_user.is_admin:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
data = request.json
|
|
|
|
# Aktualisiere Felder, die gesendet wurden
|
|
if 'title' in data:
|
|
thought.title = data['title']
|
|
if 'content' in data:
|
|
thought.content = data['content']
|
|
if 'abstract' in data:
|
|
thought.abstract = data['abstract']
|
|
if 'keywords' in data:
|
|
thought.keywords = data['keywords']
|
|
if 'color_code' in data:
|
|
thought.color_code = data['color_code']
|
|
|
|
thought.last_modified = datetime.utcnow()
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'id': thought.id,
|
|
'success': True
|
|
})
|
|
|
|
@app.route('/api/thoughts/<int:thought_id>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_thought(thought_id):
|
|
"""Löscht einen Gedanken."""
|
|
thought = Thought.query.get_or_404(thought_id)
|
|
|
|
# Sicherheitscheck
|
|
if thought.user_id != current_user.id and not current_user.is_admin:
|
|
return jsonify({'error': 'Nicht autorisiert'}), 403
|
|
|
|
db.session.delete(thought)
|
|
db.session.commit()
|
|
|
|
return jsonify({'success': True})
|
|
|
|
@app.route('/api/thoughts/<int:thought_id>/bookmark', methods=['POST'])
|
|
@login_required
|
|
def bookmark_thought(thought_id):
|
|
"""Fügt einen Gedanken zu den Bookmarks des Benutzers hinzu oder entfernt ihn."""
|
|
thought = Thought.query.get_or_404(thought_id)
|
|
|
|
# Toggle Bookmark-Status
|
|
if thought in current_user.bookmarked_thoughts:
|
|
current_user.bookmarked_thoughts.remove(thought)
|
|
action = 'removed'
|
|
else:
|
|
current_user.bookmarked_thoughts.append(thought)
|
|
action = 'added'
|
|
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'action': action
|
|
})
|
|
|
|
@app.route('/api/categories')
|
|
def get_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():
|
|
"""Speichert die Dark Mode-Einstellung in der Session."""
|
|
data = request.json
|
|
dark_mode = data.get('darkMode', False)
|
|
|
|
session['dark_mode'] = 'true' if dark_mode else 'false'
|
|
session.permanent = True
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'darkMode': dark_mode
|
|
})
|
|
|
|
@app.route('/api/get_dark_mode', methods=['GET'])
|
|
def get_dark_mode():
|
|
"""Liefert die aktuelle Dark Mode-Einstellung."""
|
|
dark_mode = session.get('dark_mode', 'true') # Standard: Dark Mode aktiviert
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'darkMode': dark_mode
|
|
})
|
|
|
|
# Fehlerhandler
|
|
@app.errorhandler(404)
|
|
def page_not_found(e):
|
|
return render_template('errors/404.html'), 404
|
|
|
|
@app.errorhandler(403)
|
|
def forbidden(e):
|
|
return render_template('errors/403.html'), 403
|
|
|
|
@app.errorhandler(500)
|
|
def internal_server_error(e):
|
|
return render_template('errors/500.html'), 500
|
|
|
|
@app.errorhandler(429)
|
|
def too_many_requests(e):
|
|
return render_template('errors/429.html'), 429
|
|
|
|
# OpenAI-Integration für KI-Assistenz
|
|
@app.route('/api/assistant', methods=['POST'])
|
|
def chat_with_assistant():
|
|
"""Chatbot-API mit OpenAI Integration und Datenbankzugriff."""
|
|
data = request.json
|
|
|
|
# Prüfen, ob wir ein einzelnes Prompt oder ein messages-Array haben
|
|
if 'messages' in data:
|
|
messages = data.get('messages', [])
|
|
if not messages:
|
|
return jsonify({
|
|
'error': 'Keine Nachrichten vorhanden.'
|
|
}), 400
|
|
|
|
# Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht
|
|
system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'),
|
|
"Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
|
|
"Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
|
|
"Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
|
|
"Wichtige Funktionen sind:\n"
|
|
"- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
|
|
"- Kategorisierung und thematische Organisation\n"
|
|
"- Interaktive Mindmaps zur Wissensvisualisierung\n"
|
|
"- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
|
|
"- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
|
|
"Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
|
|
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
|
|
"Antworte informativ, sachlich und gut strukturiert auf Deutsch.")
|
|
|
|
# Formatiere Nachrichten für OpenAI API
|
|
api_messages = [{"role": "system", "content": system_message}]
|
|
|
|
# Füge Benutzer- und Assistenten-Nachrichten hinzu
|
|
for msg in messages:
|
|
if msg['role'] in ['user', 'assistant']:
|
|
api_messages.append({"role": msg['role'], "content": msg['content']})
|
|
else:
|
|
# Alte Implementierung für direktes Prompt
|
|
prompt = data.get('prompt', '')
|
|
context = data.get('context', '')
|
|
selected_items = data.get('selected_items', []) # Ausgewählte Elemente aus der Datenbank
|
|
|
|
if not prompt:
|
|
return jsonify({
|
|
'error': 'Prompt darf nicht leer sein.'
|
|
}), 400
|
|
|
|
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
|
|
system_message = (
|
|
"Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
|
|
"Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
|
|
"Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
|
|
"Wichtige Funktionen sind:\n"
|
|
"- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
|
|
"- Kategorisierung und thematische Organisation\n"
|
|
"- Interaktive Mindmaps zur Wissensvisualisierung\n"
|
|
"- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
|
|
"- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
|
|
"Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
|
|
"Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
|
|
"Antworte informativ, sachlich und gut strukturiert auf Deutsch."
|
|
)
|
|
|
|
if context:
|
|
system_message += f"\n\nKontext: {context}"
|
|
|
|
if selected_items:
|
|
system_message += "\n\nAusgewählte Elemente aus der Datenbank:\n"
|
|
for item in selected_items:
|
|
if 'type' in item and 'data' in item:
|
|
if item['type'] == 'thought':
|
|
system_message += f"- Gedanke: {item['data'].get('title', 'Unbekannter Titel')}\n"
|
|
system_message += f" Zusammenfassung: {item['data'].get('abstract', 'Keine Zusammenfassung')}\n"
|
|
system_message += f" Keywords: {item['data'].get('keywords', 'Keine Keywords')}\n"
|
|
elif item['type'] == 'category':
|
|
system_message += f"- Kategorie: {item['data'].get('name', 'Unbekannte Kategorie')}\n"
|
|
system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
|
|
system_message += f" Unterkategorien: {item['data'].get('subcategories', 'Keine Unterkategorien')}\n"
|
|
elif item['type'] == 'mindmap':
|
|
system_message += f"- Mindmap: {item['data'].get('name', 'Unbekannte Mindmap')}\n"
|
|
system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
|
|
system_message += f" Knoten: {item['data'].get('nodes', 'Keine Knoten')}\n"
|
|
|
|
api_messages = [
|
|
{"role": "system", "content": system_message},
|
|
{"role": "user", "content": prompt}
|
|
]
|
|
|
|
# Extrahiere die letzte Benutzernachricht für Datenbankabfragen
|
|
user_message = next((msg['content'] for msg in reversed(api_messages) if msg['role'] == 'user'), '')
|
|
|
|
# Prüfen, ob die Anfrage nach Datenbankinformationen sucht
|
|
db_context = check_database_query(user_message)
|
|
|
|
if db_context:
|
|
# Erweitere den Kontext mit Datenbankinformationen
|
|
api_messages.append({
|
|
"role": "system",
|
|
"content": f"Hier sind relevante Informationen aus der Datenbank:\n\n{db_context}"
|
|
})
|
|
|
|
try:
|
|
# Überprüfen ob OpenAI API-Key konfiguriert ist
|
|
if not client.api_key or client.api_key.startswith("sk-dummy"):
|
|
print("Warnung: OpenAI API-Key ist nicht oder nur als Dummy konfiguriert!")
|
|
return jsonify({
|
|
'error': 'Der OpenAI API-Key ist nicht korrekt konfiguriert. Bitte konfigurieren Sie die Umgebungsvariable OPENAI_API_KEY.'
|
|
}), 500
|
|
|
|
# API-Aufruf mit Timeout
|
|
import time
|
|
start_time = time.time()
|
|
|
|
response = client.chat.completions.create(
|
|
model="gpt-4o-mini",
|
|
messages=api_messages,
|
|
max_tokens=1000, # Erhöht für ausführlichere Antworten und detaillierte Führungen
|
|
temperature=0.7,
|
|
timeout=20 # 20 Sekunden Timeout
|
|
)
|
|
|
|
print(f"OpenAI API-Antwortzeit: {time.time() - start_time:.2f} Sekunden")
|
|
|
|
answer = response.choices[0].message.content
|
|
|
|
# Für das neue Format erwarten wir response statt answer
|
|
return jsonify({
|
|
'response': answer
|
|
})
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
print(f"Fehler bei der OpenAI-Anfrage: {str(e)}")
|
|
print(traceback.format_exc())
|
|
|
|
return jsonify({
|
|
'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}'
|
|
}), 500
|
|
|
|
def check_database_query(user_message):
|
|
"""
|
|
Überprüft, ob die Benutzeranfrage nach Datenbankinformationen sucht und extrahiert
|
|
relevante Daten aus der Datenbank.
|
|
"""
|
|
context = []
|
|
|
|
# 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()
|
|
|
|
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("")
|
|
|
|
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():
|
|
"""Seite zur Gedankensuche anzeigen."""
|
|
return render_template('search.html')
|
|
|
|
@app.route('/my_account')
|
|
def my_account():
|
|
"""Zeigt die persönliche Merkliste an."""
|
|
if not current_user.is_authenticated:
|
|
flash('Bitte melde dich an, um auf deine Merkliste zuzugreifen.', 'warning')
|
|
return redirect(url_for('login'))
|
|
|
|
# Hole die Lesezeichen des Benutzers
|
|
bookmarked_thoughts = current_user.bookmarked_thoughts
|
|
|
|
return render_template('my_account.html', bookmarked_thoughts=bookmarked_thoughts)
|
|
|
|
# Dummy-Route, um 404-Fehler für fehlende Netzwerk-Hintergrundbilder zu vermeiden
|
|
@app.route('/static/network-bg.jpg')
|
|
@app.route('/static/network-bg.svg')
|
|
def dummy_network_bg():
|
|
"""Leere Antwort für die nicht mehr verwendeten Netzwerk-Hintergrundbilder."""
|
|
return '', 200
|
|
|
|
# Route zum expliziten Neu-Laden der Umgebungsvariablen
|
|
@app.route('/admin/reload-env', methods=['POST'])
|
|
@admin_required
|
|
def reload_env():
|
|
"""Lädt die Umgebungsvariablen aus der .env-Datei neu."""
|
|
try:
|
|
# Erzwinge das Neuladen der .env-Datei
|
|
load_dotenv(override=True, force=True)
|
|
|
|
# OpenAI API-Key ist bereits fest kodiert
|
|
# client wurde bereits mit festem API-Key initialisiert
|
|
|
|
# Weitere Umgebungsvariablen hier aktualisieren, falls nötig
|
|
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', app.config['SECRET_KEY'])
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'Umgebungsvariablen wurden erfolgreich neu geladen.'
|
|
})
|
|
except Exception as e:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'Fehler beim Neuladen der Umgebungsvariablen: {str(e)}'
|
|
}), 500
|
|
|
|
# Flask starten
|
|
if __name__ == '__main__':
|
|
with app.app_context():
|
|
# Make sure tables exist
|
|
db.create_all()
|
|
socketio.run(app, debug=True, host='0.0.0.0')
|
|
|
|
@app.route('/api/refresh-mindmap')
|
|
def refresh_mindmap():
|
|
"""
|
|
API-Endpunkt zum Neuladen der Mindmap-Daten,
|
|
wenn die Datenbank-Verbindung vorübergehend fehlgeschlagen ist
|
|
"""
|
|
try:
|
|
# Stelle sicher, dass wir Kategorien haben
|
|
if Category.query.count() == 0:
|
|
create_default_categories()
|
|
|
|
# Hole alle Kategorien und Knoten
|
|
categories = Category.query.filter_by(parent_id=None).all()
|
|
category_tree = [build_category_tree(cat) for cat in categories]
|
|
|
|
# Hole alle Mindmap-Knoten
|
|
nodes = MindMapNode.query.all()
|
|
node_data = []
|
|
|
|
for node in nodes:
|
|
node_obj = {
|
|
'id': node.id,
|
|
'name': node.name,
|
|
'description': node.description or '',
|
|
'color_code': node.color_code or '#9F7AEA',
|
|
'thought_count': len(node.thoughts),
|
|
'category_id': node.category_id
|
|
}
|
|
|
|
# Verbindungen hinzufügen
|
|
node_obj['connections'] = [{'target': child.id} for child in node.children]
|
|
|
|
node_data.append(node_obj)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'categories': category_tree,
|
|
'nodes': node_data
|
|
})
|
|
|
|
except Exception as e:
|
|
print(f"Fehler beim Neuladen der Mindmap: {str(e)}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Datenbankverbindung konnte nicht hergestellt werden'
|
|
}), 500
|
|
|
|
|
|
# Route zur Mindmap HTML-Seite
|
|
@app.route('/mindmap')
|
|
def mindmap_page():
|
|
return render_template('mindmap.html')
|
|
|
|
# Fehlerbehandlung
|
|
@app.errorhandler(404)
|
|
def not_found(e):
|
|
return jsonify({'error': 'Nicht gefunden'}), 404
|
|
|
|
@app.errorhandler(400)
|
|
def bad_request(e):
|
|
return jsonify({'error': 'Fehlerhafte Anfrage'}), 400
|
|
|
|
@app.errorhandler(500)
|
|
def server_error(e):
|
|
return jsonify({'error': 'Serverfehler'}), 500 |