← المكتبة إكويليبريوم equilibrium-system.com

إكويليبريوم عدة واجهة المستخدم فيغما

إكويليبريوم UI KIT / مستند FIGMA. الآن سنقوم بذلك كمستند واجهة مستخدم حقيقي لفيغما، وليس مجرد وصف. يمكنك تسليمه للمصمم أو تجميعه بنفسك مباشرة. نحن نقدم:

إكويليبريوم UI KIT / مستند FIGMA

هيكل ملف FIGMA

أنشئ ملفاً: إكويليبريوم / INTERFACE V1

الصفحات (Pages):

PAGE 00 — FOUNDATIONS

نظام الألوان

الأساسية:

الحالات:

الأسطح:

الطباعة

الآثار

PAGE 01 — COMPONENTS

عقدة (عقدة). الأحجام:

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

الخصائص:

  • اللون = النوع
  • الحجم = التأثير

الحالات:

  • normal
  • hovered
  • active
  • محمّل بشكل مفرط (نبض)

حافة فائقة (حافة فائقة). هذا ليس خطا.

العرض البصري:

  • سحابة ناعمة
  • تدرج
  • blur

الخيارات

  • ضعيف
  • نشط
  • محمّل بشكل مفرط
  • متضارب

Flow Line

  • خط رفيع
  • تحريك الحركة
  • الاتجاه

لوحة (بطاقة)

  • يُستخدم على اليمين.

الحجم

  • العرض: 360–420px

يتضمن:

  • عنوان
  • كتل البيانات
  • أزرار

Button

الأنواع:

  • Primary
  • Secondary
  • Ghost

Mode Switch. المفتاح:

  • نظرة عامة
  • التشخيص
  • السيناريو
  • الفعل

Timeline

  • مقياس أفقي
  • draggable
  • علامات الأحداث

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

من اليسار إلى اليمين:

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

LEFT PANEL

Sections:

  • أنواع العقد
  • أنواع الروابط
  • الحالات

👉 استخدم مربعات الاختيار

CENTER — GRAPH AREA

هذا هو الإطار الرئيسي.

في الداخل:

  • Nodes (Auto Layout disabled)
  • HyperEdges (under layer)
  • التدفقات (تحريك الطبقة العليا) 👉 استخدم التموضع المطلق

RIGHT PANEL

الحالة 1 — عرض العقدة

  • Title
  • Type
  • Metrics
  • Connections
  • Buttons

الحالة 2 — عرض الحافة

  • المعنى
  • المشاركون
  • الموارد
  • الأثر
  • المؤشرات

BOTTOM — TIMELINE

  • خط زمني
  • نقاط الأحداث
  • drag

PAGE 03 — FLOWS

تدفق 1 — اختيار العقدة

نقرة →

→ إضاءة

→ فتح اللوحة

Flow 2 — اختيار الحافة الفائقة

نقرة →

→ كشف البروتوكول

Flow 3 — إنشاء ارتباط

  • اختيار عقدة
  • اضغط ➕
  • اختيار النوع
  • املأ الحقول

Flow 4 — التشخيص

تبديل الوضع →

→ إضاءة المشكلات

Flow 5 — السيناريو

تغيير المعامل →

→ تحديث الرسم البياني

المكونات اللازمة للانطلاق (الحد الأدنى)

إذا أردت بسرعة:

افعل:

  • Node (1 مكون)
  • Panel
  • Graph area
  • Top bar

👉 هذا يكفي للنموذج الأولي الأول

القواعد الحرجة (احفظها)

  • ❌ لا تثقل الشاشة
  • ❌ لا تصنع عناصر صغيرة
  • ❌ لا تصنع أشكالا معقدة

3 مبادئ رئيسية:

👉 البساطة

👉 قابلية القراءة

👉 الإحساس بنظام حي




حزمة MVP

MVP

نمضي إلى الكود بحيث نحصل على هيكل MVP، لا على مجموعة كلمات جميلة. نقدم فيما يلي:

  • الحزمة،
  • بنية المشروع،
  • نماذج البيانات،
  • الواجهة على React،
  • المخزن،
  • API,
  • مخطط Neo4j،
  • أول سيناريو عامل.

بالنسبة للرسم البياني البصري، من المعقول الآن اختيار React Flow؛ في الإصدار الحالي تُستخدم الحزمة @xyflow/react، وتُصنع العقد المخصصة هناك كمكونات React عادية. وبالنسبة لقاعدة البيانات الرسومية، يلائم ذلك بشكل طبيعي السائق الرسمي neo4j-driver. 

حزمة MVP

Frontend

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

Backend

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

لماذا هكذا

  • React Flow مهيأ بالفعل لواجهة مستخدم قائمة على العُقد.  
  • Neo4j مناسب تمامًا لأننا نحتاج إلى عُقد وروابط وأنواع علاقات واستعلامات على الرسم البياني. مشغّل JS الرسمي مدعوم مباشرة.  


بنية المشروع

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


أنواع البيانات

لا تبدأ بأنواع 12، بل ابدأ بـ 6. وإلا ستغرق.

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],

        },

      };

    }),

}));


مكوّن الرسم البياني

يعرض React Flow العُقد والروابط ويعالج التفاعل عبر دوال استدعاء مثل onNodesChange. وهذا بالضبط ما نحتاجه للوحة الأساسية. 

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>

  );

}

عُقد مخصّصة

في React Flow، العُقد المخصّصة هي مكوّنات React عادية تُمرَّر إلى nodeTypes. وهذا مسار مدعوم رسميًا. 

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 }}>المعنى</div>

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

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

        الأخلاقيات: {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 }}>الموضوع</div>

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

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

        الثقة: {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 }}>الأثر</div>

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

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

        الاستقرار: {data.metrics?.stability ?? '-'}

      </div>

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

    </div>

  );

}

الصفحة الرئيسية

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>عوامل التصفية</h3>

        <div>العُقد، الروابط، الحالات</div>

      </aside>


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

        <EquilibriumCanvas />

      </section>


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

        {!selectedNode && !selectedEdge && <div>اختر عُقدة أو رابطًا</div>}


        {selectedNode && (

          <>

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

            <p>النوع: {selectedNode.data.nodeType}</p>

            <p>الحالة: {selectedNode.data.state}</p>

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

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

            </pre>

          </>

        )}


        {selectedEdge && (

          <>

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

            <p>النوع: {selectedEdge.data.edgeType}</p>

            <p>الحالة: {selectedEdge.data.state}</p>

            <p>المعنى: {selectedEdge.data.meaning || '-'}</p>

            <p>الأخلاقيات: {selectedEdge.data.ethics || '-'}</p>

          </>

        )}

      </aside>


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

        Timeline / Simulation

      </footer>

    </main>

  );

}


اتصال Neo4j

يتصل المشغّل الرسمي عبر neo4j.driver(...); وتوصي الوثائق أيضًا بتحديد قاعدة البيانات الهدف صراحةً من أجل الأداء. 

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 };

واجهة برمجة قراءة الرسم البياني

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 });

}

إنشاء عُقدة

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 });

}

إنشاء رابط

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

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: 'تطوير الكفاءات',

  nodeType: 'meaning',

  state: 'growth',

  x: 100,

  y: 120,

  ethics: 0.9

});


CREATE (:EQNode {

  id: 'actor-1',

  label: 'تعاونية فينيكس',

  nodeType: 'actor',

  state: 'harmony',

  x: 420,

  y: 180,

  trust: 0.82,

  activity: 0.77

});


CREATE (:EQNode {

  id: 'effect-1',

  label: 'نمو التوظيف',

  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: 'النية',

  edgeType: 'intent',

  meaning: 'إطلاق منظومة تعليمية',

  ethics: 'إمكانية الوصول',

  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: 'التغذية الراجعة',

  edgeType: 'feedback',

  meaning: 'تقييم نتيجة البرنامج',

  metric: 'مؤشر التوظيف',

  state: 'harmony',

  intensity: 0.7

}]->(b);

مثال JSON للواجهة الأمامية

  "nodes": [

    {

      "id": "meaning-1",

      "type": "meaning",

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

      "data": {

        "label": "تطوير الكفاءات"،

        "nodeType": "meaning",

        "state": "growth",

        "metrics": { "ethics": 0.9 }

      }

    },

    {

      "id": "actor-1",

      "type": "actor",

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

      "data": {

        "label": "التعاونية فينيكس"،

        "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": "النية"،

        "edgeType": "intent",

        "meaning": "إطلاق منظومة تعليمية"،

        "state": "growth",

        "intensity": 0.8

      }

    }

  ]

}

ما الذي يجب فعله فعلاً بشأن "الحواف الفائقة"

إليك نقطة مهمة. يعمل React Flow بحواف عادية بين عقدتين. هذا هو نموذجه الأساسي كواجهة مستخدم لرسم بياني انسيابي. لذلك بالنسبة للنسخة الأولية، افعل هكذا:

الخيار أ — عملي

  • خزّن "الحافة الفائقة" كعقدة منفصلة من نوع process أو protocol
  • اربط المشاركين بها بحواف عادية

أي:

Meaning -> ProtocolHub -> Actor

Resource -> ProtocolHub

Norm -> ProtocolHub

ProtocolHub -> Effect

هذا حل صادق. إنه لا يكسر واجهة المستخدم ويسمح بعرض ارتباط متعدد الأبعاد.

النوع المخصص لذلك

export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'process'

  | 'resource'

  | 'norm'

  | 'effect'

  | 'protocol';

وعندها تكون "الحافة الفائقة" على مستوى البيانات عقدة protocol بمجموعة من العلاقات الواردة والصادرة. هذه أفضل بداية.

أول سيناريو عملي

لا تشتت جهدك. اصنع سيناريو واحداً:

السيناريو: "التعليم التعاوني"

العقد:

  • المعنى: تطوير الكفاءات
  • الفاعل: التعاونية
  • المورد: الصندوق
  • القاعدة: اللائحة
  • Protocol: البرنامج التعليمي
  • الأثر: نمو العمالة

الروابط:

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

هذه بالفعل نسخة أولية يمكن عرضها.

أوامر التشغيل

package.json

الاعتماديات

{

  "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"

  }

}

حزمة @xyflow/react هي الاسم الحالي لـ React Flow، ومشغل Neo4j JS driver يأتي الآن في الفرع الحالي 6.x. 

.env.local

NEO4J_URI=neo4j://localhost

NEO4J_USERNAME=neo4j

NEO4J_PASSWORD=your_password

NEO4J_DATABASE=neo4j

ما سيكون لديك بالفعل بعد ذلك

بعد تنفيذ هذا الهيكل سيظهر لديك:

  • رسم بياني تفاعلي،
  • عقد مخصصة،
  • لوحة فحص جانبية،
  • تحميل البيانات من Neo4j،
  • إنشاء العقد والارتباطات عبر API،
  • أساس للسيناريوهات والتشخيص والمحاكاة.

هذا لم يعد مفهوماً. هذا بالفعل هيكل منتج حي.


أقدم أدناه هيكل النظام المجمّع بالفعل: ما هي كل وحدة، وأي عقد مخصصة تلزم، وكيف تُبنى واجهة رسم بياني تفاعلية، وكود يمكن وضعه فوراً في النسخة الأولية. بالنسبة إلى React Flow، الحزمة الحالية هي @xyflow/react؛ وتُصنع العقد المخصصة فيها كمكونات React عادية، وتُمرَّر عبر nodeTypes، وتُحدَّد نقاط الاتصال عبر <Handle />. وبالنسبة إلى Neo4j، المسار الصحيح الآن هو استخدام مشغل JS الرسمي وdriver.executeQuery() مع المعاملات وتحديد قاعدة البيانات صراحة. 

تعريفات وحدات النظام

نوصي بتثبيت 10 من الوحدات للنسخة العاملة الأولى.

Graph Core

قلب النظام. يخزن العقد والارتباطات واختيار المستخدم والأوضاع والمرشحات والتخطيط وأحداث الواجهة. ما الذي يفعله:

  • يحتفظ بالحالة الحالية للرسم البياني
  • يحدّث العُقد والروابط
  • يُخبر واجهة المستخدم بما تم اختياره
  • يُشغّل إعادة الرسم

Node Registry

سجل أنواع العُقد. ما الذي يفعله:

  • يربط node.type ← مكوّن React
  • يحدّد كيفية عرض المعنى، والفاعل، والمورد، والبروتوكول وما إلى ذلك
  • يتيح توسيع النظام دون إعادة كتابة اللوحة

Edge Registry. سجل الروابط. ما الذي يفعله:

  • يحدّد المظهر المرئي للروابط وسلوكها
  • يضع الأنماط للنية، والتعاون، والتغذية الراجعة، والتطور
  • لاحقاً قد يشمل روابط مخصّصة مع تسميات ومؤشرات خطر

Protocol Engine

هذه وحدة رئيسية إكويليبريوم. ما الذي تفعله:

  • تجمّع فعلاً متعدد الأبعاد من عدة كيانات
  • تحقّق فعلياً "الرابط الفائق" كعقدة بروتوكول مستقلة
  • تربط المعنى والفاعلين والموارد والمعايير والآثار في منظومة واحدة

بالنسبة إلى MVP هذا أفضل مسار، لأن React Flow بطبيعته يعمل بروابط عادية بين عقدتين؛ ومن الأنسب تمثيل الرابط متعدد الأبعاد كعقدة محورية مستقلة. 

Inspector Panel. اللوحة اليمنى. ما الذي تفعله:

  • تعرض بطاقة العقدة المختارة
  • تعرض بطاقة الرابط المختار
  • تتيح تعديل السمات
  • تعرض المقاييس والمخاطر والأخلاقيات والحمل

Filter & Views. اللوحة اليسرى. ما الذي تفعله:

  • تُرشّح حسب أنواع العُقد
  • تُرشّح حسب الحالات
  • تُرشّح حسب أنواع الروابط
  • تبدّل الأوضاع: استعراض، تشخيص، سيناريو، فعل

Timeline & Events. اللوحة السفلى. ما الذي تفعله:

  • تعرض الأحداث عبر الزمن
  • تتيح تشغيل حالة الرسم البياني
  • في المستقبل تتيح محاكاة السيناريوهات

Diagnostics Engine. تشخيص النظام. ما الذي يفعله:

  • يبحث عن العُقد المثقلة
  • يجد الروابط المتضاربة
  • يُبرز مناطق الأزمة
  • يحسب "الثغرات" في المنظومة: مثلاً، توجد عملية لكن لا توجد معايير

Persistence Layer. طبقة التخزين. ما الذي تفعله:

  • تقرأ الرسم البياني من Neo4j وتكتب فيه
  • تحفظ مواضع العُقد
  • تحفظ المقاييس والحالات
  • يُرسل JSON إلى الواجهة الأمامية

AI/Rule Layer. طبقة التوصيات. ما الذي تفعله:

  • تقترح روابط جديدة
  • يحذر من المخاطر
  • تحسب الاستدلالات البسيطة
  • تفسر لماذا تُصنّف المنظومة العقدة على أنها أزمة

أنواع عقد المنظومة

للبدء لا حاجة إلى 30 نوعاً. المطلوب 7.

export type EQNodeType =

  | 'meaning' // المعنى

  | 'actor' // الكيان الفاعل

  | 'resource' // المورد

  | 'norm' // القاعدة

  | 'effect' // الأثر

  | 'protocol' // الحافة الفائقة كعقدة محورية

  | 'metric'; // المؤشر


Meaning. المعنى، المهمة، القيمة. يجيب على السؤال: "لماذا".

Actor. الإنسان، المنظمة، الوكيل، التعاونية. يجيب على السؤال: "من".

Resource. المال، الصندوق، البيانات، الطاقة، الوقت. يجيب على السؤال: "على حساب ماذا".

Norm. اللائحة، القاعدة، العقد، القانون. يجيب على السؤال: "وفق أي قيود".

Effect النتيجة. يجيب على السؤال: "ما الذي تحقق".

Protocol. الوحدة العملية المركزية. هذا هو تجسيد الارتباط متعدد الأبعاد. يجمع حوله بقية الكيانات.

Metric. مقياس النتيجة: مؤشر الثقة، معامل الاستدامة، مؤشر العمالة.

أنواع الروابط

export type EQEdgeType =

  | 'intent'

  | 'input'

  | 'constraint'

  | 'output'

  | 'feedback'

  | 'evolution';

المعنى:

  • intent — المعنى يدخل في البروتوكول
  • input — المورد أو الكيان الفاعل يشارك في البروتوكول
  • constraint — القاعدة تقيّد البروتوكول
  • output — البروتوكول يُنتج الأثر
  • feedback — الأثر يؤثر عكسياً
  • evolution — تغيّر الحالة مع الزمن

أنواع البيانات الأساسية

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[];

}

العقد المخصصة: الوصف

في React Flow، العقدة المخصصة هي مكوّن React عادي يستقبل NodeProps. يمكن إنشاء العقدة بعدة Handle، وبعناصر تحكم تفاعلية وواجهة مستخدم خاصة بها. هذا نموذج مدعوم رسمياً. فيما يلي مجموعة العقد التي باتت تشبه المنظومة.

MeaningNode. عقدة المعنى. عادة صغيرة، ساطعة، بمدخل واحد ومخرج واحد.

ActorNode. عقدة الكيان الفاعل. تُظهر الثقة والنشاط والعبء.

ResourceNode. عقدة المورد. عادة مدمجة، مع مؤشر التوافر.

NormNode. عقدة القاعدة. تُظهر نوع القيد.

EffectNode. عقدة النتيجة. تُظهر التأثير والاستدامة.

MetricNode. عقدة القياس. تُستخدم كمؤشر تقييمي.

ProtocolNode. الأهم على الإطلاق. المحور المركزي. له عدة نقاط اتصال:

  • على اليسار المعنى
  • في الأعلى الكيانات الفاعلة
  • في الأسفل الموارد
  • المؤثرات على اليمين
  • مقبض منفصل للمعايير
  • مقبض منفصل للمقاييس

الحاوية الأساسية المشتركة للعقدة

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>


العقد المخصصة: الكود

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="المعنى"

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

    >

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

        الأخلاقيات: {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="الذات"

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

    >

      <div style={{ fontSize: 12 }}>الثقة: {data.metrics?.trust ?? '-'}</div>

      <div style={{ fontSize: 12 }}>النشاط: {data.metrics?.activity ?? '-'}</div>

      <div style={{ fontSize: 12 }}>الحمل: {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="المورد"

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

    >

      <div style={{ fontSize: 12 }}>الإتاحة: {data.metrics?.value ?? '-'}</div>

      <div style={{ fontSize: 12 }}>الاستقرار: {data.metrics?.stability ?? '-'}</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="القاعدة"

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

    >

      <div style={{ fontSize: 12 }}>{data.description || 'قاعدة / قيد'}</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="المؤثر"

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

    >

      <div style={{ fontSize: 12 }}>التأثير: {data.metrics?.influence ?? '-'}</div>

      <div style={{ fontSize: 12 }}>الصمود: {data.metrics?.stability ?? '-'}</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="المقياس"

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

    >

      <div style={{ fontSize: 12 }}>القيمة: {data.metrics?.value ?? '-'}</div>


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

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

    </NodeShell>

  );

ProtocolNode.tsx. هذه هي العقدة الرئيسية.

'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 }}>

        البروتوكول / الحافة الفائقة

      </div>


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


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

        {data.description || 'إطار عمل متعدد الأبعاد'}

      </div>


      <div

        style={{

          display: 'grid',

          gridTemplateColumns: '1fr 1fr',

          gap: 6,

          marginTop: 10,

          fontSize: 12,

        }}

      >

        <div>الأخلاقيات: {data.metrics?.ethics ?? '-'}</div>

        <div>الحمل: {data.metrics?.load ?? '-'}</div>

        <div>الاستقرار: {data.metrics?.stability ?? '-'}</div>

        <div>النشاط: {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>

  );


العقدة ذات المقابض المتعددة تتوافق تماما مع نموذج React Flow، حيث يمكن أن تحتوي العقد المخصصة على أي عدد من نقاط اتصال المصدر والهدف. 

سجل العقد المخصصة

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,

};

الرسم البياني التفاعلي: كود اللوحة

<ReactFlow /> — المكون الأساسي الذي يعرض العقد والحواف، ويعالج التفاعلات ويعمل مع الحالة. هذه هي النقطة المركزية الرسمية للمكتبة. 

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: 'اتصال جديد',

            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>

  );

}

مثال على شاشة تفاعلية

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>عوامل التصفية</h3>

        <div>أنواع العقد</div>

        <div>الحالات</div>

        <div>أنواع الاتصالات</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>اختر عقدة أو اتصالا</div>}


        {selectedNode && (

          <>

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

            <div>النوع: {selectedNode.data.nodeType}</div>

            <div>الحالة: {selectedNode.data.state}</div>

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

              {selectedNode.data.description || 'بدون وصف'}

            </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>النوع: {selectedEdge.data.edgeType}</div>

            <div>الحالة: {selectedEdge.data.state}</div>

            <div>المعنى: {selectedEdge.data.meaning || '-'}</div>

            <div>الخطر: {selectedEdge.data.risk ?? '-'}</div>

            <div>الشدة: {selectedEdge.data.intensity ?? '-'}</div>

          </>

        )}

      </aside>


      <footer

        style={{

          gridColumn: '1 / 4',

          borderTop: '1px solid #1F2633',

          padding: 16,

        }}

      >

        Timeline / Events / Simulation

      </footer>

    </main>

  );

}


مثال على الرسم البياني لـ إكويليبريوم. هذا لم يعد تجريدا، بل سيناريو عملي.

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: 'تطوير الكفاءات',

        nodeType: 'meaning',

        description: 'نمو الإمكانات البشرية في المنطقة',

        state: 'growth',

        metrics: { ethics: 0.92 },

      },

    },

    {

      id: 'actor-1',

      type: 'actor',

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

      data: {

        label: 'تعاونية فينيكس',

        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: 'الصندوق التعليمي',

        nodeType: 'resource',

        state: 'harmony',

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

      },

    },

    {

      id: 'norm-1',

      type: 'norm',

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

      data: {

        label: 'لائحة البرنامج',

        nodeType: 'norm',

        description: 'شفافية الاختيار وإتاحة المشاركة',

        state: 'harmony',

        metrics: {},

      },

    },

    {

      id: 'protocol-1',

      type: 'protocol',

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

      data: {

        label: 'بروتوكول التعليم التعاوني',

        nodeType: 'protocol',

        description: 'المنظومة: تعليم → مشاركة → توظيف',

        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: 'نمو التوظيف',

        nodeType: 'effect',

        state: 'growth',

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

      },

    },

    {

      id: 'metric-1',

      type: 'metric',

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

      data: {

        label: 'مؤشر التوظيف',

        nodeType: 'metric',

        state: 'harmony',

        metrics: { value: 0.63 },

      },

    },

  ],

  edges: [

    {

      id: 'e1',

      source: 'meaning-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'النية',

        edgeType: 'intent',

        state: 'growth',

        meaning: 'المعنى يدخل في البروتوكول',

        intensity: 0.88,

      },

    },

    {

      id: 'e2',

      source: 'actor-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'المشاركة',

        edgeType: 'input',

        state: 'harmony',

        meaning: 'الفاعل ينفذ البروتوكول',

        intensity: 0.72,

      },

    },

    {

      id: 'e3',

      source: 'resource-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'المورد',

        edgeType: 'input',

        state: 'harmony',

        intensity: 0.68,

      },

    },

    {

      id: 'e4',

      source: 'norm-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'القيد',

        edgeType: 'constraint',

        state: 'harmony',

        meaning: 'القاعدة تقيد وتوجه',

      },

    },

    {

      id: 'e5',

      source: 'protocol-1',

      target: 'effect-1',

      type: 'smoothstep',

      data: {

        label: 'النتيجة',

        edgeType: 'output',

        state: 'growth',

        intensity: 0.81,

      },

    },

    {

      id: 'e6',

      source: 'protocol-1',

      target: 'metric-1',

      type: 'smoothstep',

      data: {

        label: 'القياس',

        edgeType: 'output',

        state: 'harmony',

      },

    },

    {

      id: 'e7',

      source: 'effect-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'التغذية الراجعة',

        edgeType: 'feedback',

        state: 'harmony',

        risk: 0.21,

      },

    },

  ],

};

Neo4j: مخطط التخزين

برنامج التشغيل الرسمي JS driver Neo4j يوصي باستخدام استعلامات مُعلَّمة بالمعاملات، كما أن executeQuery() ينشئ المعاملة بنفسه. من المفيد أيضا دائما تحديد database صراحة لتقليل overhead الزائد. 

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 });

}


التشخيص: القواعد الأساسية. هذا ليس الذكاء الاصطناعي، بل منطق صارم. ومنه ينبغي البدء.

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: 'لا أساس دلالي للبروتوكول',

        });

      }


      if (!hasActor) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'لا فاعل منفذ للبروتوكول',

        });

      }


      if (!hasResource) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'لا مدخل موارد للبروتوكول',

        });

      }


      if (!hasNorm) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'لا قيد تنظيمي للبروتوكول',

        });

      }


      if (!hasOutput) {

        result.push({

          type: 'error',

          targetId: node.id,

          message: 'لا نتيجة للبروتوكول',

        });

      }


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

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'البروتوكول مثقل',

        });

      }

    }

  }


  return result;

}

ما هو "الرسم البياني التفاعلي" في جوهره

ليس مجرد لوحة بعقد، بل 7 وظائف:

  • Drag
    نقل العقد.
  • Connect
    إنشاء ارتباط جديد عبر handles.
  • Select
    اختيار عقدة أو ارتباط.
  • Inspect
    عرض المحتوى على اليمين.
  • Filter
    إبقاء الأنواع المطلوبة فقط على الشاشة.
  • Diagnose
    إبراز الأخطاء والمخاطر.
  • Evolve
    إضافة بروتوكول أو مورد أو قاعدة أو مقياس جديد.

إن React Flow مخصص تحديدا لمحررات العقد والرسوم التفاعلية؛ وتصف الوثائق بشكل منفصل تخصيص العقد ومقابضها والسمات والأمثلة. 


ما الذي لا يزال يجب إضافته إلزاميا إلى الإصدار 2

هذا كلام جدي بلا زخرفة. نحتاج الوحدات التالية:

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

والتحسينات التالية:

  • حفظ مواضع العقد في قاعدة البيانات
  • نموذج إنشاء البروتوكول
  • تحرير المقاييس
  • خوارزمية التخطيط
  • إبراز الروابط المتعارضة
  • إعداد آمن للعرض من الخادم إذا كان هناك عرض من الخادم؛ وفي React Flow يوجد بشكل منفصل NodeHandle للعرض من الخادم عندما لا يكون DOM قد قيس بعد.  

كيف نجمع هذا في نسخة أولية قابلة للاستخدام

الحد الأدنى من التقنيات:

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

الحد الأدنى من الكيانات:

  • 7 أنواع العقد
  • 6 أنواع الروابط
  • 1 شاشة
  • 1 اللوحة اليمنى
  • 1 تشخيص
  • 1 سيناريو

الحد الأدنى من السيناريو:

  • المعنى
  • الكيان
  • المورد
  • القاعدة
  • البروتوكول
  • الأثر
  • المقياس

سيبدو هذا بالفعل كلوحة تشغيل حقيقية، لا كمخطط "للعرض التقديمي".

أهم استنتاج معماري

لا ينبغي محاولة رسم الحافة الفائقة في إكويليبريوم قسرا كخط خارق سحري.

لواجهة العمل يجب جعلها عقدة بروتوكول يلتف حولها جميع المشاركين في العملية.

هذا الحل:

  • لا يكسر React Flow،
  • يُخزَّن بشكل طبيعي في Neo4j،
  • يسهل تشخيصه،
  • يسهل توسيعه،
  • يُعرض جيدا في واجهة المستخدم.

وهذه أرجح طريقة لجعل النظام حقيقيا.

من المنطقي بعد ذلك تجميع مجموعة ملفات المشروع الكاملة: package.json وtsconfig وapp/page.tsx وجميع مكونات العقد ومسارات API وبذرة Neo4j وCSS، حتى يمكن إدراج ذلك في المستودع وتشغيله.