Compare commits

...

7 Commits

7 changed files with 584 additions and 93 deletions

Binary file not shown.

46
app.py
View File

@@ -46,10 +46,10 @@ app.config['UPLOAD_FOLDER'] = os.getenv('UPLOAD_FOLDER', os.path.join(os.getcwd(
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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"
api_key = "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"
# api_key = os.environ.get("OPENAI_API_KEY")
# if not api_key:
# print("WARNUNG: Kein OPENAI_API_KEY in Umgebungsvariablen gefunden. KI-Funktionalität wird nicht verfügbar sein.")
client = OpenAI(api_key=api_key)
@@ -1544,8 +1544,11 @@ def chat_with_assistant():
})
try:
# OpenAI-Client mit dem API-Key initialisieren
client = OpenAI(api_key=api_key)
# Überprüfen ob OpenAI API-Key konfiguriert ist
if not client.api_key or client.api_key.startswith("sk-dummy"):
if not api_key or 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.'
@@ -1555,12 +1558,13 @@ def chat_with_assistant():
import time
start_time = time.time()
# Erhöhtes Timeout für die API-Anfrage
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
max_tokens=1000,
temperature=0.7,
timeout=20 # 20 Sekunden Timeout
timeout=60 # Erhöht auf 60 Sekunden für bessere Zuverlässigkeit
)
print(f"OpenAI API-Antwortzeit: {time.time() - start_time:.2f} Sekunden")
@@ -1574,12 +1578,30 @@ def chat_with_assistant():
except Exception as e:
import traceback
print(f"Fehler bei der OpenAI-Anfrage: {str(e)}")
print(traceback.format_exc())
error_message = str(e)
stack_trace = traceback.format_exc()
return jsonify({
'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}'
}), 500
print(f"Fehler bei der OpenAI-Anfrage: {error_message}")
print(f"Stack Trace: {stack_trace}")
# Überprüfen auf spezifische Fehlertypen
if "timeout" in error_message.lower():
return jsonify({
'error': 'Die Anfrage hat zu lange gedauert. Bitte versuchen Sie es später erneut.'
}), 504
elif "rate limit" in error_message.lower():
return jsonify({
'error': 'API-Ratelimit erreicht. Bitte warten Sie einen Moment und versuchen Sie es erneut.'
}), 429
elif "internal server error" in error_message.lower() or "500" in error_message:
return jsonify({
'error': 'Es ist ein Serverfehler aufgetreten. Unser Team wurde benachrichtigt.'
}), 500
else:
# Allgemeine Fehlermeldung
return jsonify({
'error': 'Bei der Verarbeitung Ihrer Anfrage ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.'
}), 500
def check_database_query(user_message):
"""

View File

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

After

Width:  |  Height:  |  Size: 583 B

View File

@@ -342,14 +342,27 @@ class ChatGPTAssistant {
messages: this.messages
}),
cache: 'no-cache', // Kein Cache verwenden
credentials: 'same-origin' // Cookies senden
credentials: 'same-origin', // Cookies senden
timeout: 60000 // 60 Sekunden Timeout
});
// Ladeindikator entfernen
this.removeLoadingIndicator();
if (!response.ok) {
throw new Error(`Serverfehler: ${response.status} ${response.statusText}`);
const errorText = await response.text();
let errorMessage;
try {
// Versuche, die Fehlermeldung zu parsen
const errorData = JSON.parse(errorText);
errorMessage = errorData.error || `Serverfehler: ${response.status} ${response.statusText}`;
} catch {
// Bei Parsing-Fehler verwende Standardfehlermeldung
errorMessage = `Serverfehler: ${response.status} ${response.statusText}`;
}
throw new Error(errorMessage);
}
const data = await response.json();
@@ -375,24 +388,45 @@ class ChatGPTAssistant {
// Ladeindikator entfernen, falls noch vorhanden
this.removeLoadingIndicator();
// Spezielle Fehlermeldungen für bestimmte Fehlertypen
const errorMessage = error.message || '';
let userFriendlyMessage = 'Es gab ein Problem mit der Anfrage.';
if (errorMessage.includes('timeout') || errorMessage.includes('Zeitüberschreitung')) {
userFriendlyMessage = 'Die Antwort hat zu lange gedauert. Der Server ist möglicherweise überlastet.';
} else if (errorMessage.includes('500') || errorMessage.includes('Internal Server Error')) {
userFriendlyMessage = 'Ein Serverfehler ist aufgetreten. Wir arbeiten an einer Lösung.';
} else if (errorMessage.includes('429') || errorMessage.includes('rate limit')) {
userFriendlyMessage = 'Die API-Anfragelimits wurden erreicht. Bitte warte einen Moment.';
}
// Fehlermeldung anzeigen oder Wiederholungsversuch starten
if (this.retryCount < this.maxRetries) {
this.retryCount++;
this.addMessage('assistant', 'Es gab ein Problem mit der Anfrage. Ich versuche es erneut...');
this.addMessage('assistant', `${userFriendlyMessage} Ich versuche es erneut... (Versuch ${this.retryCount}/${this.maxRetries})`);
// Kurze Verzögerung vor dem erneuten Versuch
setTimeout(() => {
// Letzte Benutzernachricht aus dem Messages-Array entfernen
const lastUserMessage = this.messages[this.messages.length - 2].content;
this.messages = this.messages.slice(0, -2); // Entferne Benutzernachricht und Fehlermeldung
// Letzte Benutzernachricht speichern für den Wiederholungsversuch
const lastUserMessageIndex = this.messages.findLastIndex(msg => msg.role === 'user');
if (lastUserMessageIndex >= 0) {
const lastUserMessage = this.messages[lastUserMessageIndex].content;
// Erneuter Versand mit gleicher Nachricht
this.inputField.value = lastUserMessage;
this.sendMessage();
}, 1500);
// Kurze Verzögerung vor dem erneuten Versuch mit exponentieller Backoff-Strategie
const retryDelay = 1500 * Math.pow(2, this.retryCount - 1); // 1.5s, 3s, 6s, ...
setTimeout(() => {
// Entferne Fehlermeldung aus dem Messages-Array, behalte aber die Benutzernachricht
this.messages = this.messages.filter(msg =>
!(msg.role === 'assistant' && msg.content.includes('versuche es erneut'))
);
// Erneuter Versand mit gleicher Nachricht
this.inputField.value = lastUserMessage;
this.sendMessage();
}, retryDelay);
}
} else {
// Maximale Anzahl an Wiederholungsversuchen erreicht
this.addMessage('assistant', 'Es tut mir leid, aber es gab ein Problem bei der Verarbeitung deiner Anfrage. Bitte versuche es später noch einmal.');
this.addMessage('assistant', 'Es tut mir leid, aber es gab ein Problem bei der Verarbeitung deiner Anfrage. Bitte versuche es später noch einmal oder kontaktiere den Support, falls das Problem weiterhin besteht.');
this.retryCount = 0; // Zurücksetzen für die nächste Anfrage
}
} finally {

View File

@@ -459,12 +459,21 @@
{% if current_user.avatar %}
<img src="{{ current_user.avatar }}" alt="{{ current_user.username }}" class="w-full h-full object-cover">
{% else %}
{{ current_user.username[0].upper() }}
<svg width="100%" height="100%" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="98" fill="url(#user-gradient)" stroke="#7C3AED" stroke-width="4"/>
<circle cx="100" cy="80" r="36" fill="white"/>
<path d="M100 140C77.9086 140 60 157.909 60 180H140C140 157.909 122.091 140 100 140Z" fill="white"/>
<defs>
<linearGradient id="user-gradient" x1="0" y1="0" x2="200" y2="200" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#8B5CF6"/>
<stop offset="1" stop-color="#3B82F6"/>
</linearGradient>
</defs>
</svg>
{% endif %}
</div>
<span class="text-sm hidden lg:block">{{ current_user.username }}</span>
<i class="fa-solid fa-chevron-down text-xs hidden lg:block transition-transform duration-200"
x-bind:class="open ? 'transform rotate-180' : ''"></i>
<span class="hidden md:block">{{ current_user.username }}</span>
<i class="fas fa-chevron-down text-xs opacity-60 ml-1.5"></i>
</button>
<!-- Dropdown-Menü -->
@@ -730,6 +739,186 @@
<!-- KI-Chat Initialisierung -->
<script>
// ChatGPT-Assistent Klasse
class ChatGPTAssistant {
constructor() {
this.chatContainer = null;
this.messages = [];
this.isOpen = false;
}
init() {
// Chat-Container erstellen, falls noch nicht vorhanden
if (!document.getElementById('chat-assistant-container')) {
this.createChatInterface();
}
// Event-Listener für Chat-Button
const chatButton = document.getElementById('chat-assistant-button');
if (chatButton) {
chatButton.addEventListener('click', () => this.toggleChat());
}
// Event-Listener für Senden-Button
const sendButton = document.getElementById('chat-send-button');
if (sendButton) {
sendButton.addEventListener('click', () => this.sendMessage());
}
// Event-Listener für Eingabefeld (Enter-Taste)
const inputField = document.getElementById('chat-input');
if (inputField) {
inputField.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.sendMessage();
}
});
}
console.log('KI-Assistent erfolgreich initialisiert');
}
createChatInterface() {
// Chat-Button erstellen
const chatButton = document.createElement('button');
chatButton.id = 'chat-assistant-button';
chatButton.className = 'fixed bottom-6 right-6 bg-primary-600 text-white rounded-full p-4 shadow-lg z-50 hover:bg-primary-700 transition-all';
chatButton.innerHTML = '<i class="fas fa-robot text-xl"></i>';
document.body.appendChild(chatButton);
// Chat-Container erstellen
const chatContainer = document.createElement('div');
chatContainer.id = 'chat-assistant-container';
chatContainer.className = 'fixed bottom-24 right-6 w-80 md:w-96 bg-white dark:bg-gray-800 rounded-xl shadow-xl z-50 flex flex-col transition-all duration-300 transform scale-0 origin-bottom-right';
chatContainer.style.height = '500px';
chatContainer.style.maxHeight = '70vh';
// Chat-Header
chatContainer.innerHTML = `
<div class="p-4 border-b dark:border-gray-700 flex justify-between items-center">
<h3 class="font-bold text-gray-800 dark:text-white">Systades Assistent</h3>
<button id="chat-close-button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<i class="fas fa-times"></i>
</button>
</div>
<div id="chat-messages" class="flex-1 overflow-y-auto p-4 space-y-4"></div>
<div class="p-4 border-t dark:border-gray-700">
<div class="flex space-x-2">
<input id="chat-input" type="text" placeholder="Frage stellen..." class="flex-1 px-4 py-2 rounded-lg border dark:border-gray-700 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500">
<button id="chat-send-button" class="bg-primary-600 text-white px-4 py-2 rounded-lg hover:bg-primary-700 transition-all">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
`;
document.body.appendChild(chatContainer);
this.chatContainer = chatContainer;
// Event-Listener für Schließen-Button
const closeButton = document.getElementById('chat-close-button');
if (closeButton) {
closeButton.addEventListener('click', () => this.toggleChat());
}
}
toggleChat() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.chatContainer.classList.remove('scale-0');
this.chatContainer.classList.add('scale-100');
} else {
this.chatContainer.classList.remove('scale-100');
this.chatContainer.classList.add('scale-0');
}
}
async sendMessage() {
const inputField = document.getElementById('chat-input');
const messageText = inputField.value.trim();
if (!messageText) return;
// Benutzer-Nachricht anzeigen
this.addMessage('user', messageText);
inputField.value = '';
// Lade-Indikator anzeigen
this.addMessage('assistant', '...', 'loading-message');
try {
// API-Anfrage senden
const response = await fetch('/api/assistant', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: this.messages.map(msg => ({
role: msg.role,
content: msg.content
}))
})
});
const data = await response.json();
// Lade-Nachricht entfernen
const loadingMessage = document.getElementById('loading-message');
if (loadingMessage) {
loadingMessage.remove();
}
if (data.error) {
this.addMessage('assistant', 'Entschuldigung, es ist ein Fehler aufgetreten: ' + data.error);
} else {
this.addMessage('assistant', data.response);
}
} catch (error) {
console.error('Fehler bei der API-Anfrage:', error);
// Lade-Nachricht entfernen
const loadingMessage = document.getElementById('loading-message');
if (loadingMessage) {
loadingMessage.remove();
}
this.addMessage('assistant', 'Entschuldigung, es ist ein Fehler aufgetreten. Bitte versuche es später erneut.');
}
}
addMessage(role, content, id = null) {
const messagesContainer = document.getElementById('chat-messages');
// Nachricht zum Array hinzufügen (außer Lade-Nachrichten)
if (id !== 'loading-message') {
this.messages.push({ role, content });
}
// Nachricht zum DOM hinzufügen
const messageElement = document.createElement('div');
messageElement.className = `p-3 rounded-lg ${role === 'user' ? 'bg-primary-100 dark:bg-primary-900/30 ml-6' : 'bg-gray-100 dark:bg-gray-700 mr-6'}`;
if (id) {
messageElement.id = id;
}
messageElement.innerHTML = `
<div class="flex items-start">
<div class="w-8 h-8 rounded-full flex items-center justify-center ${role === 'user' ? 'bg-primary-600' : 'bg-gray-600'} text-white mr-2">
<i class="fas ${role === 'user' ? 'fa-user' : 'fa-robot'} text-xs"></i>
</div>
<div class="flex-1 text-sm ${role === 'user' ? 'text-gray-800 dark:text-gray-200' : 'text-gray-700 dark:text-gray-300'}">
${content}
</div>
</div>
`;
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}
// Initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
// dass er auf jeder Seite verfügbar ist, selbst wenn MindMap nicht geladen ist
document.addEventListener('DOMContentLoaded', function() {

View File

@@ -163,7 +163,17 @@
{% if post.author.avatar %}
<img src="{{ post.author.avatar }}" alt="{{ post.author.username }}" class="w-full h-full object-cover">
{% else %}
{{ post.author.username[0].upper() }}
<svg width="100%" height="100%" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="98" fill="url(#post-avatar-gradient)" stroke="#7C3AED" stroke-width="4"/>
<circle cx="100" cy="80" r="36" fill="white"/>
<path d="M100 140C77.9086 140 60 157.909 60 180H140C140 157.909 122.091 140 100 140Z" fill="white"/>
<defs>
<linearGradient id="post-avatar-gradient" x1="0" y1="0" x2="200" y2="200" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#8B5CF6"/>
<stop offset="1" stop-color="#3B82F6"/>
</linearGradient>
</defs>
</svg>
{% endif %}
</div>
@@ -259,7 +269,17 @@
{% if reply.author.avatar %}
<img src="{{ reply.author.avatar }}" alt="{{ reply.author.username }}" class="w-full h-full object-cover">
{% else %}
{{ reply.author.username[0].upper() }}
<svg width="100%" height="100%" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="98" fill="url(#reply-avatar-gradient)" stroke="#7C3AED" stroke-width="4"/>
<circle cx="100" cy="80" r="36" fill="white"/>
<path d="M100 140C77.9086 140 60 157.909 60 180H140C140 157.909 122.091 140 100 140Z" fill="white"/>
<defs>
<linearGradient id="reply-avatar-gradient" x1="0" y1="0" x2="200" y2="200" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#8B5CF6"/>
<stop offset="1" stop-color="#3B82F6"/>
</linearGradient>
</defs>
</svg>
{% endif %}
</div>

View File

@@ -4,17 +4,53 @@
{% block extra_css %}
<style>
/* Gradient Text Styles */
.text-gradient-purple-blue {
background: linear-gradient(135deg, #b38fff, #58a9ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.text-gradient-blue-cyan {
background: linear-gradient(135deg, #58a9ff, #38bdf8);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.text-gradient-cyan-teal {
background: linear-gradient(135deg, #38bdf8, #14b8a6);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.text-gradient-teal-green {
background: linear-gradient(135deg, #14b8a6, #22c55e);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.text-gradient-green-yellow {
background: linear-gradient(135deg, #22c55e, #eab308);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
/* Grundstile für das Profil mit verbessertem Glasmorphismus */
.profile-container {
background: rgba(24, 28, 45, 0.75);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 24px;
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.35);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.35),
0 8px 16px rgba(179, 143, 255, 0.1);
padding: 2rem;
margin-bottom: 2rem;
transition: all 0.3s ease;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.profile-container:hover {
@@ -189,34 +225,62 @@
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: rgba(179, 143, 255, 0.5) rgba(255, 255, 255, 0.05);
padding: 0.5rem;
background: rgba(24, 28, 45, 0.4);
border-radius: 16px 16px 0 0;
gap: 0.5rem;
}
.profile-tab {
padding: 1rem 1.5rem;
padding: 0.75rem 1.25rem;
font-weight: 600;
color: rgba(255, 255, 255, 0.7);
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
white-space: nowrap;
border-radius: 12px;
position: relative;
overflow: hidden;
}
.profile-tab:hover {
color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.05);
background: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.profile-tab::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(179, 143, 255, 0.1), rgba(88, 169, 255, 0.1));
opacity: 0;
transition: opacity 0.3s ease;
}
.profile-tab:hover::before {
opacity: 1;
}
.profile-tab.active {
color: #b38fff;
border-bottom: 3px solid #b38fff;
background: rgba(179, 143, 255, 0.1);
background: rgba(179, 143, 255, 0.15);
box-shadow: 0 4px 15px rgba(179, 143, 255, 0.2);
transform: translateY(-2px);
}
/* Aktivitäten und Beiträge */
.activity-feed {
display: flex;
flex-direction: column;
gap: 1.25rem;
gap: 1.5rem;
padding: 0.5rem;
}
.activity-card {
@@ -224,15 +288,18 @@
border-radius: 20px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1),
0 4px 8px rgba(179, 143, 255, 0.05);
}
.activity-card:hover {
transform: translateY(-3px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25), 0 0 15px rgba(179, 143, 255, 0.15);
transform: translateY(-5px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.25),
0 8px 24px rgba(179, 143, 255, 0.15);
}
.activity-header {
@@ -385,9 +452,17 @@
body:not(.dark) .profile-tabs,
body:not(.dark) .activity-card,
body:not(.dark) .settings-card {
background: rgba(255, 255, 255, 0.92);
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.06),
0 2px 8px rgba(0, 0, 0, 0.03);
}
body:not(.dark) .profile-container {
background: linear-gradient(to bottom right,
rgba(255, 255, 255, 0.98),
rgba(249, 250, 251, 0.96)
);
}
body:not(.dark) .glass-card {
@@ -418,18 +493,85 @@
color: #4a5568;
}
body:not(.dark) .stat-item,
body:not(.dark) .settings-input {
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.05);
/* Light Mode Statistik-Karten */
body:not(.dark) .stat-item {
background: linear-gradient(to bottom right,
rgba(255, 255, 255, 0.98),
rgba(249, 250, 251, 0.95)
);
border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.03),
0 2px 4px rgba(124, 58, 237, 0.02);
}
body:not(.dark) .stat-item:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1),
0 4px 8px rgba(126, 63, 242, 0.1);
border-color: rgba(126, 63, 242, 0.2);
}
body:not(.dark) .stat-icon {
background: rgba(126, 63, 242, 0.1);
}
body:not(.dark) .stat-value {
background: linear-gradient(135deg, #7e3ff2, #3282f6);
background: linear-gradient(135deg, #7c3aed, #3b82f6);
color: transparent;
-webkit-background-clip: text;
background-clip: text;
}
body:not(.dark) .stat-label {
color: #4a5568;
color: #64748b;
}
/* Light Mode Einstellungen */
body:not(.dark) .settings-input {
background: rgba(255, 255, 255, 0.98);
border: 1px solid rgba(0, 0, 0, 0.08);
color: #111827;
font-size: 0.95rem;
padding: 0.75rem 1rem;
transition: all 0.2s ease;
}
body:not(.dark) .settings-input:hover {
border-color: rgba(124, 58, 237, 0.2);
background: white;
}
body:not(.dark) .settings-input:focus {
border-color: rgba(124, 58, 237, 0.3);
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.08);
background: white;
outline: none;
}
body:not(.dark) .settings-input::placeholder {
color: #9ca3af;
font-size: 0.95rem;
}
/* Verbesserte Formulargruppen */
body:not(.dark) .settings-group {
background: rgba(255, 255, 255, 0.5);
padding: 1.5rem;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.04);
transition: all 0.2s ease;
}
body:not(.dark) .settings-group:hover {
background: rgba(255, 255, 255, 0.8);
border-color: rgba(124, 58, 237, 0.1);
}
body:not(.dark) .settings-group .settings-label {
margin-bottom: 0.75rem;
color: #374151;
font-size: 0.95rem;
font-weight: 600;
}
body:not(.dark) .profile-tab {
@@ -447,10 +589,38 @@
background: rgba(126, 63, 242, 0.08);
}
/* Verbesserte Typografie im Light Mode */
body:not(.dark) .activity-title,
body:not(.dark) .settings-card-header,
body:not(.dark) .settings-card-header {
color: #111827;
font-weight: 700;
letter-spacing: -0.025em;
}
body:not(.dark) .settings-label {
color: #1a202c;
color: #374151;
font-weight: 600;
font-size: 0.95rem;
}
body:not(.dark) .settings-card-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
background: linear-gradient(to bottom,
rgba(249, 250, 251, 0.8),
rgba(248, 250, 252, 0.8)
);
}
body:not(.dark) .settings-card-body {
padding: 1.75rem;
}
body:not(.dark) .settings-group {
margin-bottom: 1.75rem;
}
body:not(.dark) .settings-group:last-child {
margin-bottom: 1rem;
}
body:not(.dark) .activity-date {
@@ -461,29 +631,48 @@
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
/* Verbesserte Interaktionselemente im Light Mode */
body:not(.dark) .reaction-button {
color: #4a5568;
background: rgba(0, 0, 0, 0.03);
color: #4b5563;
background: rgba(243, 244, 246, 0.8);
border: 1px solid rgba(0, 0, 0, 0.04);
font-weight: 500;
}
body:not(.dark) .reaction-button:hover {
background: rgba(126, 63, 242, 0.1);
color: #7e3ff2;
background: rgba(124, 58, 237, 0.08);
color: #6d28d9;
border-color: rgba(124, 58, 237, 0.2);
transform: translateY(-1px);
}
body:not(.dark) .reaction-button.active {
background: rgba(126, 63, 242, 0.15);
color: #7e3ff2;
background: rgba(124, 58, 237, 0.12);
color: #6d28d9;
border-color: rgba(124, 58, 237, 0.25);
font-weight: 600;
}
/* Verbesserte Aktionsbuttons im Light Mode */
body:not(.dark) .action-button {
background: rgba(126, 63, 242, 0.1);
color: #7e3ff2;
border: 1px solid rgba(126, 63, 242, 0.2);
background: rgba(124, 58, 237, 0.08);
color: #6d28d9;
border: 1px solid rgba(124, 58, 237, 0.15);
font-weight: 500;
padding: 0.625rem 1rem;
}
body:not(.dark) .action-button:hover {
background: rgba(126, 63, 242, 0.2);
background: rgba(124, 58, 237, 0.12);
border-color: rgba(124, 58, 237, 0.25);
color: #5b21b6;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.1);
}
body:not(.dark) .action-button:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(124, 58, 237, 0.1);
}
/* Verbesserte Styles für Card-Items im Light Mode */
@@ -583,7 +772,23 @@
<!-- User Info Section -->
<div class="profile-header">
<div class="avatar-container">
<img src="{{ user.avatar if user.avatar else url_for('static', filename='img/default-avatar.png') }}" alt="Profilbild" class="avatar">
{% if user.avatar %}
<img src="{{ user.avatar }}" alt="Profilbild" class="avatar">
{% else %}
<div class="avatar default-avatar">
<svg width="100%" height="100%" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="98" fill="url(#gradient)" stroke="#7C3AED" stroke-width="4"/>
<circle cx="100" cy="80" r="36" fill="white"/>
<path d="M100 140C77.9086 140 60 157.909 60 180H140C140 157.909 122.091 140 100 140Z" fill="white"/>
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="200" y2="200" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#8B5CF6"/>
<stop offset="1" stop-color="#3B82F6"/>
</linearGradient>
</defs>
</svg>
</div>
{% endif %}
<div class="avatar-edit">
<i class="fas fa-camera"></i>
</div>
@@ -600,50 +805,60 @@
</div>
<!-- Statistiken -->
<div class="profile-stats">
<div class="profile-stats grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mt-6">
<!-- Gedanken -->
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-lightbulb"></i>
<div class="stat-item bg-opacity-70 backdrop-blur-md rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 hover:transform hover:scale-105">
<div class="stat-icon w-12 h-12 rounded-full flex items-center justify-center bg-gradient-to-br from-purple-500/20 to-blue-500/20 mb-3">
<i class="fas fa-lightbulb text-xl text-gradient-purple-blue"></i>
</div>
<div class="stat-value">{{ stats.thought_count if stats and stats.thought_count else 0 }}</div>
<div class="stat-label">Gedanken</div>
<div class="stat-value text-2xl font-bold mb-1 bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">
{{ stats.thought_count if stats and stats.thought_count else 0 }}
</div>
<div class="stat-label text-sm text-gray-400">Gedanken</div>
</div>
<!-- Verbindungen -->
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-project-diagram"></i>
<div class="stat-item bg-opacity-70 backdrop-blur-md rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 hover:transform hover:scale-105">
<div class="stat-icon w-12 h-12 rounded-full flex items-center justify-center bg-gradient-to-br from-blue-500/20 to-cyan-500/20 mb-3">
<i class="fas fa-project-diagram text-xl text-gradient-blue-cyan"></i>
</div>
<div class="stat-value">{{ stats.connections_count if stats and stats.connections_count else 0 }}</div>
<div class="stat-label">Verbindungen</div>
<div class="stat-value text-2xl font-bold mb-1 bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
{{ stats.connections_count if stats and stats.connections_count else 0 }}
</div>
<div class="stat-label text-sm text-gray-400">Verbindungen</div>
</div>
<!-- Follower -->
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-users"></i>
<div class="stat-item bg-opacity-70 backdrop-blur-md rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 hover:transform hover:scale-105">
<div class="stat-icon w-12 h-12 rounded-full flex items-center justify-center bg-gradient-to-br from-cyan-500/20 to-teal-500/20 mb-3">
<i class="fas fa-users text-xl text-gradient-cyan-teal"></i>
</div>
<div class="stat-value">{{ stats.followers_count if stats and stats.followers_count else 0 }}</div>
<div class="stat-label">Follower</div>
<div class="stat-value text-2xl font-bold mb-1 bg-gradient-to-r from-cyan-400 to-teal-400 bg-clip-text text-transparent">
{{ stats.followers_count if stats and stats.followers_count else 0 }}
</div>
<div class="stat-label text-sm text-gray-400">Follower</div>
</div>
<!-- Beiträge -->
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-comment-dots"></i>
<div class="stat-item bg-opacity-70 backdrop-blur-md rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 hover:transform hover:scale-105">
<div class="stat-icon w-12 h-12 rounded-full flex items-center justify-center bg-gradient-to-br from-teal-500/20 to-green-500/20 mb-3">
<i class="fas fa-comment-dots text-xl text-gradient-teal-green"></i>
</div>
<div class="stat-value">{{ stats.contributions_count if stats and stats.contributions_count else 0 }}</div>
<div class="stat-label">Beiträge</div>
<div class="stat-value text-2xl font-bold mb-1 bg-gradient-to-r from-teal-400 to-green-400 bg-clip-text text-transparent">
{{ stats.contributions_count if stats and stats.contributions_count else 0 }}
</div>
<div class="stat-label text-sm text-gray-400">Beiträge</div>
</div>
<!-- Bewertung -->
<div class="stat-item">
<div class="stat-icon">
<i class="fas fa-star"></i>
<div class="stat-item bg-opacity-70 backdrop-blur-md rounded-xl p-4 flex flex-col items-center justify-center transition-all duration-300 hover:transform hover:scale-105">
<div class="stat-icon w-12 h-12 rounded-full flex items-center justify-center bg-gradient-to-br from-green-500/20 to-yellow-500/20 mb-3">
<i class="fas fa-star text-xl text-gradient-green-yellow"></i>
</div>
<div class="stat-value">{{ stats.rating if stats and stats.rating else '0.0' }}</div>
<div class="stat-label">Bewertung</div>
<div class="stat-value text-2xl font-bold mb-1 bg-gradient-to-r from-green-400 to-yellow-400 bg-clip-text text-transparent">
{{ stats.rating if stats and stats.rating else '0.0' }}
</div>
<div class="stat-label text-sm text-gray-400">Bewertung</div>
</div>
</div>
</div>