إكويليبريوم UI KIT / مستند FIGMA. الآن سنقوم بذلك كمستند واجهة مستخدم حقيقي لفيغما، وليس مجرد وصف. يمكنك تسليمه للمصمم أو تجميعه بنفسك مباشرة. نحن نقدم:
- هيكل الإطارات
- الأحجام
- المكونات
- الأنماط
- منطق التفاعل
إكويليبريوم UI KIT / مستند FIGMA
هيكل ملف FIGMA
أنشئ ملفاً: إكويليبريوم / INTERFACE V1
الصفحات (Pages):
- 00 — Foundations (الأنماط)
- 01 — Components
- 02 — Screens
- 03 — Flows (المنطق)
PAGE 00 — FOUNDATIONS
نظام الألوان
الأساسية:
- Background: #0B0F1A (داكن عميق)
- أساسي: #4DA3FF (العقل الأزرق)
- لون التمييز: #7B61FF (بنفسجي التحول)
الحالات:
- 🟢 Harmony: #3DDC97
- 🟡 Tension: #FFC857
- 🔴 Crisis: #FF4D4F
- 🔵 Growth: #4DA3FF
- ⚫ Unknown: #2A2F3A
الأسطح:
- Card: #121826
- Border: #1F2633
الطباعة
- العنوان: Inter / Bold / 20–28px
- النص: Inter / Regular / 14–16px
- الصغير: 12px
الآثار
- التوهج (للحواف الفائقة):
- blur: 20–40
- opacity: 0.3–0.6
- Hover:
- scale: 1.05
- توهج تعزيز
PAGE 01 — COMPONENTS
عقدة (عقدة). الأحجام:
- S: 16px
- M: 32px
- L: 64px
الخصائص:
الحالات:
حافة فائقة (حافة فائقة)
. هذا ليس خطا.
العرض البصري:
الخيارات
Flow Line
لوحة (بطاقة)
الحجم
يتضمن:
Button
الأنواع:
- Primary
- Secondary
- Ghost
Mode Switch. المفتاح:
- نظرة عامة
- التشخيص
- السيناريو
- الفعل
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
من اليسار إلى اليمين:
- 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، حتى يمكن إدراج ذلك في المستودع وتشغيله.