Compare commits
6 Commits
8f0a6d4372
...
48d8463481
| Author | SHA1 | Date | |
|---|---|---|---|
| 48d8463481 | |||
| 08314ec703 | |||
| 0bb7d8d0dc | |||
| 4a28c2c453 | |||
| 66d987857a | |||
| d58aba26c2 |
@@ -12,6 +12,44 @@ Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen
|
||||
- **Datenbank**: SQLite mit SQLAlchemy
|
||||
- **KI-Integration**: OpenAI API für intelligente Assistenz
|
||||
|
||||
## Installation und Verwendung
|
||||
|
||||
### Installation
|
||||
1. Repository klonen
|
||||
2. Virtuelle Umgebung erstellen: `python -m venv venv`
|
||||
3. Virtuelle Umgebung aktivieren:
|
||||
- Windows: `venv\Scripts\activate`
|
||||
- Unix/MacOS: `source venv/bin/activate`
|
||||
4. Abhängigkeiten installieren: `pip install -r requirements.txt`
|
||||
5. Datenbank initialisieren: `python TOOLS.py db:rebuild`
|
||||
6. Admin-Benutzer erstellen: `python TOOLS.py user:admin`
|
||||
7. Server starten: `python TOOLS.py server:run`
|
||||
|
||||
### Standardbenutzer
|
||||
- **Admin-Benutzer**: Username: `admin` / Passwort: `admin`
|
||||
- **Testbenutzer**: Username: `user` / Passwort: `user`
|
||||
|
||||
### Verwaltungswerkzeuge mit TOOLS.py
|
||||
Das Projekt enthält ein zentrales Verwaltungsskript `TOOLS.py`, das verschiedene Hilfsfunktionen bietet:
|
||||
|
||||
#### Datenbankverwaltung
|
||||
- `python TOOLS.py db:fix` - Reparieren der Datenbankstruktur
|
||||
- `python TOOLS.py db:rebuild` - Datenbank neu aufbauen (löscht alle Daten!)
|
||||
- `python TOOLS.py db:test` - Datenbankverbindung und Modelle testen
|
||||
- `python TOOLS.py db:stats` - Datenbankstatistiken anzeigen
|
||||
|
||||
#### Benutzerverwaltung
|
||||
- `python TOOLS.py user:list` - Alle Benutzer anzeigen
|
||||
- `python TOOLS.py user:create -u USERNAME -e EMAIL -p PASSWORD [-a]` - Neuen Benutzer erstellen
|
||||
- `python TOOLS.py user:admin` - Admin-Benutzer erstellen (admin/admin)
|
||||
- `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD` - Benutzerpasswort zurücksetzen
|
||||
- `python TOOLS.py user:delete -u USERNAME` - Benutzer löschen
|
||||
|
||||
#### Serververwaltung
|
||||
- `python TOOLS.py server:run [--host HOST] [--port PORT] [--no-debug]` - Entwicklungsserver starten
|
||||
|
||||
Für detaillierte Hilfe: `python TOOLS.py -h`
|
||||
|
||||
## Roadmap der Überarbeitung
|
||||
|
||||
### Phase 1: Grundlegende Infrastruktur ✅
|
||||
|
||||
125
website/TOOLS.py
Executable file
125
website/TOOLS.py
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
TOOLS.py - Main utility script for the website application.
|
||||
|
||||
This script provides a command-line interface to all utilities
|
||||
for database management, user management, and server administration.
|
||||
|
||||
Usage:
|
||||
python3 TOOLS.py [command] [options]
|
||||
|
||||
Available commands:
|
||||
- db:fix Fix database schema
|
||||
- db:rebuild Completely rebuild the database
|
||||
- db:test Test database connection and models
|
||||
- db:stats Show database statistics
|
||||
|
||||
- user:list List all users
|
||||
- user:create Create a new user
|
||||
- user:admin Create admin user (username: admin, password: admin)
|
||||
- user:reset-pw Reset user password
|
||||
- user:delete Delete a user
|
||||
|
||||
- server:run Run the development server
|
||||
|
||||
Examples:
|
||||
python3 TOOLS.py db:rebuild
|
||||
python3 TOOLS.py user:admin
|
||||
python3 TOOLS.py server:run --port 8080
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from utils import (
|
||||
fix_database_schema, rebuild_database, run_all_tests, print_database_stats,
|
||||
list_users, create_user, reset_password, delete_user, create_admin_user,
|
||||
run_development_server
|
||||
)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Website Administration Tools',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__
|
||||
)
|
||||
|
||||
# Main command argument
|
||||
parser.add_argument('command', help='Command to execute')
|
||||
|
||||
# Additional arguments
|
||||
parser.add_argument('--username', '-u', help='Username for user commands')
|
||||
parser.add_argument('--email', '-e', help='Email for user creation')
|
||||
parser.add_argument('--password', '-p', help='Password for user creation/reset')
|
||||
parser.add_argument('--admin', '-a', action='store_true', help='Make user an admin')
|
||||
parser.add_argument('--host', help='Host for server (default: 127.0.0.1)')
|
||||
parser.add_argument('--port', type=int, help='Port for server (default: 5000)')
|
||||
parser.add_argument('--no-debug', action='store_true', help='Disable debug mode for server')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
# Database commands
|
||||
if args.command == 'db:fix':
|
||||
fix_database_schema()
|
||||
|
||||
elif args.command == 'db:rebuild':
|
||||
print("WARNING: This will delete all data in the database!")
|
||||
confirm = input("Are you sure you want to continue? (y/n): ").lower()
|
||||
if confirm == 'y':
|
||||
rebuild_database()
|
||||
else:
|
||||
print("Aborted.")
|
||||
|
||||
elif args.command == 'db:test':
|
||||
run_all_tests()
|
||||
|
||||
elif args.command == 'db:stats':
|
||||
print_database_stats()
|
||||
|
||||
# User commands
|
||||
elif args.command == 'user:list':
|
||||
list_users()
|
||||
|
||||
elif args.command == 'user:create':
|
||||
if not args.username or not args.email or not args.password:
|
||||
print("Error: Username, email, and password are required.")
|
||||
print("Example: python3 TOOLS.py user:create -u username -e email -p password [-a]")
|
||||
sys.exit(1)
|
||||
create_user(args.username, args.email, args.password, args.admin)
|
||||
|
||||
elif args.command == 'user:admin':
|
||||
create_admin_user()
|
||||
|
||||
elif args.command == 'user:reset-pw':
|
||||
if not args.username or not args.password:
|
||||
print("Error: Username and password are required.")
|
||||
print("Example: python3 TOOLS.py user:reset-pw -u username -p new_password")
|
||||
sys.exit(1)
|
||||
reset_password(args.username, args.password)
|
||||
|
||||
elif args.command == 'user:delete':
|
||||
if not args.username:
|
||||
print("Error: Username is required.")
|
||||
print("Example: python3 TOOLS.py user:delete -u username")
|
||||
sys.exit(1)
|
||||
delete_user(args.username)
|
||||
|
||||
# Server commands
|
||||
elif args.command == 'server:run':
|
||||
host = args.host or '127.0.0.1'
|
||||
port = args.port or 5000
|
||||
debug = not args.no_debug
|
||||
run_development_server(host=host, port=port, debug=debug)
|
||||
|
||||
else:
|
||||
print(f"Unknown command: {args.command}")
|
||||
print("Run 'python3 TOOLS.py -h' for usage information")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
@@ -15,7 +15,7 @@ from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationE
|
||||
from functools import wraps
|
||||
import secrets
|
||||
from sqlalchemy.sql import func
|
||||
import openai
|
||||
from openai import OpenAI
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Modelle importieren
|
||||
@@ -26,7 +26,7 @@ from models import (
|
||||
)
|
||||
|
||||
# Lade .env-Datei
|
||||
load_dotenv(force=True) # force=True erzwingt die Synchronisierung
|
||||
load_dotenv() # force=True erzwingt die Synchronisierung
|
||||
|
||||
# Bestimme den absoluten Pfad zur Datenbank
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -41,7 +41,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
|
||||
|
||||
# OpenAI API-Konfiguration
|
||||
openai.api_key = os.environ.get('OPENAI_API_KEY')
|
||||
client = OpenAI(api_key="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")
|
||||
|
||||
# Context processor für globale Template-Variablen
|
||||
@app.context_processor
|
||||
@@ -931,21 +931,39 @@ def too_many_requests(e):
|
||||
@app.route('/api/assistant', methods=['POST'])
|
||||
def chat_with_assistant():
|
||||
"""Chatbot-API mit OpenAI Integration."""
|
||||
if not openai.api_key:
|
||||
return jsonify({
|
||||
'error': 'OpenAI API-Schlüssel fehlt. Bitte in .env-Datei konfigurieren.'
|
||||
}), 500
|
||||
|
||||
data = request.json
|
||||
prompt = data.get('prompt', '')
|
||||
context = data.get('context', '')
|
||||
|
||||
if not prompt:
|
||||
return jsonify({
|
||||
'error': 'Prompt darf nicht leer sein.'
|
||||
}), 400
|
||||
# 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 hilfreicher Assistent, der Menschen dabei hilft, "
|
||||
"Wissen zu organisieren und zu verknüpfen. Liefere informative, "
|
||||
"sachliche und gut strukturierte Antworten.")
|
||||
|
||||
# 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', '')
|
||||
|
||||
if not prompt:
|
||||
return jsonify({
|
||||
'error': 'Prompt darf nicht leer sein.'
|
||||
}), 400
|
||||
|
||||
try:
|
||||
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
|
||||
system_message = (
|
||||
"Du bist ein hilfreicher Assistent, der Menschen dabei hilft, "
|
||||
@@ -956,20 +974,24 @@ def chat_with_assistant():
|
||||
if context:
|
||||
system_message += f"\n\nKontext: {context}"
|
||||
|
||||
response = openai.ChatCompletion.create(
|
||||
model="gpt-3.5-turbo",
|
||||
messages=[
|
||||
{"role": "system", "content": system_message},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
max_tokens=500,
|
||||
api_messages = [
|
||||
{"role": "system", "content": system_message},
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-3.5-turbo-16k",
|
||||
messages=api_messages,
|
||||
max_tokens=300,
|
||||
temperature=0.7
|
||||
)
|
||||
|
||||
answer = response.choices[0].message.content.strip()
|
||||
answer = response.choices[0].message.content
|
||||
|
||||
# Für das neue Format erwarten wir response statt answer
|
||||
return jsonify({
|
||||
'answer': answer
|
||||
'response': answer
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@@ -1135,8 +1157,8 @@ def reload_env():
|
||||
# Erzwinge das Neuladen der .env-Datei
|
||||
load_dotenv(override=True, force=True)
|
||||
|
||||
# Aktualisiere OpenAI API-Key
|
||||
openai.api_key = os.environ.get('OPENAI_API_KEY')
|
||||
# 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'])
|
||||
|
||||
4
website/cookies.txt
Normal file
4
website/cookies.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# Netscape HTTP Cookie File
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
Binary file not shown.
BIN
website/database/systades.db.backup
Normal file
BIN
website/database/systades.db.backup
Normal file
Binary file not shown.
@@ -37,6 +37,12 @@ const MindMap = {
|
||||
|
||||
console.log('MindMap-Anwendung wird initialisiert...');
|
||||
|
||||
// Initialisiere den ChatGPT-Assistenten
|
||||
const assistant = new ChatGPTAssistant();
|
||||
assistant.init();
|
||||
// Speichere als Teil von MindMap
|
||||
this.assistant = assistant;
|
||||
|
||||
// Seiten-spezifische Initialisierer aufrufen
|
||||
if (this.currentPage && this.pageInitializers[this.currentPage]) {
|
||||
this.pageInitializers[this.currentPage]();
|
||||
|
||||
@@ -195,6 +195,14 @@
|
||||
: '{{ 'nav-link-light-active' if request.endpoint == 'search_thoughts_page' else 'nav-link-light' }}'">
|
||||
<i class="fa-solid fa-search mr-2"></i>Suche
|
||||
</a>
|
||||
<!-- KI-Assistent Button -->
|
||||
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.toggleAssistant(true)"
|
||||
class="nav-link flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-gradient-to-r from-purple-600/80 to-blue-500/80 text-white font-medium px-4 py-2 rounded-xl hover:shadow-lg transition-all duration-300 hover:-translate-y-0.5'
|
||||
: 'bg-gradient-to-r from-purple-500/20 to-blue-400/20 text-gray-800 font-medium px-4 py-2 rounded-xl hover:shadow-md transition-all duration-300 hover:-translate-y-0.5'">
|
||||
<i class="fa-solid fa-robot mr-2"></i>KI-Chat
|
||||
</button>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('profile') }}"
|
||||
class="nav-link flex items-center"
|
||||
@@ -348,6 +356,14 @@
|
||||
: '{{ 'bg-purple-500/10 text-gray-900' if request.endpoint == 'search_thoughts_page' else 'text-gray-700 hover:bg-gray-100 hover:text-gray-900' }}'">
|
||||
<i class="fa-solid fa-search w-5 mr-3"></i>Suche
|
||||
</a>
|
||||
<!-- KI-Button für Mobilmenü -->
|
||||
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.toggleAssistant(true); mobileMenuOpen = false;"
|
||||
class="block w-full text-left py-3.5 px-4 rounded-xl transition-all duration-200 flex items-center"
|
||||
x-bind:class="darkMode
|
||||
? 'bg-gradient-to-r from-purple-600/30 to-blue-500/30 text-white hover:from-purple-600/40 hover:to-blue-500/40'
|
||||
: 'bg-gradient-to-r from-purple-500/10 to-blue-400/10 text-gray-900 hover:from-purple-500/20 hover:to-blue-400/20'">
|
||||
<i class="fa-solid fa-robot w-5 mr-3"></i>KI-Chat
|
||||
</button>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('profile') }}"
|
||||
class="block py-3.5 px-4 rounded-xl transition-all duration-200 flex items-center"
|
||||
@@ -486,5 +502,27 @@
|
||||
|
||||
<!-- Hilfsscripts -->
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
<!-- KI-Chat Initialisierung -->
|
||||
<script type="module">
|
||||
// Importiere und initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
|
||||
// dass er auf jeder Seite verfügbar ist, selbst wenn MindMap nicht geladen ist
|
||||
import ChatGPTAssistant from "{{ url_for('static', filename='js/modules/chatgpt-assistant.js') }}";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Prüfen, ob der Assistent bereits durch MindMap initialisiert wurde
|
||||
if (!window.MindMap || !window.MindMap.assistant) {
|
||||
console.log('KI-Assistent wird direkt initialisiert...');
|
||||
const assistant = new ChatGPTAssistant();
|
||||
assistant.init();
|
||||
|
||||
// Speichere in window.MindMap, falls es existiert, oder erstelle es
|
||||
if (!window.MindMap) {
|
||||
window.MindMap = {};
|
||||
}
|
||||
window.MindMap.assistant = assistant;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -75,6 +75,62 @@
|
||||
height: 100vh;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* Chat-Animationen */
|
||||
.typing-dots span {
|
||||
animation-duration: 1.2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
/* Chat-Nachrichten-Animation */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 10px, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#embedded-chat-messages > div {
|
||||
animation: fadeInUp 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Sanftes Scrollen im Chat */
|
||||
#embedded-chat-messages {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Benutzerdefinierter Scrollbar für den Chat */
|
||||
#embedded-chat-messages::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
#embedded-chat-messages::-webkit-scrollbar-track {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#embedded-chat-messages::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(139, 92, 246, 0.3);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.dark #embedded-chat-messages::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(139, 92, 246, 0.5);
|
||||
}
|
||||
|
||||
/* Hover-Effekt für Quick-Query-Buttons */
|
||||
.quick-query-btn:hover {
|
||||
cursor: pointer;
|
||||
background: linear-gradient(to right, rgba(139, 92, 246, 0.1), rgba(96, 165, 250, 0.1));
|
||||
}
|
||||
|
||||
.dark .quick-query-btn:hover {
|
||||
background: linear-gradient(to right, rgba(139, 92, 246, 0.2), rgba(96, 165, 250, 0.2));
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -342,57 +398,61 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- KI-Assistent mit verbessertem Design -->
|
||||
<div class="glass-morphism p-6 sm:p-8 rounded-3xl transition-all duration-500 hover:-translate-y-3 hover:shadow-xl backdrop-blur-md border border-white/10">
|
||||
<!-- KI-Assistent mit eingebettetem Chat -->
|
||||
<div class="glass-morphism p-6 sm:p-8 rounded-3xl transition-all duration-500 hover:-translate-y-1 hover:shadow-xl backdrop-blur-md border border-white/10">
|
||||
<h3 class="text-xl md:text-2xl font-bold mb-4 flex flex-wrap sm:flex-nowrap items-center text-gray-800 dark:text-white">
|
||||
<div class="w-10 h-10 sm:w-12 sm:h-12 rounded-2xl bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center mr-3 sm:mr-4 shadow-lg transform transition-transform duration-300 hover:scale-110">
|
||||
<i class="fa-solid fa-robot text-white text-base sm:text-lg"></i>
|
||||
</div>
|
||||
<span class="mt-1 sm:mt-0">KI-Assistent</span>
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-5 sm:mb-6 text-sm md:text-base leading-relaxed">
|
||||
Stelle Fragen, lasse dir Themen erklären oder finde neue Verbindungen mit Hilfe
|
||||
unseres KI-Assistenten.
|
||||
</p>
|
||||
|
||||
<!-- Verbesserte Suchleiste -->
|
||||
<div class="mb-5 sm:mb-6">
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-xl blur-sm group-hover:blur-md transition-all duration-300 opacity-70 group-hover:opacity-100"></div>
|
||||
<input type="text" placeholder="Frage den KI-Assistenten"
|
||||
class="relative w-full px-4 sm:px-5 py-3 sm:py-4 rounded-xl border bg-white/70 dark:bg-gray-800/70 border-gray-300 dark:border-gray-700 shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500 text-gray-700 dark:text-gray-200"
|
||||
onkeypress="if(event.keyCode==13) window.MindMap.assistant.toggleAssistant(true);">
|
||||
<button onclick="window.MindMap.assistant.toggleAssistant(true)"
|
||||
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 dark:text-gray-400 hover:text-purple-500 dark:hover:text-purple-400 focus:outline-none transition-all duration-200 w-8 h-8 sm:w-10 sm:h-10 flex items-center justify-center bg-white/50 dark:bg-gray-700/50 rounded-full hover:bg-purple-100 dark:hover:bg-purple-900/30">
|
||||
<i class="fa-solid fa-search text-sm sm:text-base"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-morphism p-4 sm:p-5 rounded-2xl mb-5 sm:mb-6 transition-all duration-300 hover:shadow-md bg-gradient-to-br from-purple-500/5 to-blue-500/5 border border-white/10">
|
||||
<div class="flex items-start">
|
||||
<div class="w-8 h-8 sm:w-10 sm:h-10 rounded-xl bg-gradient-to-r from-purple-500 to-blue-500 flex items-center justify-center flex-shrink-0 mr-3 shadow-md">
|
||||
<i class="fa-solid fa-robot text-white text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs sm:text-sm text-gray-700 dark:text-gray-300">Frage den KI-Assistenten</p>
|
||||
<div class="mt-3 sm:mt-4 flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('mindmap') }}" class="px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
|
||||
Erkunde die Mindmap
|
||||
</a>
|
||||
<a href="{{ url_for('search_thoughts_page') }}" class="px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
|
||||
Themen durchsuchen
|
||||
</a>
|
||||
<a href="#" class="px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
|
||||
Beziehungen erforschen
|
||||
</a>
|
||||
<!-- Eingebettetes Chat-Interface -->
|
||||
<div id="embedded-assistant" class="rounded-xl border border-gray-200/50 dark:border-gray-700/50 overflow-hidden flex flex-col h-[300px]">
|
||||
<!-- Chat Verlauf -->
|
||||
<div id="embedded-chat-messages" class="flex-grow p-4 overflow-y-auto space-y-3 bg-white/70 dark:bg-gray-800/70">
|
||||
<!-- Begrüßungsnachricht -->
|
||||
<div class="flex items-start space-x-2">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fa-solid fa-robot text-white text-xs"></i>
|
||||
</div>
|
||||
<div class="max-w-[85%] bg-purple-100 dark:bg-gray-700 p-3 rounded-xl rounded-tl-none shadow-sm">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-200">Hallo! Ich bin dein KI-Assistent. Wie kann ich dir helfen?</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Eingabe -->
|
||||
<div class="p-3 border-t border-gray-200/70 dark:border-gray-700/70 bg-gray-50/90 dark:bg-gray-800/90">
|
||||
<form id="embedded-chat-form" class="flex items-center space-x-2">
|
||||
<input type="text" id="embedded-chat-input"
|
||||
placeholder="Stelle eine Frage..."
|
||||
class="flex-grow px-4 py-2 rounded-xl border bg-white/90 dark:bg-gray-700/90 border-gray-300 dark:border-gray-600 shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500 text-gray-700 dark:text-gray-200">
|
||||
<button type="submit"
|
||||
class="p-2 rounded-xl bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-md hover:shadow-lg transition-all duration-200 hover:-translate-y-0.5">
|
||||
<i class="fa-solid fa-paper-plane"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="window.MindMap.assistant.toggleAssistant(true)" class="btn-primary w-full text-center rounded-xl py-3 sm:py-3.5 shadow-md hover:shadow-lg transition-all duration-300 hover:-translate-y-1 flex items-center justify-center">
|
||||
<i class="fa-solid fa-comments mr-2"></i>
|
||||
<span>KI-Chat starten</span>
|
||||
|
||||
<!-- Schnelllinks unter dem Chat -->
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<button class="quick-query-btn px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
|
||||
Was ist Systades?
|
||||
</button>
|
||||
<button class="quick-query-btn px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
|
||||
Wie erstelle ich eine Mindmap?
|
||||
</button>
|
||||
<button class="quick-query-btn px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
|
||||
Zeige neueste Gedanken
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Vollständigen KI-Chat öffnen -->
|
||||
<button onclick="window.MindMap.assistant.toggleAssistant(true)" class="mt-4 btn-primary w-full text-center rounded-xl py-2 sm:py-2.5 shadow-md hover:shadow-lg transition-all duration-300 hover:-translate-y-1 flex items-center justify-center">
|
||||
<i class="fa-solid fa-expand mr-2"></i>
|
||||
<span>Chat in Vollansicht öffnen</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -400,3 +460,144 @@
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
<!-- JavaScript für eingebetteten Chat -->
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Warten bis MindMap und der Assistent initialisiert sind
|
||||
const waitForAssistant = setInterval(() => {
|
||||
if (window.MindMap && window.MindMap.assistant) {
|
||||
clearInterval(waitForAssistant);
|
||||
initEmbeddedChat();
|
||||
}
|
||||
}, 200);
|
||||
|
||||
function initEmbeddedChat() {
|
||||
const chatForm = document.getElementById('embedded-chat-form');
|
||||
const chatInput = document.getElementById('embedded-chat-input');
|
||||
const messagesContainer = document.getElementById('embedded-chat-messages');
|
||||
const quickQueryBtns = document.querySelectorAll('.quick-query-btn');
|
||||
|
||||
// Event-Listener für das Chat-Formular
|
||||
chatForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const userMessage = chatInput.value.trim();
|
||||
if (!userMessage) return;
|
||||
|
||||
// Nachricht des Benutzers anzeigen
|
||||
appendMessage('user', userMessage);
|
||||
chatInput.value = '';
|
||||
|
||||
// Anzeigen, dass der Assistent antwortet
|
||||
const typingIndicator = appendTypingIndicator();
|
||||
|
||||
// API-Anfrage an den Assistenten senden
|
||||
sendToAssistant(userMessage)
|
||||
.then(response => {
|
||||
// Entferne Tipp-Indikator
|
||||
typingIndicator.remove();
|
||||
// Zeige Antwort des Assistenten an
|
||||
appendMessage('assistant', response);
|
||||
})
|
||||
.catch(error => {
|
||||
typingIndicator.remove();
|
||||
appendMessage('assistant', 'Es tut mir leid, ich konnte deine Nachricht nicht verarbeiten. Bitte versuche es später noch einmal.');
|
||||
console.error('Fehler bei der Kommunikation mit dem Assistenten:', error);
|
||||
});
|
||||
});
|
||||
|
||||
// Schnellabfragen-Buttons
|
||||
quickQueryBtns.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const query = this.textContent.trim();
|
||||
chatInput.value = query;
|
||||
chatForm.dispatchEvent(new Event('submit'));
|
||||
});
|
||||
});
|
||||
|
||||
// Funktion zum Hinzufügen einer Nachricht zum Chat
|
||||
function appendMessage(sender, message) {
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.className = 'flex items-start space-x-2';
|
||||
|
||||
if (sender === 'user') {
|
||||
messageElement.innerHTML = `
|
||||
<div class="flex-grow"></div>
|
||||
<div class="max-w-[85%] bg-blue-100 dark:bg-blue-900/40 p-3 rounded-xl rounded-tr-none shadow-sm">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-200">${message}</p>
|
||||
</div>
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-indigo-500 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fa-solid fa-user text-white text-xs"></i>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
messageElement.innerHTML = `
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fa-solid fa-robot text-white text-xs"></i>
|
||||
</div>
|
||||
<div class="max-w-[85%] bg-purple-100 dark:bg-gray-700 p-3 rounded-xl rounded-tl-none shadow-sm">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-200">${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
messagesContainer.appendChild(messageElement);
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
}
|
||||
|
||||
// Tipp-Indikator für "Assistent schreibt..."
|
||||
function appendTypingIndicator() {
|
||||
const indicatorElement = document.createElement('div');
|
||||
indicatorElement.className = 'flex items-start space-x-2 typing-indicator';
|
||||
indicatorElement.innerHTML = `
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center flex-shrink-0">
|
||||
<i class="fa-solid fa-robot text-white text-xs"></i>
|
||||
</div>
|
||||
<div class="max-w-[85%] bg-purple-100 dark:bg-gray-700 p-3 rounded-xl rounded-tl-none shadow-sm">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 flex items-center">
|
||||
<span class="mr-1">Tipp</span>
|
||||
<span class="typing-dots flex space-x-1">
|
||||
<span class="w-1.5 h-1.5 bg-gray-500 dark:bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0ms;"></span>
|
||||
<span class="w-1.5 h-1.5 bg-gray-500 dark:bg-gray-400 rounded-full animate-bounce" style="animation-delay: 150ms;"></span>
|
||||
<span class="w-1.5 h-1.5 bg-gray-500 dark:bg-gray-400 rounded-full animate-bounce" style="animation-delay: 300ms;"></span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
messagesContainer.appendChild(indicatorElement);
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
return indicatorElement;
|
||||
}
|
||||
|
||||
// Sende Nachricht an den Assistenten und erhalte Antwort
|
||||
async function sendToAssistant(message) {
|
||||
try {
|
||||
const response = await fetch('/api/assistant', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: [
|
||||
{ role: "system", content: "Du bist ein hilfreicher Assistent für das Wissensnetzwerk Systades." },
|
||||
{ role: "user", content: message }
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Unbekannter Fehler');
|
||||
}
|
||||
|
||||
return data.response || data.answer || 'Ich habe keine Antwort erhalten.';
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der API-Anfrage:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -577,13 +577,38 @@
|
||||
<!-- Mindmap-Container - Jetzt größer -->
|
||||
<div class="glass-card overflow-hidden mb-12">
|
||||
<div id="mindmap-container" class="relative" style="height: 80vh; min-height: 700px;">
|
||||
<!-- Lade-Overlay -->
|
||||
<div class="mindmap-loading absolute inset-0 flex items-center justify-center z-10" style="background: rgba(14, 18, 32, 0.7); backdrop-filter: blur(5px);">
|
||||
<!-- SVG Filters for node effects -->
|
||||
<svg width="0" height="0" style="position: absolute;">
|
||||
<defs>
|
||||
<!-- Glasmorphismus-Effekt für Knoten -->
|
||||
<filter id="glass-effect" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur" />
|
||||
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" result="glow" />
|
||||
<feBlend in="SourceGraphic" in2="glow" mode="normal" />
|
||||
</filter>
|
||||
<!-- Hover-Glow-Effekt -->
|
||||
<filter id="hover-glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="5" result="blur" />
|
||||
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0.5 0 1 0 0 0.5 0 0 1 0 1 0 0 0 18 -7" result="glow" />
|
||||
<feBlend in="SourceGraphic" in2="glow" mode="normal" />
|
||||
</filter>
|
||||
<!-- Ausgewählter-Knoten-Glow-Effekt -->
|
||||
<filter id="selected-glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur" />
|
||||
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0.7 0 1 0 0 0.2 0 0 1 0 1 0 0 0 18 -7" result="glow" />
|
||||
<feBlend in="SourceGraphic" in2="glow" mode="normal" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<!-- Lade-Overlay mit verbesserter Animation und Transition -->
|
||||
<div class="mindmap-loading absolute inset-0 flex items-center justify-center z-10" style="background: rgba(14, 18, 32, 0.8); backdrop-filter: blur(10px); transition: opacity 0.5s ease-in-out;">
|
||||
<div class="text-center">
|
||||
<div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-purple-500 mb-4"></div>
|
||||
<p class="text-white text-lg">Wissenslandschaft wird geladen...</p>
|
||||
<div class="w-64 h-2 bg-gray-700 rounded-full mt-4 overflow-hidden">
|
||||
<div class="loading-progress h-full bg-gradient-to-r from-purple-500 to-blue-500 rounded-full" style="width: 0%"></div>
|
||||
<div class="inline-block animate-spin rounded-full h-16 w-16 border-t-3 border-b-3 border-purple-500 mb-6"></div>
|
||||
<p class="text-white text-xl font-semibold mb-3">Wissenslandschaft wird geladen...</p>
|
||||
<p class="text-gray-300 text-sm mb-4">Daten werden aus der Datenbank abgerufen</p>
|
||||
<div class="w-80 h-3 bg-gray-800 rounded-full mt-2 overflow-hidden">
|
||||
<div class="loading-progress h-full bg-gradient-to-r from-purple-500 via-blue-500 to-purple-500 rounded-full" style="width: 0%; transition: width 0.3s ease-in-out;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -646,6 +671,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal zum Hinzufügen eines neuen Gedanken -->
|
||||
<div id="add-thought-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden" style="backdrop-filter: blur(5px);">
|
||||
<div class="glass-card w-full max-w-md p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-white">Neuen Gedanken hinzufügen</h3>
|
||||
<button class="text-gray-400 hover:text-white" onclick="document.getElementById('add-thought-modal').classList.add('hidden')">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<form id="add-thought-form" method="POST">
|
||||
<input type="hidden" id="thought-node-id" name="node_id">
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-300 mb-2" for="thought-title">Titel</label>
|
||||
<input class="w-full bg-gray-800 text-white border border-gray-700 rounded-lg py-2 px-3" id="thought-title" name="title" placeholder="Titel des Gedankens" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-gray-300 mb-2" for="thought-content">Inhalt</label>
|
||||
<textarea class="w-full bg-gray-800 text-white border border-gray-700 rounded-lg py-2 px-3 h-32" id="thought-content" name="content" placeholder="Beschreibe deinen Gedanken..." required></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button type="button" class="py-2 px-4 bg-gray-700 text-white rounded-lg" onclick="document.getElementById('add-thought-modal').classList.add('hidden')">Abbrechen</button>
|
||||
<button type="submit" class="py-2 px-4 bg-gradient-to-r from-purple-600 to-blue-500 text-white rounded-lg">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -653,17 +705,20 @@
|
||||
<!-- D3.js Library -->
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
|
||||
<!-- Tippy.js für Tooltips -->
|
||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||
<script src="https://unpkg.com/tippy.js@6"></script>
|
||||
|
||||
<!-- Mindmap scripts -->
|
||||
<script src="{{ url_for('static', filename='d3-extensions.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='mindmap.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/modules/mindmap.js') }}"></script>
|
||||
|
||||
<!-- Initialization Script -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Inizialize the mindmap visualization
|
||||
// Mindmap-Container holen
|
||||
const mindmapContainer = document.getElementById('mindmap-container');
|
||||
|
||||
// Options for the visualization
|
||||
// Options für die Visualisierung
|
||||
const options = {
|
||||
width: mindmapContainer.clientWidth,
|
||||
height: mindmapContainer.clientHeight,
|
||||
@@ -675,21 +730,119 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
tooltipEnabled: true,
|
||||
onNodeClick: function(node) {
|
||||
console.log('Node clicked:', node);
|
||||
// Handle node click - could be expanded to display node content
|
||||
// or fetch more information from the server
|
||||
|
||||
// Gedanken zu diesem Knoten laden
|
||||
fetch(`/api/nodes/${node.id}/thoughts`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Netzwerkantwort war nicht ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Gedanken zu diesem Knoten:', data);
|
||||
|
||||
// Gedanken im Seitenbereich anzeigen
|
||||
const thoughtsContainer = document.getElementById('thoughts-container');
|
||||
if (thoughtsContainer) {
|
||||
thoughtsContainer.innerHTML = '';
|
||||
|
||||
if (data.thoughts && data.thoughts.length > 0) {
|
||||
data.thoughts.forEach(thought => {
|
||||
const thoughtElement = document.createElement('div');
|
||||
thoughtElement.className = 'thought-item bg-gray-800 rounded-lg p-4 mb-3';
|
||||
thoughtElement.innerHTML = `
|
||||
<h3 class="text-lg font-semibold text-white">${thought.title}</h3>
|
||||
<p class="text-gray-300 mt-2">${thought.content}</p>
|
||||
<div class="flex justify-between mt-3">
|
||||
<span class="text-sm text-gray-400">${new Date(thought.created_at).toLocaleDateString('de-DE')}</span>
|
||||
<button class="text-blue-400 hover:text-blue-300" data-thought-id="${thought.id}">
|
||||
<i class="fas fa-bookmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
thoughtsContainer.appendChild(thoughtElement);
|
||||
});
|
||||
} else {
|
||||
thoughtsContainer.innerHTML = '<p class="text-gray-400">Keine Gedanken für diesen Knoten vorhanden.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Aktualisiere das Formular zum Hinzufügen von Gedanken
|
||||
document.getElementById('thought-node-id').value = node.id;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden der Gedanken:', error);
|
||||
// Benutzer über den Fehler informieren
|
||||
if (window.mindmap && window.mindmap.showFlash) {
|
||||
window.mindmap.showFlash('Fehler beim Laden der Gedanken', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Create and initialize the visualization
|
||||
// Ladebalken-Animation starten
|
||||
const progressBar = document.querySelector('.loading-progress');
|
||||
let progress = 0;
|
||||
const loadingInterval = setInterval(() => {
|
||||
progress += 5;
|
||||
if (progress > 100) progress = 100;
|
||||
progressBar.style.width = `${progress}%`;
|
||||
if (progress === 100) {
|
||||
clearInterval(loadingInterval);
|
||||
}
|
||||
}, 150);
|
||||
|
||||
// Mindmap erstellen und initialisieren
|
||||
window.mindmap = new MindMapVisualization('#mindmap-container', options);
|
||||
|
||||
// Set up UI event handlers
|
||||
// API-Aufruf, um echte Daten zu laden
|
||||
fetch('/api/mindmap')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Netzwerkantwort war nicht ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Ladebalken auf 100% setzen
|
||||
progressBar.style.width = '100%';
|
||||
|
||||
// Lade-Overlay nach kurzer Verzögerung ausblenden
|
||||
setTimeout(() => {
|
||||
const loadingOverlay = document.querySelector('.mindmap-loading');
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
loadingOverlay.style.display = 'none';
|
||||
}, 500);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
console.log('Mindmap-Daten geladen:', data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden der Mindmap-Daten:', error);
|
||||
// Fehlerbehandlung: Zeige trotzdem die Standard-Mindmap
|
||||
progressBar.style.width = '100%';
|
||||
setTimeout(() => {
|
||||
const loadingOverlay = document.querySelector('.mindmap-loading');
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
loadingOverlay.style.display = 'none';
|
||||
}, 500);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// UI Event-Handler einrichten
|
||||
document.getElementById('zoom-in-btn').addEventListener('click', function() {
|
||||
if (window.mindmap) {
|
||||
const transform = d3.zoomTransform(window.mindmap.svg.node());
|
||||
window.mindmap.svg.call(
|
||||
d3.zoom().transform,
|
||||
d3.zoomIdentity.scale(transform.k * 1.3)
|
||||
d3.zoomIdentity.translate(transform.x, transform.y).scale(transform.k * 1.3)
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -699,7 +852,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const transform = d3.zoomTransform(window.mindmap.svg.node());
|
||||
window.mindmap.svg.call(
|
||||
d3.zoom().transform,
|
||||
d3.zoomIdentity.scale(transform.k / 1.3)
|
||||
d3.zoomIdentity.translate(transform.x, transform.y).scale(transform.k / 1.3)
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -714,14 +867,163 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
document.getElementById('add-thought-btn').addEventListener('click', function() {
|
||||
alert('Diese Funktion steht demnächst zur Verfügung.');
|
||||
if (window.mindmap && window.mindmap.selectedNode) {
|
||||
const nodeId = window.mindmap.selectedNode.id;
|
||||
const modal = document.getElementById('add-thought-modal');
|
||||
if (modal) {
|
||||
// Modal öffnen, wenn vorhanden
|
||||
modal.classList.remove('hidden');
|
||||
// Node-ID in ein verstecktes Feld setzen
|
||||
const nodeIdField = document.getElementById('thought-node-id');
|
||||
if (nodeIdField) nodeIdField.value = nodeId;
|
||||
} else {
|
||||
// Simpler Dialog, wenn kein Modal existiert
|
||||
const thoughtText = prompt('Neuen Gedanken eingeben:');
|
||||
if (thoughtText) {
|
||||
// Gedanken über API hinzufügen
|
||||
fetch(`/api/nodes/${nodeId}/thoughts`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: thoughtText,
|
||||
content: thoughtText
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Erfolgsmeldung anzeigen
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 bg-green-600 text-white p-4 rounded-lg shadow-lg z-50 animate-fade-in';
|
||||
notification.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-check-circle mr-2"></i>
|
||||
<p>Gedanke wurde erfolgreich hinzugefügt!</p>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Notification nach 3 Sekunden ausblenden
|
||||
setTimeout(() => {
|
||||
notification.classList.add('animate-fade-out');
|
||||
setTimeout(() => document.body.removeChild(notification), 500);
|
||||
}, 3000);
|
||||
|
||||
// Aktualisiere den Gedankenzähler am Knoten, falls vorhanden
|
||||
const nodeElement = document.getElementById(`node-${window.mindmap.selectedNode.id}`);
|
||||
const countElement = nodeElement.querySelector('.thought-count');
|
||||
if (countElement) {
|
||||
const currentCount = parseInt(countElement.textContent);
|
||||
countElement.textContent = (currentCount + 1).toString();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Hinzufügen des Gedankens:', error);
|
||||
alert('Fehler beim Hinzufügen des Gedankens.');
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert('Bitte wähle zuerst einen Knoten aus.');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('connect-btn').addEventListener('click', function() {
|
||||
alert('Diese Funktion steht demnächst zur Verfügung.');
|
||||
if (window.mindmap && window.mindmap.selectedNode) {
|
||||
// Speichere den ersten ausgewählten Knoten
|
||||
window.mindmap.sourceNode = window.mindmap.selectedNode;
|
||||
|
||||
// Visuelles Feedback für den Benutzer
|
||||
const selectedCircle = d3.select(`#node-${window.mindmap.selectedNode.id} circle`);
|
||||
selectedCircle.classed('connection-source', true);
|
||||
|
||||
// Benutzerfreundlichere Benachrichtigung mit Statusanzeige
|
||||
const notification = document.createElement('div');
|
||||
notification.id = 'connection-notification';
|
||||
notification.className = 'fixed top-4 right-4 bg-purple-600 text-white p-4 rounded-lg shadow-lg z-50';
|
||||
notification.innerHTML = `
|
||||
<p class="font-bold mb-1">Verbindungsmodus aktiv</p>
|
||||
<p class="text-sm">Wähle einen zweiten Knoten aus, um eine Verbindung herzustellen</p>
|
||||
<button id="cancel-connection" class="mt-2 px-3 py-1 bg-purple-800 rounded hover:bg-purple-900 text-sm">Abbrechen</button>
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Abbrechen-Button-Funktionalität
|
||||
document.getElementById('cancel-connection').addEventListener('click', function() {
|
||||
window.mindmap.connectMode = false;
|
||||
window.mindmap.sourceNode = null;
|
||||
selectedCircle.classed('connection-source', false);
|
||||
document.body.removeChild(notification);
|
||||
});
|
||||
|
||||
// Aktiviere den Verbindungsmodus
|
||||
window.mindmap.connectMode = true;
|
||||
|
||||
// Cursor-Stil ändern, um den Verbindungsmodus anzuzeigen
|
||||
document.getElementById('mindmap-container').style.cursor = 'crosshair';
|
||||
} else {
|
||||
alert('Bitte wähle zuerst einen Knoten aus.');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle window resize
|
||||
// Formular zum Hinzufügen neuer Gedanken behandeln
|
||||
const thoughtForm = document.getElementById('add-thought-form');
|
||||
if (thoughtForm) {
|
||||
thoughtForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const nodeId = document.getElementById('thought-node-id').value;
|
||||
const title = document.getElementById('thought-title').value;
|
||||
const content = document.getElementById('thought-content').value;
|
||||
|
||||
if (!nodeId || !title || !content) {
|
||||
alert('Bitte fülle alle Felder aus.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Gedanken über API hinzufügen
|
||||
fetch(`/api/nodes/${nodeId}/thoughts`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: title,
|
||||
content: content,
|
||||
branch: 'main' // Default-Zweig
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Hinzufügen des Gedankens');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Modal schließen
|
||||
document.getElementById('add-thought-modal').classList.add('hidden');
|
||||
|
||||
// Formular zurücksetzen
|
||||
thoughtForm.reset();
|
||||
|
||||
// Erfolgsmeldung anzeigen
|
||||
alert('Gedanke wurde erfolgreich hinzugefügt!');
|
||||
|
||||
// Optional: Knoten in der Mindmap aktualisieren (z.B. Zähler erhöhen)
|
||||
if (window.mindmap && window.mindmap.selectedNode) {
|
||||
window.mindmap.selectedNode.thought_count += 1;
|
||||
window.mindmap.updateNodeLabels();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Speichern des Gedankens:', error);
|
||||
alert('Fehler beim Speichern des Gedankens. Bitte versuche es erneut.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Fenstergrößen-Änderung behandeln
|
||||
let resizeTimeout;
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(resizeTimeout);
|
||||
@@ -730,16 +1032,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
window.mindmap.width = mindmapContainer.clientWidth;
|
||||
window.mindmap.height = mindmapContainer.clientHeight;
|
||||
window.mindmap.svg
|
||||
.attr('width', window.mindmap.width)
|
||||
.attr('width', '100%')
|
||||
.attr('height', window.mindmap.height)
|
||||
.attr('viewBox', `0 0 ${window.mindmap.width} ${window.mindmap.height}`);
|
||||
|
||||
// Update the center force
|
||||
// Mittelpunkt-Kraft aktualisieren
|
||||
window.mindmap.simulation.force('center',
|
||||
d3.forceCenter(window.mindmap.width / 2, window.mindmap.height / 2)
|
||||
);
|
||||
|
||||
// Restart the simulation
|
||||
// Simulation neu starten
|
||||
window.mindmap.simulation.alpha(0.3).restart();
|
||||
}
|
||||
}, 250);
|
||||
|
||||
34
website/utils/__init__.py
Executable file
34
website/utils/__init__.py
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Utility functions for the website application.
|
||||
This package contains various utilities for database management,
|
||||
user management, and server administration.
|
||||
"""
|
||||
|
||||
from .db_fix import fix_database_schema
|
||||
from .db_rebuild import rebuild_database
|
||||
from .db_test import test_database_connection, test_models, print_database_stats, run_all_tests
|
||||
from .user_manager import list_users, create_user, reset_password, delete_user, create_admin_user
|
||||
from .server import run_development_server
|
||||
|
||||
__all__ = [
|
||||
# Database utilities
|
||||
'fix_database_schema',
|
||||
'rebuild_database',
|
||||
'test_database_connection',
|
||||
'test_models',
|
||||
'print_database_stats',
|
||||
'run_all_tests',
|
||||
|
||||
# User management
|
||||
'list_users',
|
||||
'create_user',
|
||||
'reset_password',
|
||||
'delete_user',
|
||||
'create_admin_user',
|
||||
|
||||
# Server management
|
||||
'run_development_server',
|
||||
]
|
||||
BIN
website/utils/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
website/utils/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
website/utils/__pycache__/db_fix.cpython-311.pyc
Normal file
BIN
website/utils/__pycache__/db_fix.cpython-311.pyc
Normal file
Binary file not shown.
BIN
website/utils/__pycache__/db_rebuild.cpython-311.pyc
Normal file
BIN
website/utils/__pycache__/db_rebuild.cpython-311.pyc
Normal file
Binary file not shown.
BIN
website/utils/__pycache__/db_test.cpython-311.pyc
Normal file
BIN
website/utils/__pycache__/db_test.cpython-311.pyc
Normal file
Binary file not shown.
BIN
website/utils/__pycache__/server.cpython-311.pyc
Normal file
BIN
website/utils/__pycache__/server.cpython-311.pyc
Normal file
Binary file not shown.
BIN
website/utils/__pycache__/user_manager.cpython-311.pyc
Normal file
BIN
website/utils/__pycache__/user_manager.cpython-311.pyc
Normal file
Binary file not shown.
78
website/utils/db_fix.py
Executable file
78
website/utils/db_fix.py
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import importlib.util
|
||||
|
||||
# Add the parent directory to path so we can import the app
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
from app import app, db_path
|
||||
from models import db
|
||||
|
||||
def ensure_db_dir():
|
||||
"""Make sure the database directory exists."""
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
|
||||
def fix_database_schema():
|
||||
"""Fix the database schema by adding missing columns."""
|
||||
with app.app_context():
|
||||
# Ensure directory exists
|
||||
ensure_db_dir()
|
||||
|
||||
# Check if database exists, create tables if needed
|
||||
if not os.path.exists(db_path):
|
||||
print("Database doesn't exist. Creating all tables from scratch...")
|
||||
db.create_all()
|
||||
print("Database tables created successfully!")
|
||||
return
|
||||
|
||||
# Connect to existing database
|
||||
print(f"Connecting to database: {db_path}")
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if User table exists
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user'")
|
||||
if not cursor.fetchone():
|
||||
print("User table doesn't exist. Creating all tables from scratch...")
|
||||
conn.close()
|
||||
db.create_all()
|
||||
print("Database tables created successfully!")
|
||||
return
|
||||
|
||||
# Check existing columns
|
||||
cursor.execute("PRAGMA table_info(user)")
|
||||
columns = cursor.fetchall()
|
||||
column_names = [col[1] for col in columns]
|
||||
print("Existing columns in User table:", column_names)
|
||||
|
||||
# Add missing columns
|
||||
if 'created_at' not in column_names:
|
||||
print("Adding 'created_at' column to User table...")
|
||||
cursor.execute("ALTER TABLE user ADD COLUMN created_at TIMESTAMP")
|
||||
|
||||
if 'last_login' not in column_names:
|
||||
print("Adding 'last_login' column to User table...")
|
||||
cursor.execute("ALTER TABLE user ADD COLUMN last_login TIMESTAMP")
|
||||
|
||||
if 'avatar' not in column_names:
|
||||
print("Adding 'avatar' column to User table...")
|
||||
cursor.execute("ALTER TABLE user ADD COLUMN avatar VARCHAR(200)")
|
||||
|
||||
if 'bio' not in column_names:
|
||||
print("Adding 'bio' column to User table...")
|
||||
cursor.execute("ALTER TABLE user ADD COLUMN bio TEXT")
|
||||
|
||||
# Commit the changes
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("Database schema updated successfully!")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
fix_database_schema()
|
||||
81
website/utils/db_rebuild.py
Executable file
81
website/utils/db_rebuild.py
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import importlib.util
|
||||
|
||||
# Add the parent directory to path so we can import the app
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
from app import app, db_path, create_default_categories
|
||||
from models import db, User, Category
|
||||
|
||||
def rebuild_database():
|
||||
"""Completely rebuilds the database by dropping and recreating all tables."""
|
||||
with app.app_context():
|
||||
print(f"Database path: {db_path}")
|
||||
|
||||
# Back up existing database if it exists
|
||||
if os.path.exists(db_path):
|
||||
backup_path = db_path + '.backup'
|
||||
try:
|
||||
import shutil
|
||||
shutil.copy2(db_path, backup_path)
|
||||
print(f"Backed up existing database to {backup_path}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not create backup - {str(e)}")
|
||||
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
|
||||
# Drop all tables and recreate them
|
||||
print("Dropping all tables...")
|
||||
db.drop_all()
|
||||
|
||||
print("Creating all tables...")
|
||||
db.create_all()
|
||||
|
||||
# Create admin user
|
||||
print("Creating admin user...")
|
||||
admin = User(
|
||||
username='admin',
|
||||
email='admin@example.com',
|
||||
is_admin=True,
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
admin.set_password('admin')
|
||||
db.session.add(admin)
|
||||
|
||||
# Create regular user
|
||||
print("Creating regular user...")
|
||||
user = User(
|
||||
username='user',
|
||||
email='user@example.com',
|
||||
is_admin=False,
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
user.set_password('user')
|
||||
db.session.add(user)
|
||||
|
||||
try:
|
||||
# Commit to generate user IDs
|
||||
db.session.commit()
|
||||
print("Users created successfully!")
|
||||
|
||||
# Create default categories
|
||||
print("Creating default categories...")
|
||||
create_default_categories()
|
||||
|
||||
print("Database rebuild completed successfully!")
|
||||
return True
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Error during database rebuild: {str(e)}")
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
rebuild_database()
|
||||
120
website/utils/db_test.py
Executable file
120
website/utils/db_test.py
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import sqlite3
|
||||
|
||||
# Add the parent directory to path so we can import the app
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
from app import app, db_path
|
||||
from models import db, User, Thought, MindMapNode, Category
|
||||
|
||||
def test_database_connection():
|
||||
"""Test if the database exists and can be connected to."""
|
||||
try:
|
||||
if not os.path.exists(db_path):
|
||||
print(f"Database file does not exist: {db_path}")
|
||||
return False
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("PRAGMA integrity_check")
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if result and result[0] == "ok":
|
||||
print(f"Database integrity check passed: {db_path}")
|
||||
return True
|
||||
else:
|
||||
print(f"Database integrity check failed: {result}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error testing database connection: {e}")
|
||||
return False
|
||||
|
||||
def test_models():
|
||||
"""Test if all models are properly defined and can be queried."""
|
||||
with app.app_context():
|
||||
try:
|
||||
print("\nTesting User model...")
|
||||
user_count = User.query.count()
|
||||
print(f" Found {user_count} users")
|
||||
|
||||
print("\nTesting Category model...")
|
||||
category_count = Category.query.count()
|
||||
print(f" Found {category_count} categories")
|
||||
|
||||
print("\nTesting MindMapNode model...")
|
||||
node_count = MindMapNode.query.count()
|
||||
print(f" Found {node_count} mindmap nodes")
|
||||
|
||||
print("\nTesting Thought model...")
|
||||
thought_count = Thought.query.count()
|
||||
print(f" Found {thought_count} thoughts")
|
||||
|
||||
if user_count == 0:
|
||||
print("\nWARNING: No users found in the database. You might need to create an admin user.")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error testing models: {e}")
|
||||
return False
|
||||
|
||||
def print_database_stats():
|
||||
"""Print database statistics."""
|
||||
with app.app_context():
|
||||
try:
|
||||
stats = []
|
||||
stats.append(("Users", User.query.count()))
|
||||
stats.append(("Categories", Category.query.count()))
|
||||
stats.append(("Mindmap Nodes", MindMapNode.query.count()))
|
||||
stats.append(("Thoughts", Thought.query.count()))
|
||||
|
||||
print("\nDatabase Statistics:")
|
||||
print("-" * 40)
|
||||
for name, count in stats:
|
||||
print(f"{name:<20} : {count}")
|
||||
print("-" * 40)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error generating database statistics: {e}")
|
||||
return False
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all database tests."""
|
||||
success = True
|
||||
|
||||
print("=" * 60)
|
||||
print("STARTING DATABASE TESTS")
|
||||
print("=" * 60)
|
||||
|
||||
# Test database connection
|
||||
print("\n1. Testing database connection...")
|
||||
if not test_database_connection():
|
||||
success = False
|
||||
|
||||
# Test models
|
||||
print("\n2. Testing database models...")
|
||||
if not test_models():
|
||||
success = False
|
||||
|
||||
# Print statistics
|
||||
print("\n3. Database statistics:")
|
||||
if not print_database_stats():
|
||||
success = False
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
if success:
|
||||
print("All database tests completed successfully!")
|
||||
else:
|
||||
print("Some database tests failed. Check the output above for details.")
|
||||
print("=" * 60)
|
||||
|
||||
return success
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_all_tests()
|
||||
34
website/utils/server.py
Executable file
34
website/utils/server.py
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add the parent directory to path so we can import the app
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
from app import app
|
||||
|
||||
def run_development_server(host='127.0.0.1', port=5000, debug=True):
|
||||
"""Run the Flask development server."""
|
||||
try:
|
||||
print(f"Starting development server on http://{host}:{port}")
|
||||
print("Press CTRL+C to stop the server")
|
||||
app.run(host=host, port=port, debug=debug)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error starting development server: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Run the development server')
|
||||
parser.add_argument('--host', default='127.0.0.1', help='Host to bind to')
|
||||
parser.add_argument('--port', type=int, default=5000, help='Port to bind to')
|
||||
parser.add_argument('--debug', action='store_true', default=True, help='Enable debug mode')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
run_development_server(host=args.host, port=args.port, debug=args.debug)
|
||||
159
website/utils/user_manager.py
Executable file
159
website/utils/user_manager.py
Executable file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Add the parent directory to path so we can import the app
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
from app import app
|
||||
from models import db, User
|
||||
|
||||
def list_users():
|
||||
"""List all users in the database."""
|
||||
with app.app_context():
|
||||
try:
|
||||
users = User.query.all()
|
||||
if not users:
|
||||
print("No users found in the database.")
|
||||
return []
|
||||
|
||||
print("Found {} users:".format(len(users)))
|
||||
print("-" * 60)
|
||||
print("{:<5} {:<20} {:<30} {:<10}".format("ID", "Username", "Email", "Admin"))
|
||||
print("-" * 60)
|
||||
|
||||
for user in users:
|
||||
print("{:<5} {:<20} {:<30} {:<10}".format(
|
||||
user.id, user.username, user.email, "Yes" if user.is_admin else "No"
|
||||
))
|
||||
return users
|
||||
except Exception as e:
|
||||
print(f"Error listing users: {e}")
|
||||
return []
|
||||
|
||||
def create_user(username, email, password, is_admin=False):
|
||||
"""Create a new user in the database."""
|
||||
with app.app_context():
|
||||
try:
|
||||
# Check if user already exists
|
||||
existing_user = User.query.filter_by(username=username).first()
|
||||
if existing_user:
|
||||
print(f"User with username '{username}' already exists.")
|
||||
return False
|
||||
|
||||
# Check if email already exists
|
||||
existing_email = User.query.filter_by(email=email).first()
|
||||
if existing_email:
|
||||
print(f"User with email '{email}' already exists.")
|
||||
return False
|
||||
|
||||
# Create new user
|
||||
user = User(
|
||||
username=username,
|
||||
email=email,
|
||||
is_admin=is_admin,
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
user.set_password(password)
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
print(f"User '{username}' created successfully!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Error creating user: {e}")
|
||||
return False
|
||||
|
||||
def reset_password(username, new_password):
|
||||
"""Reset password for a user."""
|
||||
with app.app_context():
|
||||
try:
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if not user:
|
||||
print(f"User '{username}' not found.")
|
||||
return False
|
||||
|
||||
user.set_password(new_password)
|
||||
db.session.commit()
|
||||
|
||||
print(f"Password for user '{username}' reset successfully!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Error resetting password: {e}")
|
||||
return False
|
||||
|
||||
def delete_user(username):
|
||||
"""Delete a user from the database."""
|
||||
with app.app_context():
|
||||
try:
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if not user:
|
||||
print(f"User '{username}' not found.")
|
||||
return False
|
||||
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
|
||||
print(f"User '{username}' deleted successfully!")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"Error deleting user: {e}")
|
||||
return False
|
||||
|
||||
def create_admin_user():
|
||||
"""Create an admin user in the database."""
|
||||
return create_user('admin', 'admin@example.com', 'admin', is_admin=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='User management utility')
|
||||
subparsers = parser.add_subparsers(dest='command', help='Command to execute')
|
||||
|
||||
# List users command
|
||||
list_parser = subparsers.add_parser('list', help='List all users')
|
||||
|
||||
# Create user command
|
||||
create_parser = subparsers.add_parser('create', help='Create a new user')
|
||||
create_parser.add_argument('--username', '-u', required=True, help='Username')
|
||||
create_parser.add_argument('--email', '-e', required=True, help='Email address')
|
||||
create_parser.add_argument('--password', '-p', required=True, help='Password')
|
||||
create_parser.add_argument('--admin', '-a', action='store_true', help='Make user an admin')
|
||||
|
||||
# Reset password command
|
||||
reset_parser = subparsers.add_parser('reset-password', help='Reset a user password')
|
||||
reset_parser.add_argument('--username', '-u', required=True, help='Username')
|
||||
reset_parser.add_argument('--password', '-p', required=True, help='New password')
|
||||
|
||||
# Delete user command
|
||||
delete_parser = subparsers.add_parser('delete', help='Delete a user')
|
||||
delete_parser.add_argument('--username', '-u', required=True, help='Username to delete')
|
||||
|
||||
# Create admin command (shortcut)
|
||||
admin_parser = subparsers.add_parser('create-admin', help='Create the default admin user')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == 'list':
|
||||
list_users()
|
||||
elif args.command == 'create':
|
||||
create_user(args.username, args.email, args.password, args.admin)
|
||||
elif args.command == 'reset-password':
|
||||
reset_password(args.username, args.password)
|
||||
elif args.command == 'delete':
|
||||
delete_user(args.username)
|
||||
elif args.command == 'create-admin':
|
||||
create_admin_user()
|
||||
else:
|
||||
parser.print_help()
|
||||
Reference in New Issue
Block a user