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:
- Rahmenstruktur
- Größen
- Komponenten
- Stile
- Logik der Interaktion
EQUILIBRIUM UI KIT / FIGMA DOKUMENT
KUNSTSTRUKTUR DATEI FIGMA
Datei erstellen: EQUILIBRIUM / INTERFACE V1
Seiten (Seiten):
- 00 - Stiftungen (Stile)
- 01 — Components
- 02 — Screens
- 03 — Flows (Logik)
PAGE 00 — FOUNDATIONS
Farbsystem
Allgemein:
- Background: #0B0F1A (dunkel)
- Primary: #4DA3FF (blauer Geist)
- Accent: #7B61FF (Violett-Transformation)
Zustände:
- 🟢 Harmony: #3DDC97
- 🟡 Tension: #FFC857
- 🔴 Crisis: #FF4D4F
- 🔵 Growth: #4DA3FF
- ⚫ Unknown: #2A2F3A
Oberflächen:
- Card: #121826
- Border: #1F2633
Typografie
- Titel: Inter / Bold / 20–28px
- Text: Inter / Regular / 14–16px
- Klein: 12px
Effekte
- Glow (für Hyperrippen):
- blur: 20–40
- opacity: 0.3–0.6
- Hover:
- scale: 1.05
- Glow Verstärkung
PAGE 01 — COMPONENTS
Node (Gegenstück). Maße:
- S: 16px
- M: 32px
- L: 64px
Eigenschaften:
Zustände:
HyperEdge (Überraschung)
. Das ist keine Linie.
Visualisierung:
Optionen
Flow Line
Panel (Karte)
Größe
Enthält:
Button
Typen:
- Primary
- Secondary
- Ghost
Mode Switch. Schalter:
- Übersicht
- Diagnostik
- Szenario
- Handeln
Timeline
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.