diff --git a/app.py b/app.py index 18b4032..7883c5f 100644 --- a/app.py +++ b/app.py @@ -1930,4 +1930,73 @@ def admin_update_database(): message = f"Fehler: {str(e)}" success = False - return render_template('admin/update_database.html', message=message, success=success) \ No newline at end of file + return render_template('admin/update_database.html', message=message, success=success) + +@app.route('/api/mindmap/') +def get_mindmap_node(node_id): + """Liefert die Mindmap-Daten für einen bestimmten Knoten und seine Subthemen.""" + try: + if node_id == 'root': + # Hauptknoten (Wissen) abrufen + wissen_node = MindMapNode.query.filter_by(name="Wissen").first() + if not wissen_node: + wissen_node = MindMapNode( + name="Wissen", + description="Zentrale Wissensbasis", + color_code="#4299E1", + is_public=True + ) + db.session.add(wissen_node) + db.session.commit() + + # Alle direkten Kinder des Wissen-Knotens holen + nodes = wissen_node.children.all() + else: + # Bestimmten Knoten und seine Kinder abrufen + parent_node = MindMapNode.query.get_or_404(node_id) + nodes = parent_node.children.all() + wissen_node = parent_node + + # Ergebnisdaten vorbereiten + nodes_data = [] + edges_data = [] + + # Hauptknoten hinzufügen + nodes_data.append({ + 'id': wissen_node.id, + 'name': wissen_node.name, + 'description': wissen_node.description or '', + 'color_code': wissen_node.color_code or '#4299E1', + 'is_center': True, + 'has_children': len(nodes) > 0 + }) + + # Kinder hinzufügen + for node in nodes: + nodes_data.append({ + 'id': node.id, + 'name': node.name, + 'description': node.description or '', + 'color_code': node.color_code or '#9F7AEA', + 'is_center': False, + 'has_children': len(node.children.all()) > 0 + }) + + # Verbindung zum Elternknoten hinzufügen + edges_data.append({ + 'source_id': wissen_node.id, + 'target_id': node.id, + 'strength': 0.8 + }) + + return jsonify({ + 'nodes': nodes_data, + 'edges': edges_data + }) + + except Exception as e: + print(f"Fehler beim Abrufen der Mindmap-Daten: {str(e)}") + return jsonify({ + 'success': False, + 'error': 'Mindmap-Daten konnten nicht geladen werden' + }), 500 \ No newline at end of file diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js index 08e6063..4145129 100644 --- a/static/js/update_mindmap.js +++ b/static/js/update_mindmap.js @@ -129,7 +129,9 @@ const mindmapData = { color: '#f5f5f5', icon: 'fa-solid fa-circle', fontColor: '#222', - fontSize: 22 + fontSize: 22, + neuronSize: 12, + neuronActivity: 1.0 }, { id: 'philosophy', @@ -248,7 +250,7 @@ document.addEventListener('DOMContentLoaded', function() { console.log('Initialisiere Cytoscape...'); - // Initialisiere Cytoscape mit concentric/circle Layout und Icon-Overlay + // Initialisiere Cytoscape mit einem schlichten, modernen Design window.cy = cytoscape({ container: cyContainer, elements: elements, @@ -262,62 +264,66 @@ document.addEventListener('DOMContentLoaded', function() { 'text-valign': 'center', 'text-halign': 'center', 'font-size': 'data(fontSize)', - 'width': 'mapData(neuronSize, 3, 10, 60, 110)', - 'height': 'mapData(neuronSize, 3, 10, 60, 110)', - 'border-width': 4, - 'border-color': '#fff', - 'border-opacity': 0.9, - 'overlay-padding': 4, - 'z-index': 10, + 'width': function(ele) { + return ele.data('isCenter') ? 100 : 80; + }, + 'height': function(ele) { + return ele.data('isCenter') ? 100 : 80; + }, + 'border-width': 2, + 'border-color': '#ffffff', + 'border-opacity': 0.8, 'shape': 'ellipse', - 'background-opacity': 0.95, - 'shadow-blur': 20, - 'shadow-color': 'data(color)', - 'shadow-opacity': 0.7, - 'shadow-offset-x': 0, - 'shadow-offset-y': 0, + 'background-opacity': 0.9, 'text-wrap': 'wrap', - 'text-max-width': 90 + 'text-max-width': 80, + 'transition-property': 'background-color, border-width', + 'transition-duration': '0.2s' } }, { - selector: 'node[isCenter]','style': { + selector: 'node[isCenter]', + style: { 'background-color': '#f5f5f5', 'color': '#222', - 'font-size': 22, - 'border-width': 0, - 'width': 120, - 'height': 120 + 'font-size': 20, + 'border-width': 3, + 'width': 100, + 'height': 100 } }, { selector: 'node:selected', style: { 'border-color': '#f59e42', - 'border-width': 6, - 'shadow-blur': 30, - 'shadow-opacity': 0.9 + 'border-width': 3, + 'background-opacity': 1 } }, { selector: 'edge', style: { - 'width': 'mapData(strength, 0.2, 1, 3, 7)', - 'line-color': '#bdbdbd', - 'line-opacity': 0.7, + 'width': function(ele) { + return ele.data('strength') ? ele.data('strength') * 2 : 1; + }, + 'line-color': function(ele) { + const sourceColor = ele.source().data('color'); + return sourceColor || '#8a8aaa'; + }, + 'line-opacity': function(ele) { + return ele.data('strength') ? ele.data('strength') * 0.6 : 0.4; + }, 'curve-style': 'bezier', 'target-arrow-shape': 'none', - 'control-point-distances': [40, -40], - 'control-point-weights': [0.5, 0.5], - 'edge-distances': 'intersection', - 'line-style': 'solid' + 'control-point-distances': [30, -30], + 'control-point-weights': [0.5, 0.5] } } ], layout: { name: 'concentric', fit: true, - padding: 60, + padding: 50, animate: true, concentric: function(node) { return node.data('isCenter') ? 2 : 1; @@ -373,20 +379,11 @@ document.addEventListener('DOMContentLoaded', function() { } }); - // Nach dem Rendern: Icons als HTML-Overlay einfügen + // Entferne den Icon-Overlay-Code setTimeout(() => { - cy.nodes().forEach(node => { - const icon = node.data('icon'); - if (icon) { - const dom = cy.getElementById(node.id()).popperRef(); - const iconDiv = document.createElement('div'); - iconDiv.className = 'cy-node-icon'; - iconDiv.innerHTML = ``; - document.body.appendChild(iconDiv); - // Positionierung mit Popper.js oder absolut über node.position() - } - }); - }, 500); + // Entferne alle existierenden Icon-Overlays + document.querySelectorAll('.cy-node-icon').forEach(icon => icon.remove()); + }, 0); }); // Funktion zum Initialisieren des neuronalen Designs @@ -547,17 +544,20 @@ function enhanceMindmap() { cy.layout({ name: 'cose', animate: true, - animationDuration: 1800, + animationDuration: 2000, nodeDimensionsIncludeLabels: true, padding: 100, - spacingFactor: 1.8, - randomize: false, + spacingFactor: 2, + randomize: true, fit: true, - componentSpacing: 100, - nodeRepulsion: 8000, - edgeElasticity: 100, - nestingFactor: 1.2, - gravity: 80 + componentSpacing: 150, + nodeRepulsion: 10000, + edgeElasticity: 150, + nestingFactor: 1.5, + gravity: 100, + initialTemp: 1000, + coolingFactor: 0.95, + minTemp: 1 }).run(); // Neuronen-Namen mit besserer Lesbarkeit umgestalten @@ -617,152 +617,149 @@ function enhanceMindmap() { * @param {Object} cy - Cytoscape-Instanz */ function applyNeuralNetworkStyle(cy) { - // Wende erweiterte Stile für Neuronen und Synapsen an cy.style() .selector('node') .style({ - 'label': 'data(name)', - 'text-valign': 'bottom', + 'label': 'data(label)', + 'text-valign': 'center', 'text-halign': 'center', - 'color': '#ffffff', - 'text-outline-width': 1.5, - 'text-outline-color': '#0a0e19', + 'color': 'data(fontColor)', + 'text-outline-width': 2, + 'text-outline-color': 'rgba(0,0,0,0.8)', 'text-outline-opacity': 0.9, - 'font-size': 10, - 'text-margin-y': 7, - 'width': 'mapData(neuronSize, 3, 10, 15, 40)', - 'height': 'mapData(neuronSize, 3, 10, 15, 40)', + 'font-size': 'data(fontSize)', + 'font-weight': '500', + 'text-margin-y': 8, + 'width': function(ele) { + if (ele.data('isCenter')) return 120; + return ele.data('neuronSize') ? ele.data('neuronSize') * 10 : 80; + }, + 'height': function(ele) { + if (ele.data('isCenter')) return 120; + return ele.data('neuronSize') ? ele.data('neuronSize') * 10 : 80; + }, 'background-color': 'data(color)', - 'background-opacity': 0.85, - 'border-width': 0, + 'background-opacity': 0.9, + 'border-width': 2, + 'border-color': '#ffffff', + 'border-opacity': 0.8, 'shape': 'ellipse', - 'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)', - 'shadow-color': 'data(color)', - 'shadow-opacity': 0.6, - 'shadow-offset-x': 0, - 'shadow-offset-y': 0 + 'transition-property': 'background-color, background-opacity, border-width', + 'transition-duration': '0.3s', + 'transition-timing-function': 'ease-in-out' }) .selector('edge') .style({ - 'width': 'mapData(strength, 0.2, 0.8, 0.7, 2)', + 'width': function(ele) { + return ele.data('strength') ? ele.data('strength') * 3 : 1; + }, 'curve-style': 'bezier', - 'line-color': '#8a8aaa', - 'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)', + 'line-color': function(ele) { + const sourceColor = ele.source().data('color'); + return sourceColor || '#8a8aaa'; + }, + 'line-opacity': function(ele) { + return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; + }, 'line-style': function(ele) { const strength = ele.data('strength'); + if (!strength) return 'solid'; if (strength <= 0.4) return 'dotted'; if (strength <= 0.6) return 'dashed'; return 'solid'; }, 'target-arrow-shape': 'none', 'source-endpoint': '0% 50%', - 'target-endpoint': '100% 50%' - }) - .selector('node[isRoot]') - .style({ - 'font-size': 12, - 'font-weight': 'bold', - 'width': 50, - 'height': 50, - 'background-color': '#6366f1', - 'shadow-blur': 20, - 'shadow-color': '#6366f1', - 'shadow-opacity': 0.8, - 'text-margin-y': 8 + 'target-endpoint': '100% 50%', + 'transition-property': 'line-opacity, width', + 'transition-duration': '0.3s', + 'transition-timing-function': 'ease-in-out' }) .update(); } -/** - * Simuliert neuronale Aktivität in der Mindmap - * @param {Object} cy - Cytoscape-Instanz - */ +// Vereinfachte neuronale Aktivitätssimulation function startNeuralActivitySimulation(cy) { - if (window.neuralInterval) clearInterval(window.neuralInterval); - - const nodes = cy.nodes(); - const edges = cy.edges(); - let currentTime = Date.now(); - - // Neuronale Aktivität simulieren - function simulateNeuralActivity() { - currentTime = Date.now(); + if (window.neuralInterval) clearInterval(window.neuralInterval); - // Zufällige Neuronen "feuern" lassen - nodes.forEach(node => { - const data = node.data(); - const lastFired = data.lastFired || 0; - const timeSinceLastFire = currentTime - lastFired; - - // Prüfen ob Neuron feuern kann (Refraktionsperiode) - if (timeSinceLastFire > data.refractionPeriod) { - // Zufälliges Feuern basierend auf Aktivität - if (Math.random() < data.neuronActivity * 0.1) { - fireNeuron(node, true, currentTime); - } - } - }); - } - - // Neuron feuern lassen - function fireNeuron(node, state, currentTime) { - const data = node.data(); - data.lastFired = currentTime; + const nodes = cy.nodes(); + let currentTime = Date.now(); - // Visuelles Feedback - node.style({ - 'background-opacity': 1, - 'shadow-blur': 25, - 'shadow-opacity': 0.9 - }); - - // Nach kurzer Zeit zurück zum Normalzustand - setTimeout(() => { - node.style({ - 'background-opacity': 0.85, - 'shadow-blur': 18, - 'shadow-opacity': 0.6 - }); - }, 200); - - // Signal weiterleiten - if (state) { - propagateSignal(node, currentTime); - } - } - - // Signal über Kanten weiterleiten - function propagateSignal(sourceNode, currentTime) { - const outgoingEdges = sourceNode.connectedEdges('out'); - - outgoingEdges.forEach(edge => { - const targetNode = edge.target(); - const edgeData = edge.data(); - const latency = edgeData.latency; - - // Signal mit Verzögerung weiterleiten - setTimeout(() => { - const targetData = targetNode.data(); - const timeSinceLastFire = currentTime - (targetData.lastFired || 0); + function simulateNeuralActivity() { + currentTime = Date.now(); - // Prüfen ob Zielneuron feuern kann - if (timeSinceLastFire > targetData.refractionPeriod) { - // Signalstärke berechnen - const signalStrength = edgeData.strength * - edgeData.conductionVelocity * - sourceNode.data('neuronActivity'); - - // Neuron feuern lassen wenn Signal stark genug - if (signalStrength > targetData.threshold) { - fireNeuron(targetNode, true, currentTime + latency); - } + nodes.forEach(node => { + const data = node.data(); + const lastFired = data.lastFired || 0; + const timeSinceLastFire = currentTime - lastFired; + + if (timeSinceLastFire > data.refractionPeriod) { + if (Math.random() < data.neuronActivity * 0.1) { + fireNeuron(node, true, currentTime); + } + } + }); + } + + function fireNeuron(node, state, currentTime) { + const data = node.data(); + data.lastFired = currentTime; + + node.style({ + 'background-opacity': 1, + 'border-width': 3 + }); + + setTimeout(() => { + node.style({ + 'background-opacity': 0.9, + 'border-width': 2 + }); + }, 200); + + if (state) { + propagateSignal(node, currentTime); } - }, latency); - }); - } - - // Simulation starten - window.neuralInterval = setInterval(simulateNeuralActivity, 100); + } + + function propagateSignal(sourceNode, currentTime) { + const outgoingEdges = sourceNode.connectedEdges(); + + outgoingEdges.forEach(edge => { + const targetNode = edge.target(); + const edgeData = edge.data(); + const latency = edgeData.latency; + + edge.style({ + 'line-opacity': 0.8, + 'width': edgeData.strength * 3 + }); + + setTimeout(() => { + edge.style({ + 'line-opacity': edgeData.strength * 0.6, + 'width': edgeData.strength * 2 + }); + }, 200); + + setTimeout(() => { + const targetData = targetNode.data(); + const timeSinceLastFire = currentTime - (targetData.lastFired || 0); + + if (timeSinceLastFire > targetData.refractionPeriod) { + const signalStrength = edgeData.strength * + edgeData.conductionVelocity * + sourceNode.data('neuronActivity'); + + if (signalStrength > targetData.threshold) { + fireNeuron(targetNode, true, currentTime + latency); + } + } + }, latency); + }); + } + + window.neuralInterval = setInterval(simulateNeuralActivity, 100); } // Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises @@ -803,29 +800,152 @@ function createFlashContainer() { return container; } +// Funktion zum Laden der Mindmap-Daten aus der Datenbank +async function loadMindmapData(nodeId = null) { + try { + const response = await fetch(`/api/mindmap/${nodeId || 'root'}`); + if (!response.ok) throw new Error('Fehler beim Laden der Mindmap-Daten'); + return await response.json(); + } catch (error) { + console.error('Fehler beim Laden der Mindmap-Daten:', error); + showFlash('Fehler beim Laden der Mindmap-Daten', 'error'); + return null; + } +} + +// Funktion zum Initialisieren der Mindmap +async function initializeMindmap() { + const mindmapData = await loadMindmapData(); + if (!mindmapData) return; + + const elements = [ + // Knoten + ...mindmapData.nodes.map(node => ({ + data: { + id: node.id, + label: node.name, + category: node.category, + description: node.description, + hasChildren: node.has_children, + expanded: false, + color: node.color_code, + fontColor: '#ffffff', + fontSize: node.is_center ? 20 : 16 + } + })), + // Kanten + ...mindmapData.edges.map(edge => ({ + data: { + source: edge.source_id, + target: edge.target_id, + strength: edge.strength || 0.5 + } + })) + ]; + + window.cy = cytoscape({ + container: document.getElementById('cy'), + elements: elements, + style: [ + { + selector: 'node', + style: { + 'background-color': 'data(color)', + 'label': 'data(label)', + 'color': 'data(fontColor)', + 'text-valign': 'center', + 'text-halign': 'center', + 'font-size': 'data(fontSize)', + 'width': function(ele) { + return ele.data('isCenter') ? 100 : 80; + }, + 'height': function(ele) { + return ele.data('isCenter') ? 100 : 80; + }, + 'border-width': 2, + 'border-color': '#ffffff', + 'border-opacity': 0.8, + 'shape': 'ellipse', + 'background-opacity': 0.9, + 'text-wrap': 'wrap', + 'text-max-width': 80, + 'transition-property': 'background-color, border-width', + 'transition-duration': '0.2s' + } + }, + { + selector: 'node[isCenter]', + style: { + 'background-color': '#f5f5f5', + 'color': '#222', + 'font-size': 20, + 'border-width': 3, + 'width': 100, + 'height': 100 + } + }, + { + selector: 'node:selected', + style: { + 'border-color': '#f59e42', + 'border-width': 3, + 'background-opacity': 1 + } + }, + { + selector: 'edge', + style: { + 'width': function(ele) { + return ele.data('strength') ? ele.data('strength') * 2 : 1; + }, + 'line-color': function(ele) { + const sourceColor = ele.source().data('color'); + return sourceColor || '#8a8aaa'; + }, + 'line-opacity': function(ele) { + return ele.data('strength') ? ele.data('strength') * 0.6 : 0.4; + }, + 'curve-style': 'bezier', + 'target-arrow-shape': 'none', + 'control-point-distances': [30, -30], + 'control-point-weights': [0.5, 0.5] + } + } + ], + layout: { + name: 'concentric', + fit: true, + padding: 50, + animate: true, + concentric: function(node) { + return node.data('isCenter') ? 2 : 1; + }, + levelWidth: function() { return 1; } + } + }); + + // Event-Listener für Knoten-Klicks + cy.on('tap', 'node', async function(evt) { + const node = evt.target; + if (node.data('hasChildren') && !node.data('expanded')) { + await loadSubthemes(node); + } + }); +} + // Funktion zum Laden der Subthemen async function loadSubthemes(node) { try { - console.log('Loading subthemes for node:', node.id()); - - // Simuliere Datenbankabfrage - const subthemes = subthemesDatabase[node.id()]; - - if (!subthemes) { - console.log('No subthemes found for node:', node.id()); - return; - } + const mindmapData = await loadMindmapData(node.id()); + if (!mindmapData) return; - // Animation starten showFlash('Lade Subthemen...', 'info'); - // Neue Seite erstellen const mindmapContainer = document.querySelector('.mindmap-container'); const newPage = document.createElement('div'); newPage.className = 'mindmap-page'; newPage.style.display = 'none'; - // Kopfzeile erstellen const header = document.createElement('div'); header.className = 'mindmap-header'; header.innerHTML = ` @@ -837,20 +957,38 @@ async function loadSubthemes(node) {

${node.data('label')}

`; - // Container für die neue Mindmap const newContainer = document.createElement('div'); newContainer.id = `cy-${node.id()}`; newContainer.className = 'mindmap-view'; - // Elemente zur Seite hinzufügen newPage.appendChild(header); newPage.appendChild(newContainer); mindmapContainer.appendChild(newPage); - // Neue Cytoscape-Instanz erstellen const newCy = cytoscape({ container: newContainer, - elements: [], + elements: [ + ...mindmapData.nodes.map(node => ({ + data: { + id: node.id, + label: node.name, + category: node.category, + description: node.description, + hasChildren: node.has_children, + expanded: false, + color: node.color_code, + fontColor: '#ffffff', + fontSize: 16 + } + })), + ...mindmapData.edges.map(edge => ({ + data: { + source: edge.source_id, + target: edge.target_id, + strength: edge.strength || 0.5 + } + })) + ], style: cy.style(), layout: { name: 'cose', @@ -861,30 +999,17 @@ async function loadSubthemes(node) { padding: 30, nodeRepulsion: 4500, idealEdgeLength: 50, - edgeElasticity: 0.45 + edgeElasticity: 0.45, + randomize: true, + componentSpacing: 100, + nodeOverlap: 20, + gravity: 0.25, + initialTemp: 1000, + coolingFactor: 0.95, + minTemp: 1 } }); - // Neue Knoten hinzufügen - subthemes.forEach(subtheme => { - newCy.add({ - group: 'nodes', - data: { - id: subtheme.id, - label: subtheme.label, - category: subtheme.category, - description: subtheme.description, - hasChildren: subtheme.hasChildren, - expanded: false, - neuronSize: 6, - neuronActivity: 0.7 - } - }); - }); - - // Neuronales Design für die neue Mindmap initialisieren - initializeNeuralDesign(newCy); - // Event-Listener für die neue Mindmap newCy.on('tap', 'node', async function(evt) { const clickedNode = evt.target; @@ -897,26 +1022,8 @@ async function loadSubthemes(node) { cy.container().style.display = 'none'; newPage.style.display = 'block'; - // Layout ausführen - newCy.layout().run(); - - // Erfolgsmeldung anzeigen showFlash('Subthemen erfolgreich geladen', 'success'); - // Icons für Subthemen wie oben einfügen - setTimeout(() => { - newCy.nodes().forEach(node => { - const icon = node.data('icon'); - if (icon) { - const dom = newCy.getElementById(node.id()).popperRef(); - const iconDiv = document.createElement('div'); - iconDiv.className = 'cy-node-icon'; - iconDiv.innerHTML = ``; - document.body.appendChild(iconDiv); - } - }); - }, 500); - } catch (error) { console.error('Fehler beim Laden der Subthemen:', error); showFlash('Fehler beim Laden der Subthemen', 'error'); @@ -1001,4 +1108,7 @@ style.textContent = ` text-shadow: 0 2px 8px rgba(0,0,0,0.25); } `; -document.head.appendChild(style); \ No newline at end of file +document.head.appendChild(style); + +// Initialisiere die Mindmap beim Laden der Seite +document.addEventListener('DOMContentLoaded', initializeMindmap); \ No newline at end of file diff --git a/static/neural-network-background-full.js b/static/neural-network-background-full.js index 7c7ecab..009eda6 100644 --- a/static/neural-network-background-full.js +++ b/static/neural-network-background-full.js @@ -1106,4 +1106,66 @@ window.addEventListener('beforeunload', () => { if (window.neuralNetworkBackground) { window.neuralNetworkBackground.destroy(); } -}); \ No newline at end of file +}); + +function applyNeuralNetworkStyle(cy) { + cy.style() + .selector('node') + .style({ + 'label': 'data(label)', + 'text-valign': 'center', + 'text-halign': 'center', + 'color': 'data(fontColor)', + 'text-outline-width': 2, + 'text-outline-color': 'rgba(0,0,0,0.8)', + 'text-outline-opacity': 0.9, + 'font-size': 'data(fontSize)', + 'font-weight': '500', + 'text-margin-y': 8, + 'width': function(ele) { + if (ele.data('isCenter')) return 120; + return ele.data('neuronSize') ? ele.data('neuronSize') * 10 : 80; + }, + 'height': function(ele) { + if (ele.data('isCenter')) return 120; + return ele.data('neuronSize') ? ele.data('neuronSize') * 10 : 80; + }, + 'background-color': 'data(color)', + 'background-opacity': 0.9, + 'border-width': 2, + 'border-color': '#ffffff', + 'border-opacity': 0.8, + 'shape': 'ellipse', + 'transition-property': 'background-color, background-opacity, border-width', + 'transition-duration': '0.3s', + 'transition-timing-function': 'ease-in-out' + }) + .selector('edge') + .style({ + 'width': function(ele) { + return ele.data('strength') ? ele.data('strength') * 3 : 1; + }, + 'curve-style': 'bezier', + 'line-color': function(ele) { + const sourceColor = ele.source().data('color'); + return sourceColor || '#8a8aaa'; + }, + 'line-opacity': function(ele) { + return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; + }, + 'line-style': function(ele) { + const strength = ele.data('strength'); + if (!strength) return 'solid'; + if (strength <= 0.4) return 'dotted'; + if (strength <= 0.6) return 'dashed'; + return 'solid'; + }, + 'target-arrow-shape': 'none', + 'source-endpoint': '0% 50%', + 'target-endpoint': '100% 50%', + 'transition-property': 'line-opacity, width', + 'transition-duration': '0.3s', + 'transition-timing-function': 'ease-in-out' + }) + .update(); +} \ No newline at end of file diff --git a/systades.db b/systades.db new file mode 100644 index 0000000..019eb0c Binary files /dev/null and b/systades.db differ diff --git a/utils/__pycache__/db_rebuild.cpython-313.pyc b/utils/__pycache__/db_rebuild.cpython-313.pyc index e52baf0..49f3705 100644 Binary files a/utils/__pycache__/db_rebuild.cpython-313.pyc and b/utils/__pycache__/db_rebuild.cpython-313.pyc differ diff --git a/utils/__pycache__/db_test.cpython-313.pyc b/utils/__pycache__/db_test.cpython-313.pyc index 39ed7c3..bd43f5f 100644 Binary files a/utils/__pycache__/db_test.cpython-313.pyc and b/utils/__pycache__/db_test.cpython-313.pyc differ diff --git a/utils/__pycache__/server.cpython-313.pyc b/utils/__pycache__/server.cpython-313.pyc index 96ce9cd..ccc45a0 100644 Binary files a/utils/__pycache__/server.cpython-313.pyc and b/utils/__pycache__/server.cpython-313.pyc differ diff --git a/utils/__pycache__/user_manager.cpython-313.pyc b/utils/__pycache__/user_manager.cpython-313.pyc index d77e2b1..f8c0dcc 100644 Binary files a/utils/__pycache__/user_manager.cpython-313.pyc and b/utils/__pycache__/user_manager.cpython-313.pyc differ