# HMMSX और एनईपी के साथ काम करने के लिए व्यवस्था बनाना ।

<!--category-- ASP.NET, HTMX, Alpine.js, Workflow, TailwindCSS -->
<datetime class="hidden">2025-01-15T16:00</datetime>

## परिचय

में[पार्ट 2](/blog/workflowsystem-part2-architecture), हमने एक शक्‍तिशाली काम का निर्माण किया है इंजन.

लेकिन कार्य फूल JSON में परिभाषित नहीं हैं बहुत उपयोगकर्ता अनुकूल नहीं हैं.

- इस पोस्ट में, हम एक अद्भुत दृश्‍य चित्र संपादक बनाते हैं - सोचते हैं कि "ममम्मी का नोड" - HMMMX, Als.jys, s.jyz, और गाउन.I.
- इस पोस्ट के अंत तक, आप होगा:
- खींचें और ड्रॉप कार्य कैनवास
- एसवीजी के साथ दृश्य नोड कनेक्शन
- रीयल- समय नोड कॉन्फ़िगरेशन

[TOC]

## सुंदर, प्रसंग- मैजिकेबल यूआई

पूरा एचएमएक्सीकरण

1. दर्शन
2. हम उपयोक्ता चाहते हैं:
3. रंग पट्टिका में से नोड्स को खींचें
4. नोड्स नोड्स नोड्स कनेक्ट करें

बाजू पटल में नोड गुण कॉन्फ़िगर करें

## फाइल संग्रहित करें

### JSON की एक पंक्ति लिखने के बिना सभी बिना!

**तकनीक स्टैक**यह स्टैक क्यों?

- एचएसएमएक्स 2. 0
- जावास्क्रिप्ट को लिखने के बिना सर्वर द्वारा संपर्क
- सहेजने/ लोड करने के लिए सही

**क्लाएंट- साइड जटिलता कम करता है**सर्वर सच्चाई के स्रोत को बनाए रखता है

- यू. एस.
- कैनवास के लिए हल्के रंग
- सिर्फ ~ 15केबी न्यूनतम`x-data`अंतर्क्रियात्मक यूआई अवयव के लिए सही

**सरल**निर्देश

- टिला- तूफान + गुलबहारा
- : सुंदर, प्रसंगीय यूआई
- उपयोगिता- प्रथम सीएसएस

**पेटी में से गहरा/लाइट मोड**प्री- उन्नत अवयव (कार्ड्स, बटन, वीकार्ड्स)

- एसवीजी
- नेटिव कनेक्शन रेंडरिंग
- स्केलेबल, डब्बी पंक्तियाँ

## सीएसएस- शैली

कोई बाहरी लाइब्रेरी नहीं है

```
┌─────────────────────────────────────────────────────┐
│  Top Toolbar (Name, Save, Run, Cancel)             │
├─────────┬───────────────────────────┬───────────────┤
│  Node   │      Canvas               │    Node       │
│ Palette │    (Drag & Drop)          │  Inspector    │
│         │                            │               │
│ [HTTP]  │   ┌─────┐                 │  Name: ___    │
│ [Xform] │   │Node1│─┐               │  Type: ___    │
│ [Delay] │   └─────┘ │               │  Config: ___  │
│ [Cond]  │           ▼               │  Style: ___   │
│         │      ┌─────┐              │               │
│         │      │Node2│              │               │
│         │      └─────┘              │               │
└─────────┴───────────────────────────┴───────────────┘
```

## ओवरव्यू

हमारे संपादक में तीन मुख्य फलक हैं:

```csharp
[Route("workflow")]
public class WorkflowController : BaseController
{
    private readonly WorkflowService _workflowService;
    private readonly WorkflowExecutionService _executionService;

    [HttpGet]
    public async Task<IActionResult> Index()
    {
        var workflows = await _workflowService.GetAllAsync();
        return View("Index", workflows);
    }

    [HttpGet("create")]
    public IActionResult Create()
    {
        var workflow = new WorkflowDefinition
        {
            Name = "New Workflow",
            Nodes = new List<WorkflowNode>(),
            Connections = new List<NodeConnection>()
        };
        return View("Editor", workflow);
    }

    [HttpGet("edit/{id}")]
    public async Task<IActionResult> Edit(string id)
    {
        var workflow = await _workflowService.GetByIdAsync(id);
        return workflow == null ? NotFound() : View("Editor", workflow);
    }

    [HttpPost("save")]
    public async Task<IActionResult> Save([FromBody] WorkflowDefinition workflow)
    {
        var saved = await _workflowService.CreateOrUpdateAsync(workflow);
        return Json(new { success = true, workflowId = saved.Id });
    }

    [HttpPost("execute/{id}")]
    public async Task<IActionResult> Execute(string id,
        [FromBody] Dictionary<string, object>? inputData = null)
    {
        var execution = await _executionService.ExecuteWorkflowAsync(
            id, inputData, User.Identity?.Name ?? "Anonymous");

        return Json(new
        {
            success = true,
            executionId = execution.Id,
            status = execution.Status.ToString()
        });
    }
}
```

## नियंत्रक

सबसे पहले, हम हमारे नियंत्रण अंत बिन्दु सेट है:

```html
@model Mostlylucid.Workflow.Shared.Models.WorkflowDefinition

<div class="container-fluid"
     x-data="workflowEditor(@Html.Raw(JsonSerializer.Serialize(Model)))"
     x-init="init()">

    <!-- Top Toolbar -->
    <div class="navbar bg-base-200 rounded-box shadow-lg mb-6">
        <div class="flex-1">
            <input type="text"
                   x-model="workflow.name"
                   placeholder="Workflow Name"
                   class="input input-ghost text-2xl font-bold w-96" />
        </div>
        <div class="flex-none gap-2">
            <button @click="saveWorkflow()" class="btn btn-success">
                Save
            </button>
            <button @click="runWorkflow()" class="btn btn-primary">
                Run
            </button>
        </div>
    </div>

    <!-- Three-panel layout -->
    <div class="flex gap-4 h-[calc(100vh-200px)]">
        <!-- Left: Node Palette -->
        <!-- Center: Canvas -->
        <!-- Right: Node Inspector -->
    </div>
</div>
```

## संपादक दृश्य

हमारे संपादक दृश्य राज्य प्रबंधन के लिए अप्रयोगात्मक उपयोग करता है:`workflowEditor`आन्‍ना राज्य प्रबंधन

```javascript
function workflowEditor(initialWorkflow) {
    return {
        workflow: initialWorkflow || {
            id: crypto.randomUUID(),
            name: 'New Workflow',
            nodes: [],
            connections: [],
            isEnabled: true
        },
        selectedNode: null,
        draggedNodeType: null,
        connecting: false,

        init() {
            // Initialize node inputs as JSON for editing
            this.workflow.nodes.forEach(node => {
                node.inputsJson = JSON.stringify(node.inputs || {}, null, 2);
            });
        },

        // ... methods below
    };
}
```

## वह

अपूर्ण घटक सभी संपादक अवस्था का प्रबंधन करता है:

```html
<div class="w-64 bg-base-200 rounded-box p-4 overflow-y-auto">
    <h3 class="text-lg font-bold mb-4">📦 Available Nodes</h3>

    <div class="space-y-3">
        <div class="card bg-base-100 shadow cursor-pointer"
             draggable="true"
             @dragstart="dragStart($event, 'HttpRequest', '🌐', '#10B981')">
            <div class="card-body p-4">
                <h4 class="font-semibold">🌐 HTTP Request</h4>
                <p class="text-xs">Make API calls</p>
            </div>
        </div>

        <div class="card bg-base-100 shadow cursor-pointer"
             draggable="true"
             @dragstart="dragStart($event, 'Transform', '🔄', '#3B82F6')">
            <div class="card-body p-4">
                <h4 class="font-semibold">🔄 Transform</h4>
                <p class="text-xs">Process data</p>
            </div>
        </div>

        <!-- More node types... -->
    </div>
</div>
```

### खींचें और छोड़ें: नोड पैलेट

```javascript
dragStart(event, nodeType, icon, color) {
    this.draggedNodeType = { type: nodeType, icon, color };
}
```

## बायाँ किनारा में ड्रैगेबल नोड प्रकार है:

ड्रैग नियंत्रक

```html
<div class="flex-1 bg-base-300 rounded-box relative overflow-hidden"
     @drop="dropNode($event)"
     @dragover.prevent
     id="workflow-canvas">

    <div class="absolute inset-0 overflow-auto p-8">
        <!-- Grid background -->
        <div class="absolute inset-0 opacity-20"
             style="background-image: repeating-linear-gradient(...);
                    background-size: 20px 20px;">
        </div>

        <!-- Render nodes -->
        <template x-for="node in workflow.nodes" :key="node.id">
            <div :id="'node-' + node.id"
                 class="absolute card shadow-2xl cursor-move"
                 :style="`left: ${node.position.x}px;
                          top: ${node.position.y}px;
                          width: ${node.style.width}px;
                          background-color: ${node.style.backgroundColor};`"
                 @mousedown="selectNode(node)"
                 draggable="true"
                 @dragstart="dragNode($event, node)">

                <div class="card-body p-4">
                    <div class="flex items-center gap-2">
                        <span x-text="node.style.icon || '📦'"></span>
                        <h3 x-text="node.name"></h3>
                    </div>

                    <!-- Connection points -->
                    <div class="flex justify-between mt-2">
                        <div class="badge badge-primary"
                             @click.stop="startConnection(node, 'input')">
                            ◀
                        </div>
                        <div class="badge badge-success"
                             @click.stop="startConnection(node, 'output')">
                            ▶
                        </div>
                    </div>
                </div>
            </div>
        </template>

        <!-- SVG connections layer -->
        <svg class="absolute inset-0 pointer-events-none"
             style="width: 100%; height: 100%;">
            <template x-for="conn in workflow.connections" :key="conn.id">
                <path :d="getConnectionPath(conn)"
                      stroke="#94A3B8"
                      stroke-width="3"
                      fill="none"
                      marker-end="url(#arrowhead)"/>
            </template>
            <defs>
                <marker id="arrowhead" markerWidth="10" markerHeight="10">
                    <polygon points="0 0, 10 3, 0 6" fill="#94A3B8" />
                </marker>
            </defs>
        </svg>
    </div>
</div>
```

### घर

कैनवास है जहां जादू होता है:

```javascript
dropNode(event) {
    if (!this.draggedNodeType) return;

    const canvas = document.getElementById('canvas-container');
    const rect = canvas.getBoundingClientRect();

    // Calculate drop position
    const x = event.clientX - rect.left + canvas.scrollLeft - 100;
    const y = event.clientY - rect.top + canvas.scrollTop - 50;

    // Create new node
    const newNode = {
        id: crypto.randomUUID(),
        type: this.draggedNodeType.type,
        name: this.draggedNodeType.type,
        inputs: {},
        outputs: {},
        position: { x, y },
        style: {
            backgroundColor: this.draggedNodeType.color,
            textColor: '#FFFFFF',
            borderColor: this.draggedNodeType.color,
            icon: this.draggedNodeType.icon,
            width: 200
        },
        inputsJson: '{}'
    };

    this.workflow.nodes.push(newNode);

    // Set as start node if first node
    if (this.workflow.nodes.length === 1) {
        this.workflow.startNodeId = newNode.id;
    }

    this.draggedNodeType = null;
}
```

## हैंडलर छोड़ें

जब एक नोड कैनवास पर गिर जाता है:

```javascript
getConnectionPath(conn) {
    const source = this.workflow.nodes.find(n => n.id === conn.sourceNodeId);
    const target = this.workflow.nodes.find(n => n.id === conn.targetNodeId);

    if (!source || !target) return '';

    // Calculate connection points
    const x1 = source.position.x + (source.style?.width || 200);
    const y1 = source.position.y + 50;
    const x2 = target.position.x;
    const y2 = target.position.y + 50;

    // Create curved Bezier path
    const midX = (x1 + x2) / 2;
    return `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`;
}
```

एसवीजी कनेक्शन रेंडरिंग

## नोड्स के बीच कनेक्शन एसवीजी पथों के साथ बनाया गया है:

यह नोड्स के बीच हल्का, वक्रीय कनेक्शन बनाता है!

```html
<div class="w-80 bg-base-200 rounded-box p-4 overflow-y-auto"
     x-show="selectedNode">
    <h3 class="text-lg font-bold mb-4">⚙️ Node Settings</h3>

    <template x-if="selectedNode">
        <div class="space-y-4">
            <div class="form-control">
                <label class="label">Node Name</label>
                <input type="text"
                       x-model="selectedNode.name"
                       class="input input-bordered" />
            </div>

            <div class="form-control">
                <label class="label">Background Color</label>
                <input type="color"
                       x-model="selectedNode.style.backgroundColor"
                       class="input w-full h-12" />
            </div>

            <div class="form-control">
                <label class="label">Icon (emoji)</label>
                <input type="text"
                       x-model="selectedNode.style.icon"
                       class="input input-bordered"
                       maxlength="2" />
            </div>

            <div class="form-control">
                <label class="label">Inputs (JSON)</label>
                <textarea x-model="selectedNode.inputsJson"
                          @input="updateNodeInputs()"
                          class="textarea textarea-bordered font-mono"
                          rows="6"></textarea>
            </div>

            <button @click="setStartNode(selectedNode)"
                    class="btn btn-sm"
                    :class="workflow.startNodeId === selectedNode.id ?
                            'btn-success' : 'btn-outline'">
                Set as Start Node
            </button>
        </div>
    </template>
</div>
```

### नोड इंस्पेक्टर

दायाँ फलक चुने गए नोड्स को कॉन्फ़िगर करने देता है:

```javascript
updateNodeInputs() {
    if (!this.selectedNode) return;

    try {
        this.selectedNode.inputs = JSON.parse(this.selectedNode.inputsJson);
    } catch (e) {
        console.error('Invalid JSON:', e);
        // Could show error badge
    }
}
```

## लाइव JSON पारसिंग

उपयोक्ता इनपुट संपादित करें के रूप में, हम उन्हें वास्तव में समय में व्याख्या करते हैं:

```javascript
startConnection(node, type) {
    if (this.connecting) {
        // Complete connection
        if (this.connectionStart.node.id !== node.id) {
            this.workflow.connections.push({
                id: crypto.randomUUID(),
                sourceNodeId: this.connectionStart.node.id,
                targetNodeId: node.id,
                sourceOutput: 'default',
                targetInput: 'default'
            });
        }
        this.connecting = false;
        this.connectionStart = null;
    } else {
        // Start connection
        this.connecting = true;
        this.connectionStart = { node, type };
    }
}
```

## नोड कनेक्ट करें

कनेक्शन बिंदुओं पर क्लिक करके उपयोक्ताओं से कनेक्ट हों:

```javascript
async saveWorkflow() {
    try {
        const response = await fetch('/workflow/save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(this.workflow)
        });

        const result = await response.json();
        if (result.success) {
            alert('Workflow saved!');
            window.location.href = '/workflow';
        } else {
            alert('Error: ' + result.message);
        }
    } catch (error) {
        alert('Error saving: ' + error.message);
    }
}
```

## कार्य प्रवाहित करना HMMX के साथ

जब उपयोक्ता क्लिक करें "Se", हम एक साधारण कॉल का प्रयोग करते हैं (एमएमएक्स भी हो सकता है):

```javascript
async runWorkflow() {
    try {
        // Save first
        await this.saveWorkflow();

        // Then execute
        const response = await fetch(`/workflow/execute/${this.workflow.id}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({})
        });

        const result = await response.json();
        if (result.success) {
            alert(`Workflow started! Execution ID: ${result.executionId}`);
        }
    } catch (error) {
        alert('Error: ' + error.message);
    }
}
```

## कार्य प्रवाहित कर रहा है

संपादक से कार्य प्रवाहित करता है:

```html
<!-- In your layout -->
<select data-choose-theme class="select select-bordered">
    <option value="light">Light</option>
    <option value="dark">Dark</option>
    <option value="cupcake">Cupcake</option>
    <option value="synthwave">Synthwave</option>
</select>
```

प्रसंग जोरयूआई के साथ स्विच कर रहा है`bg-base-200`, `text-base-content`गुलबहारआई स्वचालित प्रसंग स्विच प्रदान करता है.

## उपयोक्ता प्रकाश और अंधेरे मोड के बीच टॉगल कर सकते हैं, और हमारे सभी काम प्रवाह नोड्स स्वतः समायोजन कर सकते हैं!

हमारे सभी रंग गुल्बाआई का उपयोग करें (एडिस्किक क्लास)

```html
<div class="flex flex-col lg:flex-row gap-4">
    <!-- On mobile: stack vertically -->
    <!-- On desktop: side-by-side -->
</div>
```

## इस प्रकार वे चुने गए थीम के अनुकूल बन गए!

### डिज़ाइन

हमारे तीन आकारों का खाका भिन्न स्क्रीन आकार के अनुकूल:

### परफ़ॉर्मेंसाइजेशन

आभासी स्क्रॉलिंग

```javascript
let dragTimeout;
dragNode(event, node) {
    clearTimeout(dragTimeout);
    dragTimeout = setTimeout(() => {
        // Update position
    }, 16); // ~60fps
}
```

## काम के लिए सैकड़ों नोड्स के साथ फूल के लिए, हम आभासी स्क्रॉलिंग (सिर्फ दृश्‍य नोड्स को प्रस्तुत करें) लागू कर सकते हैं.

लेकिन अधिकांश उपयोग मामलों के लिए, 50-100 नोड्स का अनुवाद आधुनिक ब्राउज़रों के साथ पूरी तरह से ठीक है.

```html
<button @click="deleteNode(node)"
        class="btn"
        aria-label="Delete node"
        @keydown.delete="deleteNode(node)">
    🗑️
</button>
```

## डिबाउंस अद्यतन

जब खींच रहे नोड्स, हम अक्षम स्थिति अद्यतन कर सकते हैं:

✅ **पहुँच**हम सही ARia लेबल और कुंजीपट नेविगेशन जोड़ा है:
✅ **हमने क्या बनाया है**इस पोस्ट में, हमने बनाया:
✅ **खींचें और ड्रॉप नोड रंग पैलेट करें**- ड्रैग नोड क़िस्म
✅ **दृश्य कैनवास**- स्थान नोड्स के साथ ग्रिड पृष्ठभूमि
✅ **एसवीजी कनेक्शन**- नोड्स के बीच वक्रीय पथ
✅ **नोड एंटिटी**- रीयल- टाइम कॉन्फ़िगरेशन फलक
✅ **प्रसंग समर्थन**- सभी गुलजार प्रसंगों के साथ काम करता है
✅ **उभारे गए ख़ाका**- स्क्रीन आकार के अनुकूलक

## एचएमएक्सीकरण

- सर्वर विडगेटर सहेजना/ लोड करना

```json
{
  "name": "GitHub Stars Monitor",
  "nodes": [
    {
      "id": "1",
      "type": "HttpRequest",
      "name": "Fetch Repo Data",
      "inputs": {
        "url": "https://api.github.com/repos/{{owner}}/{{repo}}",
        "method": "GET"
      },
      "position": { "x": 100, "y": 100 },
      "style": { "backgroundColor": "#10B981", "icon": "🌐" }
    },
    {
      "id": "2",
      "type": "Condition",
      "name": "Check Stars",
      "inputs": {
        "condition": "{{stargazers_count}} > 100"
      },
      "position": { "x": 100, "y": 250 },
      "style": { "backgroundColor": "#8B5CF6", "icon": "🔀" }
    },
    {
      "id": "3",
      "type": "Log",
      "name": "Log Success",
      "inputs": {
        "message": "Repo has {{stargazers_count}} stars!",
        "level": "info"
      },
      "position": { "x": 100, "y": 400 },
      "style": { "backgroundColor": "#3B82F6", "icon": "📝" }
    }
  ],
  "connections": [
    { "sourceNodeId": "1", "targetNodeId": "2" },
    { "sourceNodeId": "2", "targetNodeId": "3" }
  ],
  "startNodeId": "1"
}
```

## आन्‍ना फिर से काम करता है

- चिकनी, प्रतिक्रिया यूआई**उदाहरण कार्य प्रवाह**यहाँ एक पूरा कार्य फूल है आप संपादक में निर्माण कर सकते हैं:

- अगला क्या है?
- में
- पार्ट 4
- , हम के लिए आग को एकीकृत कर देंगे:
- समयसारिणी:

एपीआई पोलिंग ट्रिगर्स

## ट्रिगर स्थिति प्रबंधन

पृष्ठभूमि कार्य प्रक्रिया

डैश बोर्ड मॉनीटर किया जा रहा है

- **हम अपने काम फूल वास्तव में एक पागल बना देंगे!**कंटेनमेंट
- **हम एक उत्पादन को तैयार कर रहे हैं एक दृश्य कार्य संपादक कम जावास्क्रिप्ट के साथ!**सर्वर संचार के लिए HMMMAX, Alantazs एक-दूसरे से मिलने के लिए, और Scrips/ DyyshimI के लिए, हम कुछ बनाया है कि आधुनिक लग रहा है लगता है और बनाए रखने योग्य है.
- **मुख्य अन्तर्दृष्टि:**एसवीजी
- **कनेक्शन रेंडरिंग के लिए सही है**यू. एस.

सिर्फ पर्याप्त रीनामेंट प्रदान करता है