Update OpenAI API key and enhance app functionality: Replace the OpenAI API key in the .env file for improved access. Refactor app.py to include error handling for missing API keys and implement dark mode functionality with session management. Update README.md to reflect the use of Tailwind CSS via CDN and document the Content Security Policy (CSP) adjustments. Enhance mindmap data loading with a new API endpoint for refreshing data, ensuring better user experience during database connection issues. Update styles and templates for improved UI consistency and responsiveness.
This commit is contained in:
27
static/css/all.min.css
vendored
Normal file
27
static/css/all.min.css
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Font Awesome 6.4.0
|
||||
*
|
||||
* This is a placeholder file. For production, you should:
|
||||
* 1. Download Font Awesome from https://fontawesome.com/download
|
||||
* 2. Extract the downloaded package
|
||||
* 3. Copy the 'css/all.min.css' file to this location
|
||||
* 4. Copy the 'webfonts' folder to '/static/webfonts/'
|
||||
*
|
||||
* Alternatively, you can install via npm and copy the files:
|
||||
* npm install @fortawesome/fontawesome-free
|
||||
* cp -r node_modules/@fortawesome/fontawesome-free/css/all.min.css static/css/
|
||||
* cp -r node_modules/@fortawesome/fontawesome-free/webfonts/ static/
|
||||
*/
|
||||
|
||||
/* Placeholder styles for common Font Awesome icons */
|
||||
.fa, .fas, .far, .fab {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Warning message */
|
||||
body::before {
|
||||
content: "Font Awesome CSS placeholder. Please replace with the actual file.";
|
||||
display: none;
|
||||
}
|
||||
@@ -1,21 +1,25 @@
|
||||
/* ChatGPT Assistent Styles */
|
||||
/* ChatGPT Assistent Styles - Verbesserte Version */
|
||||
#chatgpt-assistant {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
#assistant-chat {
|
||||
transition: max-height 0.3s ease, opacity 0.3s ease;
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2);
|
||||
transition: max-height 0.4s cubic-bezier(0.25, 0.1, 0.25, 1),
|
||||
opacity 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
max-width: calc(100vw - 2rem);
|
||||
}
|
||||
|
||||
#assistant-toggle {
|
||||
transition: transform 0.3s ease;
|
||||
transition: transform 0.3s ease, background-color 0.2s ease;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
#assistant-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
transform: scale(1.1) rotate(10deg);
|
||||
}
|
||||
|
||||
#assistant-history {
|
||||
@@ -40,27 +44,74 @@
|
||||
background-color: rgba(156, 163, 175, 0.3);
|
||||
}
|
||||
|
||||
/* Verbesserte Message-Bubbles mit Schatten und Animation */
|
||||
#assistant-history .flex > div {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
animation: messageAppear 0.3s ease-out forwards;
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
@keyframes messageAppear {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Verzögerte Animation für Messages */
|
||||
#assistant-history .flex:nth-child(1) > div { animation-delay: 0.05s; }
|
||||
#assistant-history .flex:nth-child(2) > div { animation-delay: 0.1s; }
|
||||
#assistant-history .flex:nth-child(3) > div { animation-delay: 0.15s; }
|
||||
#assistant-history .flex:nth-child(4) > div { animation-delay: 0.2s; }
|
||||
#assistant-history .flex:nth-child(5) > div { animation-delay: 0.25s; }
|
||||
|
||||
/* Vorschläge styling */
|
||||
#assistant-suggestions {
|
||||
padding: 0.5rem 0.75rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.suggestion-pill {
|
||||
animation: pillAppear 0.4s ease forwards;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@keyframes pillAppear {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Styling für verschiedene Verzögerungen bei Vorschlägen */
|
||||
#assistant-suggestions button:nth-child(1) { animation-delay: 0.1s; }
|
||||
#assistant-suggestions button:nth-child(2) { animation-delay: 0.2s; }
|
||||
#assistant-suggestions button:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
/* Mach Platz für Notifications, damit sie nicht mit dem Assistenten überlappen */
|
||||
.notification-area {
|
||||
bottom: 5rem;
|
||||
}
|
||||
|
||||
/* Verbesserter Glassmorphism-Effekt */
|
||||
/* Verbesserte Glassmorphism-Effekt */
|
||||
.glass-morphism {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.dark .glass-morphism {
|
||||
background: rgba(15, 23, 42, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Dunkleres Dark Theme */
|
||||
/* Verbesserte Farbpalette für Dark Theme */
|
||||
.dark {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(10, 15, 25, var(--tw-bg-opacity)) !important;
|
||||
@@ -82,6 +133,54 @@
|
||||
background-color: rgba(23, 33, 64, var(--tw-bg-opacity)) !important;
|
||||
}
|
||||
|
||||
/* Typing Indicator Animation Styles */
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.typing-indicator span {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: #888;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
opacity: 0.4;
|
||||
animation: bounce 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(1) { animation-delay: 0s; }
|
||||
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
|
||||
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% { transform: translateY(0); }
|
||||
40% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
/* Chat Input Fokus-Effekt */
|
||||
#assistant-chat input:focus {
|
||||
border-color: var(--primary-500, #3B82F6);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
|
||||
}
|
||||
|
||||
.dark #assistant-chat input:focus {
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
|
||||
/* Verbesserte Responsive Layouts */
|
||||
@media (max-width: 640px) {
|
||||
#assistant-chat {
|
||||
width: calc(100vw - 2rem) !important;
|
||||
}
|
||||
|
||||
#chatgpt-assistant {
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer immer unten */
|
||||
html, body {
|
||||
height: 100%;
|
||||
|
||||
16
static/css/tailwind.min.css
vendored
Normal file
16
static/css/tailwind.min.css
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Tailwind CSS v3.4.16
|
||||
*
|
||||
* This is a placeholder file. For production, you should:
|
||||
* 1. Install Tailwind CSS as a PostCSS plugin: https://tailwindcss.com/docs/installation
|
||||
* 2. Run the Tailwind CLI to compile this file with your custom configuration
|
||||
* 3. Replace this file with the compiled CSS
|
||||
*
|
||||
* The actual file should be generated using:
|
||||
* npx tailwindcss -i ./src/input.css -o ./static/css/tailwind.min.css --minify
|
||||
*/
|
||||
|
||||
/* Base Tailwind imports */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
70
static/d3-extensions.js
vendored
70
static/d3-extensions.js
vendored
@@ -450,6 +450,76 @@ class D3Extensions {
|
||||
// Pulsanimation starten
|
||||
pulse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet Daten aus der Datenbank für die Mindmap-Visualisierung
|
||||
* @param {Array} databaseNodes - Knotendaten aus der Datenbank
|
||||
* @param {Array} links - Verbindungsdaten oder null für automatische Extraktion
|
||||
* @returns {Object} Aufbereitete Daten für D3.js
|
||||
*/
|
||||
static processDbNodesForVisualization(databaseNodes, links = null) {
|
||||
// Überprüfe, ob Daten vorhanden sind
|
||||
if (!databaseNodes || databaseNodes.length === 0) {
|
||||
console.warn('Keine Knotendaten zum Verarbeiten vorhanden');
|
||||
return { nodes: [], links: [] };
|
||||
}
|
||||
|
||||
// Knoten mit D3-Kompatiblem Format erstellen
|
||||
const nodes = databaseNodes.map(node => {
|
||||
// Farbgenerierung, falls keine vorhanden
|
||||
const nodeColor = node.color_code ||
|
||||
node.color ||
|
||||
D3Extensions.stringToColor(node.name || 'default');
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
description: node.description || '',
|
||||
thought_count: node.thought_count || 0,
|
||||
color: nodeColor,
|
||||
// Zusätzliche Attribute
|
||||
category_id: node.category_id,
|
||||
is_public: node.is_public !== undefined ? node.is_public : true,
|
||||
// Position, falls vorhanden
|
||||
x: node.x_position,
|
||||
y: node.y_position,
|
||||
// Größe, falls vorhanden
|
||||
scale: node.scale || 1.0
|
||||
};
|
||||
});
|
||||
|
||||
// Verbindungen verarbeiten
|
||||
let processedLinks = [];
|
||||
|
||||
if (links && Array.isArray(links)) {
|
||||
// Verwende übergebene Verbindungen
|
||||
processedLinks = links.map(link => {
|
||||
return {
|
||||
source: link.source,
|
||||
target: link.target,
|
||||
// Zusätzliche Attribute
|
||||
type: link.type || 'default',
|
||||
strength: link.strength || 1
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// Extrahiere Verbindungen aus den Knoten
|
||||
databaseNodes.forEach(node => {
|
||||
if (node.connections && Array.isArray(node.connections)) {
|
||||
node.connections.forEach(conn => {
|
||||
processedLinks.push({
|
||||
source: node.id,
|
||||
target: conn.target,
|
||||
type: conn.type || 'default',
|
||||
strength: conn.strength || 1
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { nodes, links: processedLinks };
|
||||
}
|
||||
}
|
||||
|
||||
// Globale Verfügbarkeit sicherstellen
|
||||
|
||||
35
static/fonts/inter.css
Normal file
35
static/fonts/inter.css
Normal file
@@ -0,0 +1,35 @@
|
||||
/* Inter font - Local version */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/inter-light.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/inter-regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/inter-medium.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/inter-semibold.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/inter-bold.woff2') format('woff2');
|
||||
}
|
||||
21
static/fonts/jetbrains-mono.css
Normal file
21
static/fonts/jetbrains-mono.css
Normal file
@@ -0,0 +1,21 @@
|
||||
/* JetBrains Mono font - Local version */
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/jetbrainsmono-regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/jetbrainsmono-medium.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/jetbrainsmono-bold.woff2') format('woff2');
|
||||
}
|
||||
12
static/js/alpine.min.js
vendored
Normal file
12
static/js/alpine.min.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Alpine.js v3.12.3
|
||||
*
|
||||
* This is a placeholder file. For production, you should:
|
||||
* 1. Download the official Alpine.js file from https://github.com/alpinejs/alpine/releases
|
||||
* 2. Replace this file with the downloaded version
|
||||
*
|
||||
* Alternatively, you can run:
|
||||
* curl -L https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js > alpine.min.js
|
||||
*/
|
||||
|
||||
console.error('This is a placeholder for Alpine.js. Please replace with the actual library file.');
|
||||
@@ -226,4 +226,7 @@ const MindMap = {
|
||||
};
|
||||
|
||||
// Globale Export für andere Module
|
||||
window.MindMap = MindMap;
|
||||
window.MindMap = MindMap;
|
||||
|
||||
// Export als Modul
|
||||
export default MindMap;
|
||||
@@ -11,6 +11,62 @@ class ChatGPTAssistant {
|
||||
this.container = null;
|
||||
this.chatHistory = null;
|
||||
this.inputField = null;
|
||||
this.suggestionArea = null;
|
||||
this.maxRetries = 2;
|
||||
this.retryCount = 0;
|
||||
this.markdownParser = null;
|
||||
this.initializeMarkdownParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert den Markdown-Parser
|
||||
*/
|
||||
async initializeMarkdownParser() {
|
||||
// Dynamisch marked.js laden, wenn noch nicht vorhanden
|
||||
if (!window.marked) {
|
||||
try {
|
||||
// Prüfen, ob marked.js bereits im Dokument geladen ist
|
||||
if (!document.querySelector('script[src*="marked"]')) {
|
||||
// Falls nicht, Script-Tag erstellen und einfügen
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
|
||||
script.async = true;
|
||||
|
||||
// Promise erstellen, das resolved wird, wenn das Script geladen wurde
|
||||
await new Promise((resolve, reject) => {
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
console.log('Marked.js erfolgreich geladen');
|
||||
}
|
||||
|
||||
// Marked konfigurieren
|
||||
this.markdownParser = window.marked;
|
||||
this.markdownParser.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
sanitize: true,
|
||||
smartLists: true,
|
||||
smartypants: true
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden von marked.js:', error);
|
||||
// Fallback-Parser, der nur einfache Absätze erkennt
|
||||
this.markdownParser = {
|
||||
parse: (text) => {
|
||||
return text.split('\n').map(line => {
|
||||
if (line.trim() === '') return '<br>';
|
||||
return `<p>${line}</p>`;
|
||||
}).join('');
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Marked ist bereits geladen
|
||||
this.markdownParser = window.marked;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +80,16 @@ class ChatGPTAssistant {
|
||||
this.setupEventListeners();
|
||||
|
||||
// Ersten Willkommensnachricht anzeigen
|
||||
this.addMessage("assistant", "Frage den KI-Assistenten");
|
||||
this.addMessage("assistant", "Hallo! Ich bin dein KI-Assistent (4o-mini) und habe Zugriff auf die Wissensdatenbank. Wie kann ich dir helfen?\n\nDu kannst mir Fragen über:\n- **Gedanken** in der Datenbank\n- **Kategorien** und Wissenschaftsbereiche\n- **Mindmaps** und Wissensverknüpfungen\n\nstellen.");
|
||||
|
||||
// Vorschläge anzeigen
|
||||
this.showSuggestions([
|
||||
"Zeige mir Gedanken zur künstlichen Intelligenz",
|
||||
"Welche Kategorien gibt es in der Datenbank?",
|
||||
"Suche nach Mindmaps zum Thema Informatik"
|
||||
]);
|
||||
|
||||
console.log('KI-Assistent initialisiert!');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +110,7 @@ class ChatGPTAssistant {
|
||||
// Chat-Container
|
||||
const chatContainer = document.createElement('div');
|
||||
chatContainer.id = 'assistant-chat';
|
||||
chatContainer.className = 'bg-white dark:bg-dark-800 rounded-lg shadow-xl overflow-hidden transition-all duration-300 w-80 sm:w-96 max-h-0 opacity-0';
|
||||
chatContainer.className = 'bg-white dark:bg-dark-800 rounded-lg shadow-xl overflow-hidden transition-all duration-300 w-80 md:w-96 max-h-0 opacity-0';
|
||||
|
||||
// Chat-Header
|
||||
const header = document.createElement('div');
|
||||
@@ -53,7 +118,7 @@ class ChatGPTAssistant {
|
||||
header.innerHTML = `
|
||||
<div class="flex items-center">
|
||||
<i class="fas fa-robot mr-2"></i>
|
||||
<span>KI-Assistent</span>
|
||||
<span>KI-Assistent (4o-mini)</span>
|
||||
</div>
|
||||
<button id="assistant-close" class="text-white hover:text-gray-200">
|
||||
<i class="fas fa-times"></i>
|
||||
@@ -63,7 +128,12 @@ class ChatGPTAssistant {
|
||||
// Chat-Verlauf
|
||||
this.chatHistory = document.createElement('div');
|
||||
this.chatHistory.id = 'assistant-history';
|
||||
this.chatHistory.className = 'p-3 overflow-y-auto max-h-80 space-y-3';
|
||||
this.chatHistory.className = 'p-3 overflow-y-auto max-h-96 space-y-3';
|
||||
|
||||
// Vorschlagsbereich
|
||||
this.suggestionArea = document.createElement('div');
|
||||
this.suggestionArea.id = 'assistant-suggestions';
|
||||
this.suggestionArea.className = 'px-3 pb-2 flex flex-wrap gap-2 overflow-x-auto hidden';
|
||||
|
||||
// Chat-Eingabe
|
||||
const inputContainer = document.createElement('div');
|
||||
@@ -71,7 +141,7 @@ class ChatGPTAssistant {
|
||||
|
||||
this.inputField = document.createElement('input');
|
||||
this.inputField.type = 'text';
|
||||
this.inputField.placeholder = 'Frage den KI-Assistenten';
|
||||
this.inputField.placeholder = 'Stelle eine Frage zur Wissensdatenbank...';
|
||||
this.inputField.className = 'flex-1 border border-gray-300 dark:border-dark-600 dark:bg-dark-700 dark:text-white rounded-l-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary-500';
|
||||
|
||||
const sendButton = document.createElement('button');
|
||||
@@ -85,6 +155,7 @@ class ChatGPTAssistant {
|
||||
|
||||
chatContainer.appendChild(header);
|
||||
chatContainer.appendChild(this.chatHistory);
|
||||
chatContainer.appendChild(this.suggestionArea);
|
||||
chatContainer.appendChild(inputContainer);
|
||||
|
||||
this.container.appendChild(toggleButton);
|
||||
@@ -100,22 +171,40 @@ class ChatGPTAssistant {
|
||||
setupEventListeners() {
|
||||
// Toggle-Button
|
||||
const toggleButton = document.getElementById('assistant-toggle');
|
||||
toggleButton.addEventListener('click', () => this.toggleAssistant());
|
||||
if (toggleButton) {
|
||||
toggleButton.addEventListener('click', () => this.toggleAssistant());
|
||||
}
|
||||
|
||||
// Schließen-Button
|
||||
const closeButton = document.getElementById('assistant-close');
|
||||
closeButton.addEventListener('click', () => this.toggleAssistant(false));
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', () => this.toggleAssistant(false));
|
||||
}
|
||||
|
||||
// Senden-Button
|
||||
const sendButton = document.getElementById('assistant-send');
|
||||
sendButton.addEventListener('click', () => this.sendMessage());
|
||||
if (sendButton) {
|
||||
sendButton.addEventListener('click', () => this.sendMessage());
|
||||
}
|
||||
|
||||
// Enter-Taste im Eingabefeld
|
||||
this.inputField.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
if (this.inputField) {
|
||||
this.inputField.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Vorschläge klickbar machen
|
||||
if (this.suggestionArea) {
|
||||
this.suggestionArea.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('suggestion-pill')) {
|
||||
this.inputField.value = e.target.textContent;
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,14 +213,21 @@ class ChatGPTAssistant {
|
||||
*/
|
||||
toggleAssistant(state = null) {
|
||||
const chatContainer = document.getElementById('assistant-chat');
|
||||
if (!chatContainer) return;
|
||||
|
||||
this.isOpen = state !== null ? state : !this.isOpen;
|
||||
|
||||
if (this.isOpen) {
|
||||
chatContainer.classList.remove('max-h-0', 'opacity-0');
|
||||
chatContainer.classList.add('max-h-96', 'opacity-100');
|
||||
this.inputField.focus();
|
||||
chatContainer.classList.add('max-h-[32rem]', 'opacity-100');
|
||||
if (this.inputField) this.inputField.focus();
|
||||
|
||||
// Zeige Vorschläge wenn verfügbar
|
||||
if (this.suggestionArea && this.suggestionArea.children.length > 0) {
|
||||
this.suggestionArea.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
chatContainer.classList.remove('max-h-96', 'opacity-100');
|
||||
chatContainer.classList.remove('max-h-[32rem]', 'opacity-100');
|
||||
chatContainer.classList.add('max-h-0', 'opacity-0');
|
||||
}
|
||||
}
|
||||
@@ -153,22 +249,144 @@ class ChatGPTAssistant {
|
||||
bubble.className = sender === 'user'
|
||||
? 'bg-primary-100 dark:bg-primary-900 text-gray-800 dark:text-white rounded-lg py-2 px-3 max-w-[85%]'
|
||||
: 'bg-gray-100 dark:bg-dark-700 text-gray-800 dark:text-white rounded-lg py-2 px-3 max-w-[85%]';
|
||||
bubble.textContent = text;
|
||||
|
||||
// Formatierung des Texts (mit Markdown für Assistent-Nachrichten)
|
||||
let formattedText = '';
|
||||
|
||||
if (sender === 'assistant' && this.markdownParser) {
|
||||
// Für Assistentnachrichten Markdown verwenden
|
||||
try {
|
||||
formattedText = this.markdownParser.parse(text);
|
||||
|
||||
// CSS für Markdown-Formatierung hinzufügen
|
||||
const markdownStyles = `
|
||||
.markdown-bubble h1, .markdown-bubble h2, .markdown-bubble h3,
|
||||
.markdown-bubble h4, .markdown-bubble h5, .markdown-bubble h6 {
|
||||
font-weight: bold;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.markdown-bubble h1 { font-size: 1.4rem; }
|
||||
.markdown-bubble h2 { font-size: 1.3rem; }
|
||||
.markdown-bubble h3 { font-size: 1.2rem; }
|
||||
.markdown-bubble h4 { font-size: 1.1rem; }
|
||||
.markdown-bubble ul, .markdown-bubble ol {
|
||||
padding-left: 1.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.markdown-bubble ul { list-style-type: disc; }
|
||||
.markdown-bubble ol { list-style-type: decimal; }
|
||||
.markdown-bubble p { margin: 0.5rem 0; }
|
||||
.markdown-bubble code {
|
||||
font-family: monospace;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown-bubble pre {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.markdown-bubble pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-bubble blockquote {
|
||||
border-left: 3px solid rgba(0, 0, 0, 0.2);
|
||||
padding-left: 0.8rem;
|
||||
margin: 0.5rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
.dark .markdown-bubble code {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.dark .markdown-bubble pre {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.dark .markdown-bubble blockquote {
|
||||
border-left-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
`;
|
||||
|
||||
// Füge die Styles hinzu, wenn sie noch nicht vorhanden sind
|
||||
if (!document.querySelector('#markdown-chat-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'markdown-chat-styles';
|
||||
style.textContent = markdownStyles;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Klasse für Markdown-Formatierung hinzufügen
|
||||
bubble.classList.add('markdown-bubble');
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Markdown-Formatierung:', error);
|
||||
// Fallback zur einfachen Formatierung
|
||||
formattedText = text.split('\n').map(line => {
|
||||
if (line.trim() === '') return '<br>';
|
||||
return `<p>${line}</p>`;
|
||||
}).join('');
|
||||
}
|
||||
} else {
|
||||
// Für Benutzernachrichten einfache Formatierung
|
||||
formattedText = text.split('\n').map(line => {
|
||||
if (line.trim() === '') return '<br>';
|
||||
return `<p>${line}</p>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
bubble.innerHTML = formattedText;
|
||||
|
||||
messageEl.appendChild(bubble);
|
||||
this.chatHistory.appendChild(messageEl);
|
||||
|
||||
// Scroll zum Ende des Verlaufs
|
||||
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
||||
if (this.chatHistory) {
|
||||
this.chatHistory.appendChild(messageEl);
|
||||
|
||||
// Scroll zum Ende des Verlaufs
|
||||
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt Vorschläge als klickbare Pills an
|
||||
* @param {string[]} suggestions - Liste von Vorschlägen
|
||||
*/
|
||||
showSuggestions(suggestions) {
|
||||
if (!this.suggestionArea) return;
|
||||
|
||||
// Vorherige Vorschläge entfernen
|
||||
this.suggestionArea.innerHTML = '';
|
||||
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
suggestions.forEach(suggestion => {
|
||||
const pill = document.createElement('button');
|
||||
pill.className = 'suggestion-pill text-sm bg-gray-200 dark:bg-dark-600 hover:bg-gray-300 dark:hover:bg-dark-500 text-gray-800 dark:text-gray-200 rounded-full px-3 py-1 mb-2 transition-colors';
|
||||
pill.textContent = suggestion;
|
||||
this.suggestionArea.appendChild(pill);
|
||||
});
|
||||
|
||||
this.suggestionArea.classList.remove('hidden');
|
||||
} else {
|
||||
this.suggestionArea.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet die Benutzernachricht an den Server und zeigt die Antwort an
|
||||
*/
|
||||
async sendMessage() {
|
||||
if (!this.inputField) return;
|
||||
|
||||
const userInput = this.inputField.value.trim();
|
||||
if (!userInput || this.isLoading) return;
|
||||
|
||||
// Vorschläge ausblenden
|
||||
if (this.suggestionArea) {
|
||||
this.suggestionArea.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Benutzernachricht anzeigen
|
||||
this.addMessage('user', userInput);
|
||||
|
||||
@@ -180,6 +398,7 @@ class ChatGPTAssistant {
|
||||
this.showLoadingIndicator();
|
||||
|
||||
try {
|
||||
console.log('Sende Anfrage an KI-Assistent API...');
|
||||
// Anfrage an den Server senden
|
||||
const response = await fetch('/api/assistant', {
|
||||
method: 'POST',
|
||||
@@ -189,36 +408,118 @@ class ChatGPTAssistant {
|
||||
body: JSON.stringify({
|
||||
messages: this.messages
|
||||
}),
|
||||
cache: 'no-cache', // Kein Cache verwenden
|
||||
credentials: 'same-origin' // Cookies senden
|
||||
});
|
||||
|
||||
// Ladeindikator entfernen
|
||||
this.removeLoadingIndicator();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Netzwerkfehler oder Serverproblem');
|
||||
throw new Error(`Serverfehler: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Ladeindikator entfernen
|
||||
this.removeLoadingIndicator();
|
||||
console.log('Antwort erhalten:', data);
|
||||
|
||||
// Antwort anzeigen
|
||||
this.addMessage('assistant', data.response);
|
||||
if (data.response) {
|
||||
this.addMessage('assistant', data.response);
|
||||
|
||||
// Neue Vorschläge basierend auf dem aktuellen Kontext anzeigen
|
||||
this.generateContextualSuggestions();
|
||||
|
||||
// Erfolgreiche Anfrage zurücksetzen
|
||||
this.retryCount = 0;
|
||||
} else if (data.error) {
|
||||
this.addMessage('assistant', `Fehler: ${data.error}`);
|
||||
} else {
|
||||
throw new Error('Unerwartetes Antwortformat vom Server');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Kommunikation mit dem Assistenten:', error);
|
||||
|
||||
// Ladeindikator entfernen
|
||||
// Ladeindikator entfernen, falls noch vorhanden
|
||||
this.removeLoadingIndicator();
|
||||
|
||||
// Fehlermeldung anzeigen
|
||||
this.addMessage('assistant', 'Es tut mir leid, aber es gab ein Problem bei der Verarbeitung deiner Anfrage. Bitte versuche es später noch einmal.');
|
||||
// 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...');
|
||||
|
||||
// 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
|
||||
|
||||
// Erneuter Versand mit gleicher Nachricht
|
||||
this.inputField.value = lastUserMessage;
|
||||
this.sendMessage();
|
||||
}, 1500);
|
||||
} 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.retryCount = 0; // Zurücksetzen für die nächste Anfrage
|
||||
}
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert kontextbasierte Vorschläge basierend auf dem aktuellen Chat-Verlauf
|
||||
*/
|
||||
generateContextualSuggestions() {
|
||||
// Basierend auf letzter Antwort des Assistenten, verschiedene Vorschläge generieren
|
||||
const lastAssistantMessage = this.messages.findLast(msg => msg.role === 'assistant')?.content || '';
|
||||
|
||||
let suggestions = [];
|
||||
|
||||
// Intelligente Vorschläge basierend auf Kontext
|
||||
if (lastAssistantMessage.includes('Künstliche Intelligenz') ||
|
||||
lastAssistantMessage.includes('KI ') ||
|
||||
lastAssistantMessage.includes('AI ')) {
|
||||
suggestions = [
|
||||
"Wie wird KI in der Wissenschaft eingesetzt?",
|
||||
"Zeige mir Gedanken zum maschinellen Lernen",
|
||||
"Was ist der Unterschied zwischen KI und ML?"
|
||||
];
|
||||
} else if (lastAssistantMessage.includes('Kategorie') ||
|
||||
lastAssistantMessage.includes('Kategorien')) {
|
||||
suggestions = [
|
||||
"Zeige mir die Unterkategorien",
|
||||
"Welche Gedanken gehören zu dieser Kategorie?",
|
||||
"Liste alle Wissenschaftskategorien auf"
|
||||
];
|
||||
} else if (lastAssistantMessage.includes('Mindmap') ||
|
||||
lastAssistantMessage.includes('Visualisierung')) {
|
||||
suggestions = [
|
||||
"Wie kann ich eine eigene Mindmap erstellen?",
|
||||
"Zeige mir Beispiele für Mindmaps",
|
||||
"Wie funktionieren die Verbindungen in Mindmaps?"
|
||||
];
|
||||
} else {
|
||||
// Standardvorschläge
|
||||
suggestions = [
|
||||
"Erzähle mir mehr dazu",
|
||||
"Gibt es Beispiele dafür?",
|
||||
"Wie kann ich diese Information nutzen?"
|
||||
];
|
||||
}
|
||||
|
||||
this.showSuggestions(suggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt einen Ladeindikator im Chat an
|
||||
*/
|
||||
showLoadingIndicator() {
|
||||
if (!this.chatHistory) return;
|
||||
|
||||
// Entferne vorhandenen Ladeindikator (falls vorhanden)
|
||||
this.removeLoadingIndicator();
|
||||
|
||||
const loadingEl = document.createElement('div');
|
||||
loadingEl.id = 'assistant-loading';
|
||||
loadingEl.className = 'flex justify-start';
|
||||
@@ -232,46 +533,37 @@ class ChatGPTAssistant {
|
||||
|
||||
// Scroll zum Ende des Verlaufs
|
||||
this.chatHistory.scrollTop = this.chatHistory.scrollHeight;
|
||||
|
||||
// Stil für den Typing-Indikator
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.typing-indicator span {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: #888;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
opacity: 0.4;
|
||||
animation: typing 1.5s infinite ease-in-out;
|
||||
}
|
||||
.typing-indicator span:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.typing-indicator span:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
@keyframes typing {
|
||||
0% { transform: translateY(0); }
|
||||
50% { transform: translateY(-5px); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt den Ladeindikator aus dem Chat
|
||||
*/
|
||||
removeLoadingIndicator() {
|
||||
const loadingEl = document.getElementById('assistant-loading');
|
||||
if (loadingEl) {
|
||||
loadingEl.remove();
|
||||
const loadingIndicator = document.getElementById('assistant-loading');
|
||||
if (loadingIndicator) {
|
||||
loadingIndicator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet den Assistenten und sendet eine vorgegebene Frage
|
||||
* @param {string} question - Die zu stellende Frage
|
||||
*/
|
||||
async sendQuestion(question) {
|
||||
if (!question || this.isLoading) return;
|
||||
|
||||
// Assistenten öffnen
|
||||
this.toggleAssistant(true);
|
||||
|
||||
// Kurze Verzögerung, um sicherzustellen, dass der UI vollständig geöffnet ist
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Frage in Eingabefeld setzen
|
||||
if (this.inputField) {
|
||||
this.inputField.value = question;
|
||||
|
||||
// Sende die Frage
|
||||
this.sendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
* Initialisiert die Mindmap-Seite
|
||||
*/
|
||||
function initMindmapPage() {
|
||||
console.log('Mindmap-Seite Initialisierung startet...');
|
||||
console.log('D3 Bibliothek verfügbar:', typeof d3 !== 'undefined');
|
||||
console.log('MindMapVisualization verfügbar:', typeof MindMapVisualization !== 'undefined');
|
||||
|
||||
const mindmapContainer = document.getElementById('mindmap-container');
|
||||
const thoughtsContainer = document.getElementById('thoughts-container');
|
||||
|
||||
@@ -26,6 +30,7 @@ function initMindmapPage() {
|
||||
console.error('Mindmap-Container nicht gefunden!');
|
||||
return;
|
||||
}
|
||||
console.log('Mindmap-Container gefunden:', mindmapContainer);
|
||||
|
||||
// Prüfe, ob D3.js geladen ist
|
||||
if (typeof d3 === 'undefined') {
|
||||
@@ -41,17 +46,47 @@ function initMindmapPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfe, ob MindMapVisualization definiert ist
|
||||
if (typeof MindMapVisualization === 'undefined') {
|
||||
console.error('MindMapVisualization-Klasse ist nicht definiert!');
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">MindMap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Erstelle die Mindmap-Visualisierung
|
||||
const mindmap = new MindMapVisualization('#mindmap-container', {
|
||||
height: 600,
|
||||
onNodeClick: handleNodeClick
|
||||
});
|
||||
|
||||
// Globale Referenz für die Zoom-Buttons erstellen
|
||||
window.mindmapInstance = mindmap;
|
||||
|
||||
// Lade die Mindmap-Daten
|
||||
mindmap.loadData();
|
||||
try {
|
||||
console.log('Versuche, MindMapVisualization zu erstellen...');
|
||||
const mindmap = new MindMapVisualization('#mindmap-container', {
|
||||
height: 600,
|
||||
onNodeClick: handleNodeClick
|
||||
});
|
||||
|
||||
// Globale Referenz für die Zoom-Buttons erstellen
|
||||
window.mindmapInstance = mindmap;
|
||||
|
||||
// Lade die Mindmap-Daten
|
||||
mindmap.loadData();
|
||||
console.log('MindMapVisualization erfolgreich erstellt und geladen');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der MindMapVisualization:', error);
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">Fehler beim Erstellen der Mindmap-Visualisierung:</p>
|
||||
<p class="text-md mt-2">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Suchfunktion für die Mindmap
|
||||
const searchInput = document.getElementById('mindmap-search');
|
||||
|
||||
@@ -163,7 +163,7 @@ class MindMapVisualization {
|
||||
// API-Aufruf mit kürzerem Timeout im Hintergrund durchführen
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout - reduced from 10
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 Sekunden Timeout
|
||||
|
||||
const response = await fetch('/api/mindmap', {
|
||||
signal: controller.signal,
|
||||
@@ -175,8 +175,55 @@ class MindMapVisualization {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`HTTP Fehler: ${response.status}, verwende Standarddaten`);
|
||||
return; // Keep using default data
|
||||
console.warn(`HTTP Fehler: ${response.status}, versuche erneute Verbindung`);
|
||||
|
||||
// Bei Verbindungsfehler versuchen, die Verbindung neu herzustellen
|
||||
const retryResponse = await fetch('/api/refresh-mindmap', {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache'
|
||||
}
|
||||
});
|
||||
|
||||
if (!retryResponse.ok) {
|
||||
throw new Error(`Retry failed with status: ${retryResponse.status}`);
|
||||
}
|
||||
|
||||
const retryData = await retryResponse.json();
|
||||
|
||||
if (!retryData.success || !retryData.nodes || retryData.nodes.length === 0) {
|
||||
console.warn('Keine Mindmap-Daten nach Neuversuch, verwende weiterhin Standard-Daten.');
|
||||
return; // Keep using default data
|
||||
}
|
||||
|
||||
// Flache Liste von Knoten und Verbindungen erstellen
|
||||
this.nodes = [];
|
||||
this.links = [];
|
||||
|
||||
// Knoten direkt übernehmen
|
||||
retryData.nodes.forEach(node => {
|
||||
this.nodes.push({
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
description: node.description || '',
|
||||
thought_count: node.thought_count || 0,
|
||||
color: this.generateColorFromString(node.name),
|
||||
});
|
||||
|
||||
// Verbindungen hinzufügen
|
||||
if (node.connections && node.connections.length > 0) {
|
||||
node.connections.forEach(conn => {
|
||||
this.links.push({
|
||||
source: node.id,
|
||||
target: conn.target
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Visualisierung aktualisieren mit den tatsächlichen Daten
|
||||
this.updateVisualization();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
@@ -66,18 +66,18 @@ class NeuralNetworkBackground {
|
||||
flowColor: '#c4b5fd'
|
||||
};
|
||||
|
||||
// Config - Updated to be more flowing and subtle
|
||||
// Config - Updated to be more subtle, efficient, and elegant
|
||||
this.config = {
|
||||
nodeCount: 100, // Slightly fewer nodes for cleaner look
|
||||
nodeSize: 0.8, // Smaller nodes
|
||||
nodeVariation: 0.5, // Less variation for uniformity
|
||||
connectionDistance: 200, // Longer connections for better flow
|
||||
connectionOpacity: 0.2, // More subtle connections
|
||||
animationSpeed: 0.08, // Much slower movement
|
||||
pulseSpeed: 0.004, // Slower pulse
|
||||
flowSpeed: 0.6, // Speed of flow animations
|
||||
flowDensity: 0.001, // How often new flows start (lower = less frequent)
|
||||
flowLength: 0.2 // Length of the flow (percentage of the connection)
|
||||
nodeCount: 70, // Reduced node count for better performance
|
||||
nodeSize: 0.6, // Smaller nodes for subtlety
|
||||
nodeVariation: 0.3, // Less variation for uniformity
|
||||
connectionDistance: 150, // Shorter connections for cleaner look
|
||||
connectionOpacity: 0.15, // More subtle connections
|
||||
animationSpeed: 0.04, // Slower, more elegant movement
|
||||
pulseSpeed: 0.002, // Very slow pulse for subtlety
|
||||
flowSpeed: 0.4, // Slower flow animations
|
||||
flowDensity: 0.0008, // Less frequent flow animations
|
||||
flowLength: 0.15 // Shorter flow animations
|
||||
};
|
||||
|
||||
// Initialize
|
||||
@@ -713,7 +713,7 @@ class NeuralNetworkBackground {
|
||||
};
|
||||
p2 = {
|
||||
x: fromNode.x + (toNode.x - fromNode.x) * endProgress,
|
||||
y: fromNode.y + (toNode.y - fromNode.y) * endProgress
|
||||
y: fromNode.y + (toNode.y - fromNode.y) * startProgress
|
||||
};
|
||||
} else {
|
||||
p1 = {
|
||||
|
||||
6
static/three.min.js
vendored
6
static/three.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user