← Bibliothek EQUILIBRIUM equilibrium-system.com

EQUILIBRIUM UI KIT Figma

EQUILIBRIUM UI KIT / FIGMA DOKUMENT. Lassen Sie uns dies nun als echtes UI-Dokument unter Figma und nicht nur als Beschreibung tun. Du kannst es dem Designer geben oder es selbst zusammenbauen. Wir geben:

EQUILIBRIUM UI KIT / FIGMA DOKUMENT

KUNSTSTRUKTUR DATEI FIGMA

Datei erstellen: EQUILIBRIUM / INTERFACE V1

Seiten (Seiten):

PAGE 00 — FOUNDATIONS

Farbsystem

Allgemein:

Zustände:

Oberflächen:

Typografie

Effekte

PAGE 01 — COMPONENTS

Node (Gegenstück). Maße:

  • S: 16px
  • M: 32px
  • L: 64px

Eigenschaften:

  • Farbe = Typ
  • Größe = Einfluss

Zustände:

  • normal
  • hovered
  • active
  • Überladen (Pulsation)

HyperEdge (Überraschung). Das ist keine Linie.

Visualisierung:

  • Weiche Wolke
  • Steigung
  • blur

Optionen

  • schwach
  • aktiv
  • überladen
  • konflikt

Flow Line

  • dünne Linie
  • Bewegungsanimation
  • richtung

Panel (Karte)

  • Rechts verwendet.

Größe

  • Breite: 360–420px

Enthält:

  • Titel
  • Datenblöcke
  • Schaltflächen

Button

Typen:

  • Primary
  • Secondary
  • Ghost

Mode Switch. Schalter:

  • Übersicht
  • Diagnostik
  • Szenario
  • Handeln

Timeline

  • horizontale Skala
  • draggable
  • Ereignismarkierungen

PAGE 02 — SCREENS

SCREEN 1 — GLOBAL DASHBOARD

Frame 1440 × 900 (desktop)

Layout

TOP BAR (64px)


LEFT PANEL (240px) | CENTER GRAPH | RIGHT PANEL (400px)


BOTTOM TIMELINE (120px)

TOP BAR

Von links → nach rechts:

  • Logo (EQUILIBRIUM)
  • Search
  • Level selector
  • Mode switch
  • AI button
  • Settings

LEFT PANEL

Sections:

  • Knotentypen
  • Arten von Verbindungen
  • Zustände

Verwenden Sie Checkboxen

CENTER — GRAPH AREA

Das ist der Hauptrahmen.

Innerhalb:

  • Nodes (Auto Layout disabled)
  • HyperEdges (under layer)
  • flows (top layer animation) - Verwenden Sie absolute Positionierung

RIGHT PANEL

Zustand 1 - Node Vie

  • Title
  • Type
  • Metrics
  • Connections
  • Buttons

Zustand 2 - Edge View

  • Bedeutung
  • Teilnehmer
  • Ressourcen
  • Effekt
  • Metriken

BOTTOM — TIMELINE

  • Zeitlinie
  • Veranstaltungspunkte
  • drag

PAGE 03 — FLOWS

Flow 1 - Knotenauswahl

Klick →

→ Hervorhebung

→ Öffnen des Bedienfelds

Flow 2 - Auswahl eines Hyperrippen

Klick →

→ Offenlegung des Protokolls

Flow 3 - Verbindung herstellen

  • Knoten auswählen
  • Drücken Sie .
  • Typ auswählen
  • Felder ausfüllen

Flow 4 - Diagnose

Moduswechsel →

→ Probleme hervorheben

Flow 5 — Skript

Änderung des Parameters →

→ Aktualisierung des Graphen

ARTIKELKOMPONENTEN (Minimum)

Wenn Sie schnell sein wollen:

Mach:

  • Node (1 Komponente)
  • Panel
  • Graph area
  • Top bar

Das reicht für den ersten Prototyp.

KRITISCHE REGELN (Erinnern)

  • Überlasten Sie den Bildschirm nicht
  • * Mach keine kleinen Elemente
  • * Machen Sie keine komplizierten Formen

3 Hauptprinzipien:

* Einfachheit

* Lesbarkeit

Das Gefühl eines lebenden Systems




Stack MVP

MVP

Gehen wir in den Code, damit wir ein Skelett von MVP haben, nicht eine Reihe schöner Wörter. Unten geben wir:

  • stapeln,
  • Projektstruktur,
  • Datenmodelle,
  • Front auf React,
  • Seite,
  • API,
  • Neo4j-schaltung,
  • Das erste Arbeitsszenario.

Für einen visuellen Graphen ist es jetzt sinnvoll, React Flow zu verwenden; In der aktuellen Version wird das Paket ?xyflow/react verwendet, und die benutzerdefinierten Knoten werden dort von den üblichen React-Komponenten hergestellt. Für Grafik-DB unter dieser natürlich liegt der offizielle neo4j-driver. 

Stack MVP

Frontend

  • Next.js / React
  • TypeScript
  • @xyflow/react
  • Zustand

Backend

  • Next.js Route Handlers oder Node/Express
  • TypeScript
  • Neo4j
  • neo4j-driver

Warum das so ist

  • React Flow ist bereits unter der node-based UI geschliffen.  
  • Neo4j ist gut geeignet, weil wir Knoten, Verbindungen, Beziehungstypen und Abfragen nach Spalte benötigen. Der offizielle JS-Treiber wird direkt unterstützt.  


Projektstruktur

equilibrium-mvp/

├─ app/

│  ├─ page.tsx

│  ├─ api/

│  │  ├─ graph/route.ts

│  │  ├─ node/route.ts

│  │  └─ edge/route.ts

├─ components/

│  ├─ graph/

│  │  ├─ EquilibriumCanvas.tsx

│  │  ├─ nodes/

│  │  │  ├─ MeaningNode.tsx

│  │  │  ├─ ActorNode.tsx

│  │  │  └─ EffectNode.tsx

│  │  ├─ panels/

│  │  │  ├─ LeftFilters.tsx

│  │  │  ├─ RightInspector.tsx

│  │  │  └─ BottomTimeline.tsx

│  │  └─ TopBar.tsx

├─ lib/

│  ├─ neo4j.ts

│  ├─ mapper.ts

│  └─ constants.ts

├─ store/

│  └─ equilibrium.store.ts

├─ types/

│  └─ equilibrium.ts

├─ cypher/

│  ├─ schema.cypher

│  ├─ seed.cypher

│  └─ queries.cypher

├─ package.json

└─ .env.local


Datentypen

Beginnen Sie nicht mit 12-Typen, sondern mit 6. Sonst ertrinken Sie.

types/equilibrium.ts

export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'process'

  | 'resource'

  | 'norm'

  | 'effect';


export type EQEdgeType =

  | 'intent'

  | 'cooperation'

  | 'production'

  | 'feedback'

  | 'evolution';


export type EQState =

  | 'harmony'

  | 'tension'

  | 'crisis'

  | 'growth'

  | 'transformation'

  | 'unknown';


export interface EQMetrics {

  trust?: number;

  activity?: number;

  load?: number;

  influence?: number;

  stability?: number;

  ethics?: number;

}


export interface EQNodeData {

  label: string;

  nodeType: EQNodeType;

  description?: string;

  state: EQState;

  metrics: EQMetrics;

  tags?: string[];

}


export interface EQNode {

  id: string;

  type: EQNodeType;

  position: { x: number; y: number };

  data: EQNodeData;

}


export interface EQEdgeData {

  label: string;

  edgeType: EQEdgeType;

  meaning?: string;

  process?: string;

  norm?: string;

  metric?: string;

  ethics?: string;

  intensity?: number;

  risk?: number;

  state: EQState;

}


export interface EQEdge {

  id: string;

  source: string;

  target: string;

  type: 'default' | 'hyper';

  data: EQEdgeData;

}


export interface GraphPayload {

  nodes: EQNode[];

  edges: EQEdge[];

}

Zustand store

store/equilibrium.store.ts

'use client';


import { create } from 'zustand';

import type { GraphPayload, EQNode, EQEdge, EQState, EQNodeType, EQEdgeType } from '@/types/equilibrium';


interface FiltersState {

  nodeTypes: EQNodeType[];

  edgeTypes: EQEdgeType[];

  states: EQState[];

}


interface EquilibriumStore {

  graph: GraphPayload;

  selectedNode: EQNode | null;

  selectedEdge: EQEdge | null;

  filters: FiltersState;


  setGraph: (graph: GraphPayload) => void;

  selectNode: (node: EQNode | null) => void;

  selectEdge: (edge: EQEdge | null) => void;

  updateFilters: (filters: Partial<FiltersState>) => void;

  upsertNode: (node: EQNode) => void;

  upsertEdge: (edge: EQEdge) => void;

}


export const useEquilibriumStore = create<EquilibriumStore>((set) => ({

  graph: { nodes: [], edges: [] },

  selectedNode: null,

  selectedEdge: null,

  filters: {

    nodeTypes: ['meaning', 'actor', 'process', 'resource', 'norm', 'effect'],

    edgeTypes: ['intent', 'cooperation', 'production', 'feedback', 'evolution'],

    states: ['harmony', 'tension', 'crisis', 'growth', 'transformation', 'unknown'],

  },


  setGraph: (graph) => set({ graph }),

  selectNode: (node) => set({ selectedNode: node, selectedEdge: null }),

  selectEdge: (edge) => set({ selectedEdge: edge, selectedNode: null }),

  updateFilters: (filters) =>

    set((state) => ({

      filters: { ...state.filters, ...filters },

    })),


  upsertNode: (node) =>

    set((state) => {

      const exists = state.graph.nodes.some((n) => n.id === node.id);

      return {

        graph: {

          ...state.graph,

          nodes: exists

            ? state.graph.nodes.map((n) => (n.id === node.id ? node : n))

            : [...state.graph.nodes, node],

        },

      };

    }),


  upsertEdge: (edge) =>

    set((state) => {

      const exists = state.graph.edges.some((e) => e.id === edge.id);

      return {

        graph: {

          ...state.graph,

          edges: exists

            ? state.graph.edges.map((e) => (e.id === edge.id ? edge : e))

            : [...state.graph.edges, edge],

        },

      };

    }),

}));


Grafische Komponente

React Flow rendert nodes, edges und verarbeitet die Interaktion über Colbacks wie onNodesChange. Das ist genau das, was wir für die Basis-Leinwand brauchen. 

components/graph/EquilibriumCanvas.tsx


'use client';


import { useEffect, useMemo } from 'react';

import {

  Background,

  Controls,

  MiniMap,

  ReactFlow,

  ReactFlowProvider,

  addEdge,

  useEdgesState,

  useNodesState,

  Connection,

  Edge,

  NodeMouseHandler,

  EdgeMouseHandler,

} from '@xyflow/react';

import '@xyflow/react/dist/style.css';


import { useEquilibriumStore } from '@/store/equilibrium.store';

import MeaningNode from './nodes/MeaningNode';

import ActorNode from './nodes/ActorNode';

import EffectNode from './nodes/EffectNode';


const nodeTypes = {

  meaning: MeaningNode,

  actor: ActorNode,

  effect: EffectNode,

};


function CanvasInner() {

  const { graph, selectNode, selectEdge } = useEquilibriumStore();


  const initialNodes = useMemo(() => graph.nodes, [graph.nodes]);

  const initialEdges = useMemo(

    () =>

      graph.edges.map((e) => ({

        ...e,

        animated: e.data.state === 'growth',

        style: {

          strokeWidth: 2,

        },

      })),

    [graph.edges]

  );


  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);

  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);


  useEffect(() => setNodes(initialNodes), [initialNodes, setNodes]);

  useEffect(() => setEdges(initialEdges), [initialEdges, setEdges]);


  const onConnect = (connection: Connection) => {

    setEdges((eds) =>

      addEdge(

        {

          ...connection,

          type: 'default',

          data: {

            label: 'New relation',

            edgeType: 'cooperation',

            state: 'unknown',

          },

        },

        eds

      )

    );

  };


  const onNodeClick: NodeMouseHandler = (_, node) => {

    selectNode(node as any);

  };


  const onEdgeClick: EdgeMouseHandler = (_, edge) => {

    selectEdge(edge as any);

  };


  return (

    <div style={{ width: '100%', height: '100%' }}>

      <ReactFlow

        nodes={nodes}

        edges={edges as Edge[]}

        onNodesChange={onNodesChange}

        onEdgesChange={onEdgesChange}

        onConnect={onConnect}

        onNodeClick={onNodeClick}

        onEdgeClick={onEdgeClick}

        nodeTypes={nodeTypes}

        fitView

      >

        <MiniMap />

        <Controls />

        <Background gap={20} />

      </ReactFlow>

    </div>

  );

}


export default function EquilibriumCanvas() {

  return (

    <ReactFlowProvider>

      <CanvasInner />

    </ReactFlowProvider>

  );

}

Kundenspezifische Knoten

Bei React Flow handelt es sich um normale React-Komponenten, die an nodeTypes übertragen werden. Es ist ein offiziell unterstützter Weg. 

components/graph/nodes/MeaningNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function MeaningNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(123, 97, 255, 0.22)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{ fontSize: 12, opacity: 0.7 }}>Bedeutung</div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Ethik: {data.metrics?.ethics ?? '-'}

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

components/graph/nodes/ActorNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function ActorNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(77, 163, 255, 0.18)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{ fontSize: 12, opacity: 0.7 }}>Subjekt</div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Vertrauen: {data.metrics?.trust ?? '-'}

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

components/graph/nodes/EffectNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function EffectNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(61, 220, 151, 0.18)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{ fontSize: 12, opacity: 0.7 }}>Effekt</div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Stabilität: {data.metrics?.Stabilität ?? '-'}

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

Startseite

app/page.tsx

'use client';


import { useEffect } from 'react';

import EquilibriumCanvas from '@/components/graph/EquilibriumCanvas';

import { useEquilibriumStore } from '@/store/equilibrium.store';


export default function HomePage() {

  const { setGraph, selectedNode, selectedEdge } = useEquilibriumStore();


  useEffect(() => {

    fetch('/api/graph')

      .then((r) => r.json())

      .then(setGraph)

      .catch(console.error);

  }, [setGraph]);


  return (

    <main

      style={{

        display: 'grid',

        gridTemplateColumns: '240px 1fr 400px',

        gridTemplateRows: '64px 1fr 120px',

        height: '100vh',

        background: '#0B0F1A',

        color: 'white',

      }}

    >

      <header style={{ gridColumn: '1 / 4', borderBottom: '1px solid #1F2633', display: 'flex', alignItems: 'center', padding: '0 16px' }}>

        EQUILIBRIUM

      </header>


      <aside style={{ borderRight: '1px solid #1F2633', padding: 16 }}>

        <h3>Filter</h3>

        <div>Knoten, Verbindungen, Zustände</div>

      </aside>


      <section style={{ position: 'relative' }}>

        <EquilibriumCanvas />

      </section>


      <aside style={{ borderLeft: '1px solid #1F2633', padding: 16 }}>

        {!selectedNode && !selectedEdge && <div>Wählen Sie einen Knoten oder eine Verbindung</div>}


        {selectedNode && (

          <>

            <h3>{selectedNode.data.label}</h3>

            <p>Typ: {selectedNode.data.nodeType}</p>

            <p>Zustand: {selectedNode.data.state}</p>

            <pre style={{ whiteSpace: 'pre-wrap' }}>

              {JSON.stringify(selectedNode.data.metrics, null, 2)}

            </pre>

          </>

        )}


        {selectedEdge && (

          <>

            <h3>{selectedEdge.data.label}</h3>

            <p>Typ: {selectedEdge.data.edgeType}</p>

            <p>Zustand: {selectedEdge.data.state}</p>

            <p>Sinn: {selectedEdge.data.meaning ? '-'}</p>

            <p>Ethik: {selectedEdge.data.ethics ? '-'}</p>

          </>

        )}

      </aside>


      <footer style={{ gridColumn: '1 / 4', borderTop: '1px solid #1F2633', padding: 16 }}>

        Timeline / Simulation

      </footer>

    </main>

  );

}


Neo4j Verbindung

Der offizielle Treiber wird über neo4j.driver(...) angeschlossen; In der Dokumentation wird auch empfohlen, die Zielbasis für die Leistung explizit anzugeben. 

lib/neo4j.ts

import neo4j from 'neo4j-driver';


const uri = process.env.NEO4J_URI!;

const username = process.env.NEO4J_USERNAME!;

const password = process.env.NEO4J_PASSWORD!;

const database = process.env.NEO4J_DATABASE || 'neo4j';


export const driver = neo4j.driver(uri, neo4j.auth.basic(username, password));


export { database };

API Graf Lesung

app/api/graph/route.ts

import { NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function GET() {

  const query = `

    MATCH (n:EQNode)

    OPTIONAL MATCH (n)-[r:RELATES_TO]->(m:EQNode)

    RETURN collect(DISTINCT {

      id: n.id,

      type: n.nodeType,

      position: { x: n.x, y: n.y },

      data: {

        label: n.label,

        nodeType: n.nodeType,

        description: n.description,

        state: n.state,

        metrics: {

          trust: n.trust,

          activity: n.activity,

          load: n.load,

          influence: n.influence,

          stability: n.stability,

          ethics: n.ethics

        },

        tags: n.tags

      }

    }) AS nodes,

    collect(DISTINCT CASE WHEN r IS NULL THEN NULL ELSE {

      id: r.id,

      source: startNode(r).id,

      target: endNode(r).id,

      type: 'default',

      data: {

        label: r.label,

        edgeType: r.edgeType,

        meaning: r.meaning,

        process: r.process,

        norm: r.norm,

        metric: r.metric,

        ethics: r.ethics,

        intensity: r.intensity,

        risk: r.risk,

        state: r.state

      }

    } END) AS edges

  `;


  const result = await driver.executeQuery(query, {}, { database });

  const record = result.records[0];


  const nodes = record.get('nodes');

  const edges = record.get('edges').filter(Boolean);


  return NextResponse.json({ nodes, edges });

}

Erstellen eines Knotens

app/api/node/route.ts

import { NextRequest, NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function POST(req: NextRequest) {

  const body = await req.json();


  const query = `

    CREATE (n:EQNode {

      id: $id,

      label: $label,

      nodeType: $nodeType,

      description: $description,

      state: $state,

      x: $x,

      y: $y,

      trust: $trust,

      activity: $activity,

      load: $load,

      influence: $influence,

      stability: $stability,

      ethics: $ethics,

      tags: $tags

    })

    RETURN n

  `;


  await driver.executeQuery(

    query,

    {

      id: body.id,

      label: body.data.label,

      nodeType: body.data.nodeType,

      description: body.data.description || '',

      state: body.data.state,

      x: body.position.x,

      y: body.position.y,

      trust: body.data.metrics?.trust ?? null,

      activity: body.data.metrics?.activity ?? null,

      load: body.data.metrics?.load ?? null,

      influence: body.data.metrics?.influence ?? null,

      stability: body.data.metrics?.stability ?? null,

      ethics: body.data.metrics?.ethics ?? null,

      tags: body.data.tags ?? [],

    },

    { database }

  );


  return NextResponse.json({ ok: true });

}

Erstellen einer Verbindung

app/api/edge/route.ts

import { NextRequest, NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function POST(req: NextRequest) {

  const body = await req.json();


  const query = `

    MATCH (a:EQNode {id: $source})

    MATCH (b:EQNode {id: $target})

    CREATE (a)-[r:RELATES_TO {

      id: $id,

      label: $label,

      edgeType: $edgeType,

      meaning: $meaning,

      process: $process,

      norm: $norm,

      metric: $metric,

      ethics: $ethics,

      intensity: $intensity,

      risk: $risk,

      state: $state

    }]->(b)

    RETURN r

  `;


  await driver.executeQuery(

    query,

    {

      id: body.id,

      source: body.source,

      target: body.target,

      label: body.data.label,

      edgeType: body.data.edgeType,

      meaning: body.data.meaning || '',

      process: body.data.process || '',

      norm: body.data.norm || '',

      metric: body.data.metric || '',

      ethics: body.data.ethics || '',

      intensity: body.data.intensity ?? null,

      risk: body.data.risk ?? null,

      state: body.data.state,

    },

    { database }

  );


  return NextResponse.json({ ok: true });

}

Cypher-Schema

cypher/schema.cypher

CREATE CONSTRAINT eqnode_id IF NOT EXISTS

FOR (n:EQNode)REQUIRE n.id IS UNIQUE;

cypher/seed.cypher

CREATE (:EQNode {

  id: 'meaning-1',

  label: 'Entwicklung von Kompetenzen',

  nodeType: 'meaning',

  state: 'growth',

  x: 100,

  y: 120,

  ethics: 0.9

});


CREATE (:EQNode {

  id: 'actor-1',

  label: 'Kooperative FENIX',

  nodeType: 'actor',

  state: 'harmony',

  x: 420,

  y: 180,

  trust: 0.82,

  activity: 0.77

});


CREATE (:EQNode {

  id: 'effect-1',

  label: 'Beschäftigungswachstum',

  nodeType: 'effect',

  state: 'growth',

  x: 760,

  y: 200,

  stability: 0.71

});


MATCH (a:EQNode {id: 'meaning-1'})

MATCH (b:EQNode {id: 'actor-1'})

CREATE (a)-[:RELATES_TO {

  id: 'edge-1',

  Label: 'Absicht',

  edgeType: 'intent',

  meaning: 'Läuft den Bildungskreislauf',

  ethics: 'Verfügbarkeit',

  state: 'growth',

  intensity: 0.8

}]->(b);


MATCH (a:EQNode {id: 'actor-1'})

MATCH (b:EQNode {id: 'effect-1'})

CREATE (a)-[:RELATES_TO {

  id: 'edge-2',

  label: 'Feedback',

  edgeType: 'feedback',

  meaning: 'Bewertung des Ergebnisses des Programms',

  metric: 'Arbeitsindex',

  state: 'harmony',

  intensity: 0.7

}]->(b);

Beispiel JSON für die Front

  "nodes": [

    {

      "id": "meaning-1",

      "type": "meaning",

      "position": { "x": 100, "y": 120 },

      "data": {

        "Label": "Entwicklung von Kompetenzen",

        "nodeType": "meaning",

        "state": "growth",

        "metrics": { "ethics": 0.9 }

      }

    },

    {

      "id": "actor-1",

      "type": "actor",

      "position": { "x": 420, "y": 180 },

      "data": {

        "label": "Kooperative FENIX",

        "nodeType": "actor",

        "state": "harmony",

        "metrics": { "trust": 0.82, "activity": 0.77 }

      }

    }

  ],

  "edges": [

    {

      "id": "edge-1",

      "source": "meaning-1",

      "target": "actor-1",

      "type": "default",

      "data": {

        "label": "Absicht",

        "edgeType": "intent",

        "meaning": "Start des Bildungskreislaufs",

        "state": "growth",

        "intensity": 0.8

      }

    }

  ]

}

Was tun mit den Hyperrippen wirklich?

Das ist ein wichtiger Punkt. React Flow funktioniert mit normalen Edges zwischen zwei Knoten. Dies ist sein Basismodell als Flowgraph UI. Also für MVP tun Sie Folgendes:

Option A - Praktisch

  • Hyperrebro als eigenständiger Knoten vom Typ process oder protocol speichern
  • mit ihm die Teilnehmer der üblichen edges

Ich meine:

Meaning -> ProtocolHub -> Actor

Resource -> ProtocolHub

Norm -> ProtocolHub

ProtocolHub -> Effect

Das ist eine ehrliche Entscheidung. Es bricht die UI nicht und ermöglicht es Ihnen, eine mehrdimensionale Verbindung zu zeigen.

Typ dafür

export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'process'

  | 'resource'

  | 'norm'

  | 'effect'

  | 'protocol';

Und dann ist der "Hyperrib" auf Datenebene ein Protokollknoten mit einer Reihe eingehender / ausgehender Beziehungen. Das ist der beste Start.

Erstes Arbeitsszenario

Nicht sprühen. Mache ein Szenario:

Szenario: "Kooperative Bildung"

Knoten:

  • Bedeutung: Entwicklung von Kompetenzen
  • Thema: Genossenschaft
  • Ressource: Stiftung
  • Norm: Geschäftsordnung
  • Protokoll: Bildungsprogramm
  • Wirkung: Beschäftigungswachstum

Kontakte:

  • meaning → protocol
  • actor → protocol
  • resource → protocol
  • norm → protocol
  • protocol → effect

Hier ist es MVP, das gezeigt werden kann.

Befehle zum Starten

package.json

Abhängigkeiten

{

  "dependencies": {

    "@xyflow/react": "^12",

    "neo4j-driver": "^6",

    "next": "^15",

    "react": "^19",

    "react-dom": "^19",

    "zustand": "^5"

  },

  "devDependencies": {

    "@types/node": "^22",

    "@types/react": "^19",

    "typescript": "^5"

  }

}

Das Paket ?xyflow/react ist der aktuelle Name für React Flow und Neo4JS Driver ist aktuell im Einsatz 6.x. 

.env.local

NEO4J_URI=neo4j://localhost

NEO4J_USERNAME=neo4j

NEO4J_PASSWORD=your_password

NEO4J_DATABASE=neo4j

Was du danach noch haben wirst

Nach der Einführung dieses Rahmens haben Sie:

  • Der interaktive Graf,
  • kundenspezifische Knoten,
  • rechtes Inspektor-Panel,
  • Herunterladen von Daten von Neo4j,
  • Erstellen von Knoten und Verbindungen über API,
  • Basis unter Szenarien, Diagnose und Simulation.

Das ist kein Konzept mehr. Es ist bereits ein lebendiger Produktrahmen.


Im Folgenden gebe ich den bereits montierten Rahmen des Systems: Was ist jedes Modul, welche benutzerdefinierten Knoten benötigt werden, wie man einen interaktiven Graphen anordnet und welchen Code man sofort in das System einfügen kann MVP. Für React Flow ist das aktuelle Paket ?xyflow/react; die benutzerdefinierten Knoten werden dort wie normale React-Komponenten hergestellt, über nodeTypes übertragen und die Verbindungspunkte werden über <Handle />. Für Neo4j ist jetzt der normale Weg - verwenden Sie den offiziellen JS-Treiber und driver.executeQuery() mit Parametern und expliziter Datenbankangabe. 

Definitionen von Systemmodulen

Wir empfehlen für die erste Arbeitsversion, 10 Module zu fixieren.

Graph Core

Das Herz des Systems. Speichert Knoten, Verbindungen, Benutzerauswahl, Modi, Filter, Layout und Schnittstellenereignisse. Was macht:

  • Der aktuelle Zustand des Grafen
  • aktualisiert Knoten und Verbindungen
  • UI sagt, dass ausgewählt
  • Startet eine Neuzeichnung

Node Registry

Verzeichnis der Knotentypen. Was macht:

  • bindet node.type → React-Komponente
  • legt fest, wie meaning, actor, resource, protocol und so weiter rendern
  • ermöglicht die Erweiterung des Systems ohne Umschreiben der Leinwand

Edge-Registrierung. Register der Beziehungen. Was macht:

  • bestimmt das visuelle und das Verhalten der Beziehungen
  • setzt Stile für intent, cooperation, feedback, evolution
  • kann später benutzerdefinierte Rippen mit Signaturen und Risikoindikatoren enthalten

Protocol Engine

Das ist das Schlüsselmodul EQUILIBRIUM. Was macht:

  • sammelt mehrdimensionale Aktion aus mehreren Entitäten
  • tatsächlich realisiert “Hyperrebro” als separates Protokoll-Knoten
  • verbindet Bedeutung, Akteure, Ressourcen, Normen und Effekte in einem Kreis

Für MVP ist dies der beste Weg, da React Flow von Natur aus mit gewöhnlichen Rippen zwischen zwei Knoten arbeitet. 

Das Inspector Panel. Rechts. Was macht:

  • Zeigt den Pass des ausgewählten Knotens an
  • Zeigt den Pass der ausgewählten Verbindung an
  • ermöglicht die Bearbeitung von Attributen
  • Zeigt Metriken, Risiken, Ethik, Belastung

Filter & Ansichten. Linke Tafel. Was macht:

  • Filtert nach Knotentypen
  • Filtert nach Zuständen
  • Filtert nach Verknüpfungstypen
  • schaltet Modi: Übersicht, Diagnose, Skript, Aktion

Timeline & Veranstaltungen. Untere Leiste. Was macht:

  • Zeigt Ereignisse in der Zeit
  • Der Zustand des Grafen
  • In Zukunft werden Szenarien simuliert.

Die Diagnose-Engine. Systemdiagnose. Was macht:

  • Suche nach überlasteten Knoten
  • Findet Konfliktbeziehungen
  • Hervorhebung von Krisengebieten
  • berechnet "Löcher" in der Kontur: Zum Beispiel gibt es einen Prozess, aber es gibt keine Norm

Persistence Layer. Speicherschicht.. Was macht:

  • liest und schreibt einen Graphen in Neo4j
  • Speichert Knotenpositionen
  • Behält Metriken und Zustände bei
  • gibt JSON an die Front

AI/Rule Layer. Eine Reihe von Empfehlungen. Was macht:

  • bietet neue Verbindungen
  • warnt vor Risiken
  • einfache Heuristiken
  • erklärt, warum das System den Knoten als Krisenpunkt markiert

Typen von Systemknoten

Für den Start sind keine 30-Typen erforderlich. Wir brauchen 7.

export type EQNodeType =

  ? 'meaning' // Bedeutung

  ? 'actor' // Subjekt

  ? 'resource' // Ressource

  , 'normal' // Norm

  ?'effect' // Effekt

  , 'protocol' // Hyperrebro als Knoten-Hub

  ?'metric'; // Metrik


Meaning. Sinn, Mission, Wert. Die Antwort auf die Frage: "Warum?"

Actor. Mensch, Organisation, Agent, Genossenschaft. Beantwortet die Frage: "Wer".

Ressourcen. Geld, Fonds, Daten, Energie, Zeit. Er antwortet auf die Frage: "Auf Kosten von was."

Norm. Regel, Vertrag, Gesetz. Die Antwort auf die Frage: "Welche Einschränkungen?"

Effekt Ergebnis. Er antwortet auf die Frage: "Was ist passiert?"

Protokoll. Zentrales Arbeitsmodul. Das ist die Verwirklichung einer multidimensionalen Verbindung. Er sammelt die anderen Wesen um sich herum.

Metric. Ergebnis-Messgerät: Vertrauensindex, Nachhaltigkeitsfaktor, Beschäftigungsindex.

Arten von Verbindungen

export type EQEdgeType =

  | 'intent'

  | 'input'

  | 'constraint'

  | 'output'

  | 'feedback'

  | 'evolution';

Bedeutung:

  • intent - Bedeutung ist im Protokoll enthalten
  • input - eine Ressource oder ein Schauspieler nimmt am Protokoll teil
  • constraint - die Norm schränkt das Protokoll ein
  • output - Protokoll erzeugt Effekt
  • feedback - der Effekt wirkt sich wieder aus
  • Evolution - Zustandsänderung im Laufe der Zeit

Grundlegende Datentypen

types/equilibrium.ts

export type EQState =

  | 'harmony'

  | 'tension'

  | 'crisis'

  | 'growth'

  | 'transformation'

  | 'unknown';


export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'resource'

  | 'norm'

  | 'effect'

  | 'protocol'

  | 'metric';


export type EQEdgeType =

  | 'intent'

  | 'input'

  | 'constraint'

  | 'output'

  | 'feedback'

  | 'evolution';


export interface EQMetrics {

  trust?: number;

  load?: number;

  activity?: number;

  influence?: number;

  stability?: number;

  ethics?: number;

  value?: number;

}


export interface EQNodeData {

  label: string;

  nodeType: EQNodeType;

  description?: string;

  state: EQState;

  metrics: EQMetrics;

  tags?: string[];

}


export interface EQNode {

  id: string;

  type: EQNodeType;

  position: { x: number; y: number };

  data: EQNodeData;

}


export interface EQEdgeData {

  label: string;

  edgeType: EQEdgeType;

  state: EQState;

  meaning?: string;

  risk?: number;

  intensity?: number;

}


export interface EQEdge {

  id: string;

  source: string;

  target: string;

  type?: string;

  data: EQEdgeData;

}


export interface EQGraph {

  nodes: EQNode[];

  edges: EQEdge[];

}

Benutzerdefinierte Knoten: Beschreibung

In React Flow ist ein benutzerdefinierter Knoten eine normale React-Komponente, die NodeProps erhält. Der Knoten kann mit mehreren Handle, mit interaktiven Kontrollen und eigener UI hergestellt werden. Es ist ein offiziell unterstütztes Modell. Unten ist eine Reihe von Knoten, die bereits dem System ähnlich sind.

MeaningNode. Ein Sinnknoten. Normalerweise klein, hell, mit einem Eingang und einem Ausgang.

ActorNode Subjektknoten. Zeigt Vertrauen, Aktivität und Belastung.

ResourceNode. Der Ressourcenknoten. In der Regel kompakt, mit einem Indikator für die Verfügbarkeit.

NormNode. Normativer Knoten. Zeigt die Art der Einschränkung an.

Effektnode. Ergebnisknoten. Zeigt Einfluss und Nachhaltigkeit.

MetricNode. Der Messknoten. Wird als Bewertungsindikator verwendet.

ProtokollNode. Das Wichtigste. Zentrales Drehkreuz. Es hat mehrere Verbindungspunkte:

  • Linke Bedeutung
  • Schauspieler von oben
  • Ressourcen von unten
  • Rechts Effekte
  • Separates Handle für Normen
  • Separates Handle für Metriken

Gemeinsamer Basiscontainer

components/graph/nodes/NodeShell.tsx

'use client';


import { ReactNode } from 'react';


type Props = {

  title: string;

  subtitle: string;

  children?: ReactNode;

  glow?: string;

};


export default function NodeShell({ title, subtitle, children, glow }: Props) {

  return (

    <div

      style={{

        minWidth: 190,

        borderRadius: 18,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: glow || '0 0 18px rgba(255,255,255,0.08)',

        color: '#fff',

      }}

    >

      <div

        className="drag-handle"

        style={{

          fontSize: 11,

          opacity: 0.65,

          marginBottom: 6,

          cursor: 'grab',

        }}

      >

        {subtitle}

      </div>

      <div style={{ fontWeight: 700, fontSize: 15 }}>{title}</div>

      <div style={{ marginTop: 10 }}>{children}</div>

    </div>


Benutzerdefinierte Knoten: Code

MeaningNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function MeaningNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Sinn"

      glow="0 0 24px rgba(123, 97, 255, 0.24)"

    >

      <div style={{ fontSize: 12, opacity: 0.85 }}>

        Ethik: {data.metrics?.ethics ?? '-'}

      </div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

ActorNode.tsx


'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function ActorNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Subjekt"

      glow="0 0 24px rgba(77, 163, 255, 0.20)"

    >

      <div style={{ fontSize: 12 }}>Vertrauen: {data.metrics?.trust ?? '-'}</div>

      <div style={{ fontSize: 12 }}>Aktivität: {data.metrics?.activity ?? '-'}</div>

      <div style={{ fontSize: 12 }}>Belastung: {data.metrics?.load ?? '-'}</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

ResourceNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function ResourceNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Ressource"

      glow="0 0 24px rgba(255, 200, 87, 0.18)"

    >

      <div style={{ fontSize: 12 }}>Verfügbarkeit: {data.metrics?.value ?? '-'}</div>

      <div style={{ fontSize: 12 }}>Stabilität: {data.metrics?.Stabilität ?? '-'}</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

NormNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function NormNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Norm"

      glow="0 0 24px rgba(255,255,255,0.12)"

    >

      <div style={{ fontSize: 12 }}>{data.description || 'Regel / Einschränkung'}</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

EffectNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function EffectNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Effekt"

      glow="0 0 24px rgba(61, 220, 151, 0.22)"

    >

      <div style={{ fontSize: 12 }}>Einfluss: {data.metrics?.influence ?? '-'}</div>

      <div style={{ fontSize: 12 }}>Nachhaltigkeit: {data.metrics?.stabilität ?? '-'}</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

MetricNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function MetricNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Metrik"

      glow="0 0 24px rgba(80, 180, 255, 0.16)"

    >

      <div style={{ fontSize: 12 }}>Wert: {data.metrics?.value ?? '-'}</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

ProtokollNode.tsx. Das ist der Hauptknoten.

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function ProtocolNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 260,

        borderRadius: 20,

        padding: 14,

        background: '#161d2d',

        border: '1px solid #2a3550',

        boxShadow: '0 0 28px rgba(123, 97, 255, 0.28)',

        color: '#fff',

      }}

    >

      <div className="drag-handle" style={{ fontSize: 11, opacity: 0.65, marginBottom: 6 }}>

        Protokoll / Hyperrebro

      </div>


      <div style={{ fontSize: 16, fontWeight: 800 }}>{data.label}</div>


      <div style={{ marginTop: 10, fontSize: 12, opacity: 0.9 }}>

        {data.description } 'Mehrdimensionaler Aktionskreis'}

      </div>


      <div

        style={{

          display: 'grid',

          gridTemplateColumns: '1fr 1fr',

          gap: 6,

          marginTop: 10,

          fontSize: 12,

        }}

      >

        <div>Ethik: {data.metrics?.ethics ?? '-'}</div>

        <div>Last: {data.metrics?.load ?? '-'}</div>

        <div>Stabilität: {data.metrics?.Stabilität ?? '-'}</div>

        <div>Aktivität: {data.metrics?.activity ?? '-'}</div>

      </div>


      <Handle type="target" position={Position.Left} id="meaning" style={{ top: '20%' }} />

      <Handle type="target" position={Position.Top} id="actors" style={{ left: '35%' }} />

      <Handle type="target" position={Position.Bottom} id="resources" style={{ left: '35%' }} />

      <Handle type="target" position={Position.Left} id="norms" style={{ top: '75%' }} />


      <Handle type="source" position={Position.Right} id="effects" style={{ top: '35%' }} />

      <Handle type="source" position={Position.Right} id="metrics" style={{ top: '75%' }} />

    </div>

  );


Ein Multi-Handle-Knoten entspricht vollständig dem React Flow-Modell, bei dem Sie beliebig viele source/target connection points in benutzerdefinierten Knoten haben können. 

Verzeichnis der benutzerdefinierten Knoten

components/graph/nodes/index.ts

import MeaningNode from './MeaningNode';

import ActorNode from './ActorNode';

import ResourceNode from './ResourceNode';

import NormNode from './NormNode';

import EffectNode from './EffectNode';

import MetricNode from './MetricNode';

import ProtocolNode from './ProtocolNode';


export const nodeTypes = {

  meaning: MeaningNode,

  actor: ActorNode,

  resource: ResourceNode,

  norm: NormNode,

  effect: EffectNode,

  metric: MetricNode,

  protocol: ProtocolNode,

};

Interaktiver Graphen: Leinwand-Code

<ReactFlow /> ist die Hauptkomponente, die Knoten und Rippen rendern, Interaktionen verarbeiten und mit state arbeiten. Dies ist der offizielle zentrale Punkt der Bibliothek. 

components/graph/EquilibriumCanvas.tsx


'use client';


import { useEffect, useMemo, useState } from 'react';

import {

  ReactFlow,

  ReactFlowProvider,

  Background,

  MiniMap,

  Controls,

  addEdge,

  useNodesState,

  useEdgesState,

  Connection,

  Edge,

  NodeMouseHandler,

  EdgeMouseHandler,

  BackgroundVariant,

} from '@xyflow/react';

import '@xyflow/react/dist/style.css';


import { nodeTypes } from './nodes';

import { EQGraph } from '@/types/equilibrium';


type Props = {

  graph: EQGraph;

  onSelectNode?: (node: any) => void;

  onSelectEdge?: (edge: any) => void;

};


function edgeStyleByState(state?: string) {

  switch (state) {

    case 'growth':

      return { stroke: '#4DA3FF', strokeWidth: 2.5 };

    case 'crisis':

      return { stroke: '#FF4D4F', strokeWidth: 2.5 };

    case 'harmony':

      return { stroke: '#3DDC97', strokeWidth: 2.5 };

    case 'tension':

      return { stroke: '#FFC857', strokeWidth: 2.5 };

    default:

      return { stroke: '#76839a', strokeWidth: 1.8 };

  }

}


function CanvasInner({ graph, onSelectNode, onSelectEdge }: Props) {

  const initialNodes = useMemo(() => graph.nodes, [graph.nodes]);


  const initialEdges = useMemo(

    () =>

      graph.edges.map((e) => ({

        ...e,

        animated: e.data.state === 'growth' || e.data.edgeType === 'feedback',

        style: edgeStyleByState(e.data.state),

        label: e.data.label,

      })),

    [graph.edges]

  );


  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);

  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges as Edge[]);

  const [selectedId, setSelectedId] = useState<string | null>(null);


  useEffect(() => setNodes(initialNodes), [initialNodes, setNodes]);

  useEffect(() => setEdges(initialEdges as Edge[]), [initialEdges, setEdges]);


  const onConnect = (connection: Connection) => {

    setEdges((eds) =>

      addEdge(

        {

          ...connection,

          type: 'smoothstep',

          data: {

            Label: 'Neue Verbindung',

            edgeType: 'input',

            state: 'unknown',

          },

          style: edgeStyleByState('unknown'),

        },

        eds

      )

    );

  };


  const handleNodeClick: NodeMouseHandler = (_, node) => {

    setSelectedId(node.id);

    onSelectNode?.(node);

  };


  const handleEdgeClick: EdgeMouseHandler = (_, edge) => {

    setSelectedId(edge.id);

    onSelectEdge?.(edge);

  };


  return (

    <div style={{ width: '100%', height: '100%' }}>

      <ReactFlow

        nodes={nodes.map((n) => ({

          ...n,

          selected: n.id === selectedId,

          dragHandle: '.drag-handle',

        }))}

        edges={edges}

        nodeTypes={nodeTypes}

        onNodesChange={onNodesChange}

        onEdgesChange={onEdgesChange}

        onConnect={onConnect}

        onNodeClick={handleNodeClick}

        onEdgeClick={handleEdgeClick}

        fitView

      >

        <MiniMap />

        <Controls />

        <Background variant={BackgroundVariant.Dots} gap={20} size={1} />

      </ReactFlow>

    </div>

  );

}


export default function EquilibriumCanvas(props: Props) {

  return (

    <ReactFlowProvider>

      <CanvasInner {...props} />

    </ReactFlowProvider>

  );

}

Beispiel für einen interaktiven Bildschirm

app/page.tsx

'use client';


import { useEffect, useState } from 'react';

import EquilibriumCanvas from '@/components/graph/EquilibriumCanvas';

import { EQGraph } from '@/types/equilibrium';


export default function HomePage() {

  const [graph, setGraph] = useState<EQGraph>({ nodes: [], edges: [] });

  const [selectedNode, setSelectedNode] = useState<any>(null);

  const [selectedEdge, setSelectedEdge] = useState<any>(null);


  useEffect(() => {

    fetch('/api/graph')

      .then((r) => r.json())

      .then(setGraph)

      .catch(console.error);

  }, []);


  return (

    <main

      style={{

        display: 'grid',

        gridTemplateColumns: '260px 1fr 400px',

        gridTemplateRows: '64px 1fr 120px',

        height: '100vh',

        background: '#0B0F1A',

        color: '#fff',

      }}

    >

      <header

        style={{

          gridColumn: '1 / 4',

          borderBottom: '1px solid #1F2633',

          display: 'flex',

          alignItems: 'center',

          padding: '0 16px',

          fontWeight: 800,

        }}

      >

        EQUILIBRIUM

      </header>


      <aside style={{ borderRight: '1px solid #1F2633', padding: 16 }}>

        <h3>Filter</h3>

        <div>Typen von Knoten</div>

        <div>Zustände</div>

        <div>Arten von Links</div>

      </aside>


      <section style={{ position: 'relative' }}>

        <EquilibriumCanvas

          graph={graph}

          onSelectNode={(node) => {

            setSelectedEdge(null);

            setSelectedNode(node);

          }}

          onSelectEdge={(edge) => {

            setSelectedNode(null);

            setSelectedEdge(edge);

          }}

        />

      </section>


      <aside style={{ borderLeft: '1px solid #1F2633', padding: 16, overflow: 'auto' }}>

        {!selectedNode && !selectedEdge && <div>Wählen Sie einen Knoten oder eine Verbindung</div>}


        {selectedNode && (

          <>

            <h3>{selectedNode.data.label}</h3>

            <div>Typ: {selectedNode.data.nodeType}</div>

            <div>Zustand: {selectedNode.data.state}</div>

            <div style={{ marginTop: 12, fontSize: 13, opacity: 0.9 }}>

              {selectedNode.data.description } 'Keine Beschreibung'

            </div>

            <pre style={{ marginTop: 12, fontSize: 12, whiteSpace: 'pre-wrap' }}>

              {JSON.stringify(selectedNode.data.metrics, null, 2)}

            </pre>

          </>

        )}


        {selectedEdge && (

          <>

            <h3>{selectedEdge.data.label}</h3>

            <div>Typ: {selectedEdge.data.edgeType}</div>

            <div>Zustand: {selectedEdge.data.state}</div>

            <div>Bedeutung: {selectedEdge.data.meaning ’-'}</div>

            <div>Risiko: {selectedEdge.data.risk ?? '-'}</div>

            <div>Intensität: {selectedEdge.data.intensity ?? '-'}</div>

          </>

        )}

      </aside>


      <footer

        style={{

          gridColumn: '1 / 4',

          borderTop: '1px solid #1F2633',

          padding: 16,

        }}

      >

        Timeline / Events / Simulation

      </footer>

    </main>

  );

}


Beispiel-Graph für EQUILIBRIUM. Das ist keine Abstraktion mehr, sondern ein Arbeitsszenario.

lib/mockGraph.ts

import { EQGraph } from '@/types/equilibrium';


export const mockGraph: EQGraph = {

  nodes: [

    {

      id: 'meaning-1',

      type: 'meaning',

      position: { x: 60, y: 220 },

      data: {

        label: 'Entwicklung von Kompetenzen',

        nodeType: 'meaning',

        Beschreibung: "Wachstum des menschlichen Potenzials der Region",

        state: 'growth',

        metrics: { ethics: 0.92 },

      },

    },

    {

      id: 'actor-1',

      type: 'actor',

      position: { x: 280, y: 60 },

      data: {

        label: 'Kooperative FENIX',

        nodeType: 'actor',

        state: 'harmony',

        metrics: { trust: 0.82, activity: 0.77, load: 0.42 },

      },

    },

    {

      id: 'resource-1',

      type: 'resource',

      position: { x: 280, y: 360 },

      data: {

        label: 'Bildungsfonds',

        nodeType: 'resource',

        state: 'harmony',

        metrics: { value: 0.76, stability: 0.66 },

      },

    },

    {

      id: 'norm-1',

      type: 'norm',

      position: { x: 280, y: 500 },

      data: {

        label: 'Programmregeln',

        nodeType: 'norm',

        Beschreibung: "Transparenz der Auswahl und Verfügbarkeit der Teilnahme"

        state: 'harmony',

        metrics: {},

      },

    },

    {

      id: 'protocol-1',

      type: 'protocol',

      position: { x: 640, y: 230 },

      data: {

        label: 'Protokoll der kooperativen Bildung',

        nodeType: 'protocol',

        Beschreibung: 'Umriss: Ausbildung → Teilnahme → Beschäftigung >>,

        state: 'growth',

        metrics: {

          ethics: 0.9,

          load: 0.58,

          stability: 0.71,

          activity: 0.79,

        },

      },

    },

    {

      id: 'effect-1',

      type: 'effect',

      position: { x: 1020, y: 180 },

      data: {

        label: 'Beschäftigungswachstum',

        nodeType: 'effect',

        state: 'growth',

        metrics: { influence: 0.74, stability: 0.69 },

      },

    },

    {

      id: 'metric-1',

      type: 'metric',

      position: { x: 1020, y: 340 },

      data: {

        label: 'Arbeitsindex',

        nodeType: 'metric',

        state: 'harmony',

        metrics: { value: 0.63 },

      },

    },

  ],

  edges: [

    {

      id: 'e1',

      source: 'meaning-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        Label: 'Absicht',

        edgeType: 'intent',

        state: 'growth',

        meaning: 'Sinn ist im Protokoll',

        intensity: 0.88,

      },

    },

    {

      id: 'e2',

      source: 'actor-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        Label: 'Teilnahme',

        edgeType: 'input',

        state: 'harmony',

        meaning: 'Das Subjekt führt das Protokoll aus',

        intensity: 0.72,

      },

    },

    {

      id: 'e3',

      source: 'resource-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        Label: 'Ressource',

        edgeType: 'input',

        state: 'harmony',

        intensity: 0.68,

      },

    },

    {

      id: 'e4',

      source: 'norm-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Einschränkung',

        edgeType: 'constraint',

        state: 'harmony',

        meaning: 'Norm begrenzt und führt',

      },

    },

    {

      id: 'e5',

      source: 'protocol-1',

      target: 'effect-1',

      type: 'smoothstep',

      data: {

        Label: 'Ergebnis',

        edgeType: 'output',

        state: 'growth',

        intensity: 0.81,

      },

    },

    {

      id: 'e6',

      source: 'protocol-1',

      target: 'metric-1',

      type: 'smoothstep',

      data: {

        label: 'Messung',

        edgeType: 'output',

        state: 'harmony',

      },

    },

    {

      id: 'e7',

      source: 'effect-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Feedback',

        edgeType: 'feedback',

        state: 'harmony',

        risk: 0.21,

      },

    },

  ],

};

Neo4j: Speicherschaltung

Der offizielle JS-Treiber Neo4j empfiehlt die Verwendung parametrisierter Abfragen, und executeQuery() erstellt die Transaktion selbst. Es ist auch nützlich, Datenbasis immer explizit anzugeben, um überschüssigen Overhead zu reduzieren. 

lib/neo4j.ts

import neo4j from 'neo4j-driver';


const uri = process.env.NEO4J_URI!;

const username = process.env.NEO4J_USERNAME!;

const password = process.env.NEO4J_PASSWORD!;

const database = process.env.NEO4J_DATABASE || 'neo4j';


export const driver = neo4j.driver(uri, neo4j.auth.basic(username, password));

export { database };

cypher/schema.cypher

CREATE CONSTRAINT eqnode_id IF NOT EXISTS

FOR (n:EQNode)

REQUIRE n.id IS UNIQUE;

app/api/graph/route.ts

import { NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function GET() {

  const query = `

    MATCH (n:EQNode)

    OPTIONAL MATCH (n)-[r:RELATES_TO]->(m:EQNode)

    RETURN

      collect(DISTINCT {

        id: n.id,

        type: n.nodeType,

        position: { x: n.x, y: n.y },

        data: {

          label: n.label,

          nodeType: n.nodeType,

          description: n.description,

          state: n.state,

          metrics: {

            trust: n.trust,

            load: n.load,

            activity: n.activity,

            influence: n.influence,

            stability: n.stability,

            ethics: n.ethics,

            value: n.value

          }

        }

      }) AS nodes,

      collect(DISTINCT CASE WHEN r IS NULL THEN NULL ELSE {

        id: r.id,

        source: startNode(r).id,

        target: endNode(r).id,

        type: 'smoothstep',

        data: {

          label: r.label,

          edgeType: r.edgeType,

          state: r.state,

          meaning: r.meaning,

          risk: r.risk,

          intensity: r.intensity

        }

      } END) AS edges

  `;


  const result = await driver.executeQuery(query, {}, { database });

  const record = result.records[0];


  const nodes = record.get('nodes');

  const edges = record.get('edges').filter(Boolean);


  return NextResponse.json({ nodes, edges });

}


Diagnose: Grundregeln. Das ist keine KI, sondern harte Logik. Mit ihr muss man anfangen.

lib/diagnostics.ts

import { EQGraph } from '@/types/equilibrium';


export interface DiagnosticItem {

  type: 'warning' | 'error' | 'info';

  targetId: string;

  message: string;

}


export function runDiagnostics(graph: EQGraph): DiagnosticItem[] {

  const result: DiagnosticItem[] = [];


  for (const node of graph.nodes) {

    if (node.type === 'protocol') {

      const incoming = graph.edges.filter((e) => e.target === node.id);

      const outgoing = graph.edges.filter((e) => e.source === node.id);


      const hasMeaning = incoming.some((e) => e.data.edgeType === 'intent');

      const hasActor = incoming.some((e) => e.source.startsWith('actor-'));

      const hasResource = incoming.some((e) => e.source.startsWith('resource-'));

      const hasNorm = incoming.some((e) => e.data.edgeType === 'constraint');

      const hasOutput = outgoing.some((e) => e.data.edgeType === 'output');


      if (!hasMeaning) {

        result.push({

          type: 'error',

          targetId: node.id,

          Message: "Das Protokoll hat keine semantische Grundlage"

        });

      }


      if (!hasActor) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: "Das Protokoll hat kein ausführendes Subjekt",

        });

      }


      if (!hasResource) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'Das Protokoll hat keine Ressourcenanmeldung',

        });

      }


      if (!hasNorm) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: "Das Protokoll hat keine regulatorischen Einschränkungen.",

        });

      }


      if (!hasOutput) {

        result.push({

          type: 'error',

          targetId: node.id,

          Message: "Das Protokoll hat kein Ergebnis",

        });

      }


      if ((node.data.metrics.load ?? 0) > 0.85) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'Protokoll überlastet',

        });

      }

    }

  }


  return result;

}

Was ist ein "interaktiver Graf"

Nicht nur Leinwand mit Knoten, sondern 7 Funktionen:

  • Drag
    Knoten verschieben.
  • Connect
    Erstellen Sie eine neue Verbindung über handles.
  • Select
    Knoten oder Verbindung auswählen.
  • Inspect
    Inhalt auf der rechten Seite.
  • Filter
    Lassen Sie nur die richtigen Typen auf dem Bildschirm.
  • Diagnose
    Fehler und Risiken hervorheben.
  • Evolve
    Fügen Sie ein neues Protokoll, Ressource, Norm, Metrik hinzu.

React Flow ist für Node-basierte Editoren und interaktive Diagramme konzipiert; Die Dokumentation beschreibt separat die Anpassung von Knoten, Handles, Theming und Beispielen. 


Was Sie sonst noch zu Version 2 hinzufügen müssen

Es geht ums Geschäft, ohne Schmuck. Folgende Module werden benötigt:

  • LeftFilters.tsx
  • RightInspector.tsx
  • BottomTimeline.tsx
  • CreateProtocolModal.tsx
  • DiagnosticsPanel.tsx

Und die folgenden Verbesserungen:

  • Speichern von Knotenpositionen in der Datenbank
  • Form der Protokollerstellung
  • Bearbeiten von Metriken
  • layout-Algorithmus
  • Hervorhebung von Konfliktbeziehungen
  • SSR-safe Konfiguration, wenn es eine Server-Rendering; in React Flow für SSR separat gibt es NodeHandle, wenn DOM noch nicht gemessen.  

Wie man es in MVP zusammenbaut

Minimaler Stapel:

  • Next.js
  • TypeScript
  • @xyflow/react
  • Neo4j
  • Zustand

Minimale Entitäten:

  • 7 Knotentypen
  • 6 Arten von Verbindungen
  • 1 Bildschirm
  • 1 rechte Platte
  • 1 Diagnose
  • 1 Drehbuch

Minimales Szenario:

  • Bedeutung
  • Subjekt
  • Ressource
  • Norm
  • Protokoll
  • Effekt
  • Metrik

Es wird schon wie das gegenwärtige Operationsfeld, und nicht wie das Schema “für die Präsentation” aussehen.

Die wichtigste architektonische Schlussfolgerung

Hyperrebro in EQUILIBRIUM sollte nicht versuchen, gewaltsam als magische Superlinie zu zeichnen.

Für die Arbeitsoberfläche muss es als Protokollknoten ausgeführt werden, um den sich alle Prozessteilnehmer versammeln.

Das ist die Lösung:

  • nicht brechen React Flow,
  • normalerweise in Neo4j gespeichert,
  • leicht zu diagnostizieren,
  • leicht erweitert,
  • Es ist gut in UI.

Und das ist der vernünftigste Weg, um das System real zu machen.

Als nächstes ist es logisch, den gesamten Satz von Projektdateien zu sammeln: package.json, tsconfig, app/page.tsx, alle Node-Komponenten, API-Route, seed für Neo4j und CSS, so dass es einfach in das Repository eingefügt und ausgeführt werden kann.