xcomponent-ai 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QUICKSTART.md +60 -56
- package/dist/mermaid-generator.d.ts.map +1 -1
- package/dist/mermaid-generator.js +27 -39
- package/dist/mermaid-generator.js.map +1 -1
- package/package.json +1 -1
- package/public/dashboard.html +187 -114
package/QUICKSTART.md
CHANGED
|
@@ -18,7 +18,7 @@ Use a provided example:
|
|
|
18
18
|
ls $(npm root -g)/xcomponent-ai/examples/
|
|
19
19
|
|
|
20
20
|
# Load an example to see its structure
|
|
21
|
-
xcomponent-ai load examples/
|
|
21
|
+
xcomponent-ai load examples/explicit-transitions-demo.yaml
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Or create your own project:
|
|
@@ -35,7 +35,7 @@ cd my-project
|
|
|
35
35
|
- ✅ Web dashboard (for real-time visualization)
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
xcomponent-ai serve examples/
|
|
38
|
+
xcomponent-ai serve examples/explicit-transitions-demo.yaml
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
**Expected output:**
|
|
@@ -43,10 +43,10 @@ xcomponent-ai serve examples/trading.yaml
|
|
|
43
43
|
🚀 xcomponent-ai Runtime Started
|
|
44
44
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
45
45
|
|
|
46
|
-
📦 Component:
|
|
46
|
+
📦 Component: ExplicitTransitionsComponent
|
|
47
47
|
Machines:
|
|
48
|
-
-
|
|
49
|
-
-
|
|
48
|
+
- Order (6 states, 7 transitions)
|
|
49
|
+
- RiskMonitor (3 states, 3 transitions)
|
|
50
50
|
|
|
51
51
|
🌐 API Server: http://localhost:3000
|
|
52
52
|
📊 Dashboard: http://localhost:3000/dashboard.html
|
|
@@ -76,21 +76,24 @@ You'll see:
|
|
|
76
76
|
curl -X POST http://localhost:3000/api/instances \
|
|
77
77
|
-H "Content-Type: application/json" \
|
|
78
78
|
-d '{
|
|
79
|
-
"machineName": "
|
|
79
|
+
"machineName": "Order",
|
|
80
80
|
"context": {
|
|
81
81
|
"orderId": "ORD-001",
|
|
82
|
-
"
|
|
83
|
-
"symbol": "AAPL"
|
|
82
|
+
"customerId": "CUST-123",
|
|
83
|
+
"symbol": "AAPL",
|
|
84
|
+
"totalQuantity": 1000,
|
|
85
|
+
"side": "BUY",
|
|
86
|
+
"startTime": 0
|
|
84
87
|
}
|
|
85
88
|
}'
|
|
86
89
|
|
|
87
90
|
# Response: {"instanceId": "abc-123"}
|
|
88
91
|
|
|
89
|
-
# Send an event
|
|
92
|
+
# Send an event to submit the order
|
|
90
93
|
curl -X POST http://localhost:3000/api/instances/abc-123/events \
|
|
91
94
|
-H "Content-Type: application/json" \
|
|
92
95
|
-d '{
|
|
93
|
-
"type": "
|
|
96
|
+
"type": "SUBMIT",
|
|
94
97
|
"payload": {}
|
|
95
98
|
}'
|
|
96
99
|
|
|
@@ -101,86 +104,87 @@ curl http://localhost:3000/api/instances/abc-123
|
|
|
101
104
|
curl http://localhost:3000/api/instances
|
|
102
105
|
```
|
|
103
106
|
|
|
104
|
-
**Option B: Via
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
# Start REPL mode
|
|
108
|
-
xcomponent-ai repl examples/trading.yaml
|
|
109
|
-
|
|
110
|
-
# Then type commands:
|
|
111
|
-
> create OrderEntry { orderId: "ORD-001", amount: 1000 }
|
|
112
|
-
Instance created: abc-123
|
|
113
|
-
|
|
114
|
-
> send abc-123 VALIDATE
|
|
115
|
-
Transition: Pending → Validated
|
|
116
|
-
|
|
117
|
-
> list
|
|
118
|
-
Instances:
|
|
119
|
-
- abc-123 (OrderEntry) : Validated
|
|
120
|
-
|
|
121
|
-
> inspect abc-123
|
|
122
|
-
Instance: abc-123
|
|
123
|
-
Machine: OrderEntry
|
|
124
|
-
State: Validated
|
|
125
|
-
Context: { orderId: "ORD-001", amount: 1000, symbol: "AAPL" }
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**Option C: Via Web Dashboard**
|
|
107
|
+
**Option B: Via Web Dashboard**
|
|
129
108
|
|
|
130
109
|
1. Open http://localhost:3000/dashboard.html
|
|
131
|
-
2.
|
|
132
|
-
3. Select machine: `
|
|
133
|
-
4.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
110
|
+
2. Go to the **"FSM Diagram"** tab (default view)
|
|
111
|
+
3. Select machine: `Order` from the dropdown
|
|
112
|
+
4. Fill in the context fields:
|
|
113
|
+
- orderId: ORD-001
|
|
114
|
+
- customerId: CUST-123
|
|
115
|
+
- symbol: AAPL
|
|
116
|
+
- totalQuantity: 1000
|
|
117
|
+
- side: BUY (from dropdown)
|
|
118
|
+
- startTime: 0
|
|
119
|
+
5. Click **"Create Instance"**
|
|
120
|
+
6. Watch the instance appear in the "Active Instances" list
|
|
121
|
+
7. Click on instance to view details
|
|
137
122
|
|
|
138
123
|
## 🔍 Monitor FSM
|
|
139
124
|
|
|
140
125
|
### View real-time logs
|
|
141
126
|
|
|
142
|
-
In the terminal where `xcomponent-ai serve` is running:
|
|
127
|
+
In the terminal where `xcomponent-ai serve` is running, you'll see real-time activity:
|
|
143
128
|
```
|
|
144
|
-
[14:32:15] Instance abc-123 created (
|
|
145
|
-
[14:32:18] abc-123:
|
|
146
|
-
[14:32:20] abc-123:
|
|
129
|
+
[14:32:15] Instance abc-123 created (Order)
|
|
130
|
+
[14:32:18] abc-123: Created → Submitted (event: SUBMIT)
|
|
131
|
+
[14:32:20] abc-123: Submitted → PartiallyExecuted (event: EXECUTION_NOTIFICATION)
|
|
132
|
+
[14:32:22] abc-123: PartiallyExecuted → PartiallyExecuted (event: EXECUTION_NOTIFICATION)
|
|
133
|
+
[14:32:22] abc-123: PartiallyExecuted → FullyExecuted (event: FULLY_EXECUTED)
|
|
147
134
|
```
|
|
148
135
|
|
|
149
|
-
###
|
|
136
|
+
### Monitor via Dashboard
|
|
137
|
+
|
|
138
|
+
The web dashboard at **http://localhost:3000/dashboard.html** provides:
|
|
139
|
+
- **📊 Event Blotter**: Real-time event stream with filtering
|
|
140
|
+
- **📈 Statistics**: Instance counts by state (active, final, error)
|
|
141
|
+
- **🎨 FSM Diagrams**: Visual state machine representations
|
|
142
|
+
- **🔍 Traceability**: Instance history and transitions
|
|
143
|
+
|
|
144
|
+
### Check Instance Status via API
|
|
150
145
|
|
|
151
146
|
```bash
|
|
152
|
-
#
|
|
153
|
-
|
|
147
|
+
# Get specific instance details
|
|
148
|
+
curl http://localhost:3000/api/instances/abc-123
|
|
154
149
|
|
|
155
|
-
#
|
|
156
|
-
|
|
150
|
+
# List all instances
|
|
151
|
+
curl http://localhost:3000/api/instances
|
|
157
152
|
|
|
158
|
-
#
|
|
159
|
-
|
|
153
|
+
# Get component info
|
|
154
|
+
curl http://localhost:3000/api/components
|
|
160
155
|
```
|
|
161
156
|
|
|
162
157
|
## 🧪 Test Complete Scenario
|
|
163
158
|
|
|
164
159
|
```bash
|
|
165
160
|
# 1. Start runtime
|
|
166
|
-
xcomponent-ai serve examples/
|
|
161
|
+
xcomponent-ai serve examples/explicit-transitions-demo.yaml &
|
|
167
162
|
|
|
168
163
|
# 2. Create instance
|
|
169
164
|
INSTANCE=$(curl -s -X POST http://localhost:3000/api/instances \
|
|
170
165
|
-H "Content-Type: application/json" \
|
|
171
|
-
-d '{"machineName": "
|
|
166
|
+
-d '{"machineName": "Order", "context": {"orderId": "ORD-001", "customerId": "CUST-123", "symbol": "AAPL", "totalQuantity": 1000, "side": "BUY", "startTime": 0}}' \
|
|
172
167
|
| jq -r '.instanceId')
|
|
173
168
|
|
|
174
169
|
# 3. Send events in sequence
|
|
170
|
+
# Submit the order
|
|
171
|
+
curl -X POST http://localhost:3000/api/instances/$INSTANCE/events \
|
|
172
|
+
-H "Content-Type: application/json" \
|
|
173
|
+
-d '{"type": "SUBMIT", "payload": {}}'
|
|
174
|
+
|
|
175
|
+
sleep 1
|
|
176
|
+
|
|
177
|
+
# Send execution notification (partial fill)
|
|
175
178
|
curl -X POST http://localhost:3000/api/instances/$INSTANCE/events \
|
|
176
179
|
-H "Content-Type: application/json" \
|
|
177
|
-
-d '{"type": "
|
|
180
|
+
-d '{"type": "EXECUTION_NOTIFICATION", "payload": {"quantity": 600, "price": 150.0, "executionId": "EXEC-001"}}'
|
|
178
181
|
|
|
179
182
|
sleep 1
|
|
180
183
|
|
|
184
|
+
# Send another execution notification (complete fill)
|
|
181
185
|
curl -X POST http://localhost:3000/api/instances/$INSTANCE/events \
|
|
182
186
|
-H "Content-Type: application/json" \
|
|
183
|
-
-d '{"type": "
|
|
187
|
+
-d '{"type": "EXECUTION_NOTIFICATION", "payload": {"quantity": 400, "price": 150.5, "executionId": "EXEC-002"}}'
|
|
184
188
|
|
|
185
189
|
# 4. Check final state
|
|
186
190
|
curl http://localhost:3000/api/instances/$INSTANCE
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid-generator.d.ts","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"mermaid-generator.d.ts","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA0BpE;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA6C1E"}
|
|
@@ -12,13 +12,6 @@ function generateMermaidDiagram(machine) {
|
|
|
12
12
|
const lines = [];
|
|
13
13
|
lines.push('stateDiagram-v2');
|
|
14
14
|
lines.push('');
|
|
15
|
-
// Add note at the top if there's a description in metadata
|
|
16
|
-
if (machine.metadata?.description) {
|
|
17
|
-
lines.push(` note right of ${machine.initialState}`);
|
|
18
|
-
lines.push(` ${machine.metadata.description}`);
|
|
19
|
-
lines.push(` end note`);
|
|
20
|
-
lines.push('');
|
|
21
|
-
}
|
|
22
15
|
// Mark initial state
|
|
23
16
|
lines.push(` [*] --> ${machine.initialState}`);
|
|
24
17
|
lines.push('');
|
|
@@ -34,24 +27,6 @@ function generateMermaidDiagram(machine) {
|
|
|
34
27
|
lines.push(` ${state.name} --> [*]`);
|
|
35
28
|
}
|
|
36
29
|
});
|
|
37
|
-
lines.push('');
|
|
38
|
-
// Add state descriptions as notes
|
|
39
|
-
machine.states.forEach(state => {
|
|
40
|
-
if (state.metadata?.description) {
|
|
41
|
-
const desc = state.metadata.description;
|
|
42
|
-
lines.push(` note right of ${state.name}`);
|
|
43
|
-
// Handle multi-line descriptions
|
|
44
|
-
if (desc.includes('\n')) {
|
|
45
|
-
desc.split('\n').forEach((line) => {
|
|
46
|
-
lines.push(` ${line.trim()}`);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
lines.push(` ${desc}`);
|
|
51
|
-
}
|
|
52
|
-
lines.push(` end note`);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
30
|
return lines.join('\n');
|
|
56
31
|
}
|
|
57
32
|
/**
|
|
@@ -60,28 +35,41 @@ function generateMermaidDiagram(machine) {
|
|
|
60
35
|
function generateStyledMermaidDiagram(machine) {
|
|
61
36
|
const baseDiagram = generateMermaidDiagram(machine);
|
|
62
37
|
const styleLines = [];
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
38
|
+
// Define color classes once at the end
|
|
39
|
+
const usedClasses = new Set();
|
|
40
|
+
// Collect state styles
|
|
41
|
+
const stateStyles = [];
|
|
42
|
+
machine.states.forEach(state => {
|
|
43
|
+
let className = '';
|
|
66
44
|
if (state.type === 'entry') {
|
|
67
|
-
|
|
68
|
-
|
|
45
|
+
className = 'entryState';
|
|
46
|
+
usedClasses.add('entryState');
|
|
69
47
|
}
|
|
70
48
|
else if (state.type === 'final') {
|
|
71
|
-
|
|
72
|
-
|
|
49
|
+
className = 'finalState';
|
|
50
|
+
usedClasses.add('finalState');
|
|
73
51
|
}
|
|
74
52
|
else if (state.type === 'error') {
|
|
75
|
-
|
|
76
|
-
|
|
53
|
+
className = 'errorState';
|
|
54
|
+
usedClasses.add('errorState');
|
|
77
55
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
styleLines.push(` classDef ${stateClass} fill:${state.metadata.displayColor},stroke-width:2px`);
|
|
56
|
+
if (className) {
|
|
57
|
+
stateStyles.push(` class ${state.name} ${className}`);
|
|
81
58
|
}
|
|
82
59
|
});
|
|
83
|
-
|
|
84
|
-
|
|
60
|
+
// Add class definitions
|
|
61
|
+
if (usedClasses.has('entryState')) {
|
|
62
|
+
styleLines.push(` classDef entryState fill:#fbbf24,stroke:#f59e0b,stroke-width:3px,color:#000`);
|
|
63
|
+
}
|
|
64
|
+
if (usedClasses.has('finalState')) {
|
|
65
|
+
styleLines.push(` classDef finalState fill:#10b981,stroke:#059669,stroke-width:3px,color:#fff`);
|
|
66
|
+
}
|
|
67
|
+
if (usedClasses.has('errorState')) {
|
|
68
|
+
styleLines.push(` classDef errorState fill:#ef4444,stroke:#dc2626,stroke-width:3px,color:#fff`);
|
|
69
|
+
}
|
|
70
|
+
// Combine: base diagram + class definitions + state class applications
|
|
71
|
+
if (styleLines.length > 0 || stateStyles.length > 0) {
|
|
72
|
+
return baseDiagram + '\n\n' + styleLines.join('\n') + '\n' + stateStyles.join('\n');
|
|
85
73
|
}
|
|
86
74
|
return baseDiagram;
|
|
87
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid-generator.js","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAOH,
|
|
1
|
+
{"version":3,"file":"mermaid-generator.js","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xcomponent-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "LLM-first framework for AI agents (Claude, GPT) to build apps with sanctuarized business logic. Event-driven FSM runtime with multi-instance state machines, cross-component communication, event sourcing, and production-ready persistence (PostgreSQL, MongoDB)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/public/dashboard.html
CHANGED
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
/* Reset & Base */
|
|
11
11
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
12
12
|
|
|
13
|
-
/*
|
|
13
|
+
/* Dark Background */
|
|
14
14
|
body {
|
|
15
15
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
16
|
-
background: linear-gradient(-45deg, #
|
|
16
|
+
background: linear-gradient(-45deg, #0a0a0a, #1a1a1a, #2d2d2d, #1f1f1f);
|
|
17
17
|
background-size: 400% 400%;
|
|
18
18
|
animation: gradientShift 15s ease infinite;
|
|
19
19
|
min-height: 100vh;
|
|
20
|
-
color: #
|
|
20
|
+
color: #e0e0e0;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
@keyframes gradientShift {
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
left: 0;
|
|
144
144
|
right: 0;
|
|
145
145
|
height: 4px;
|
|
146
|
-
background: linear-gradient(90deg, #
|
|
146
|
+
background: linear-gradient(90deg, #fbbf24, #f59e0b);
|
|
147
147
|
transform: scaleX(0);
|
|
148
148
|
transition: transform 0.3s ease;
|
|
149
149
|
}
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
.stat-value {
|
|
161
161
|
font-size: 36px;
|
|
162
162
|
font-weight: 800;
|
|
163
|
-
background: linear-gradient(135deg, #
|
|
163
|
+
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
|
164
164
|
-webkit-background-clip: text;
|
|
165
165
|
-webkit-text-fill-color: transparent;
|
|
166
166
|
background-clip: text;
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
left: 50%;
|
|
210
210
|
width: 0;
|
|
211
211
|
height: 3px;
|
|
212
|
-
background: linear-gradient(90deg, #
|
|
212
|
+
background: linear-gradient(90deg, #fbbf24, #f59e0b);
|
|
213
213
|
transform: translateX(-50%);
|
|
214
214
|
transition: width 0.3s ease;
|
|
215
215
|
}
|
|
@@ -316,8 +316,8 @@
|
|
|
316
316
|
.form-group textarea:focus {
|
|
317
317
|
outline: none;
|
|
318
318
|
background: rgba(255, 255, 255, 0.2);
|
|
319
|
-
border-color: #
|
|
320
|
-
box-shadow: 0 0 0 3px rgba(
|
|
319
|
+
border-color: #fbbf24;
|
|
320
|
+
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.2);
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
.form-help {
|
|
@@ -360,13 +360,13 @@
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
.btn-primary {
|
|
363
|
-
background: linear-gradient(135deg, #
|
|
364
|
-
color:
|
|
363
|
+
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
|
|
364
|
+
color: #1a1a1a;
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
.btn-primary:hover {
|
|
368
368
|
transform: translateY(-2px);
|
|
369
|
-
box-shadow: 0 8px 25px 0 rgba(
|
|
369
|
+
box-shadow: 0 8px 25px 0 rgba(251, 191, 36, 0.4);
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
.btn-success {
|
|
@@ -437,14 +437,14 @@
|
|
|
437
437
|
left: 0;
|
|
438
438
|
width: 4px;
|
|
439
439
|
height: 100%;
|
|
440
|
-
background: linear-gradient(180deg, #
|
|
440
|
+
background: linear-gradient(180deg, #fbbf24, #f59e0b);
|
|
441
441
|
opacity: 0;
|
|
442
442
|
transition: opacity 0.3s ease;
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
.instance-item:hover {
|
|
446
446
|
background: rgba(255, 255, 255, 0.15);
|
|
447
|
-
box-shadow: 0 8px 30px rgba(
|
|
447
|
+
box-shadow: 0 8px 30px rgba(251, 191, 36, 0.2);
|
|
448
448
|
transform: translateY(-3px) translateX(4px);
|
|
449
449
|
}
|
|
450
450
|
|
|
@@ -453,9 +453,9 @@
|
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
.instance-item.selected {
|
|
456
|
-
border-color: #
|
|
457
|
-
background: rgba(
|
|
458
|
-
box-shadow: 0 8px 30px rgba(
|
|
456
|
+
border-color: #fbbf24;
|
|
457
|
+
background: rgba(251, 191, 36, 0.2);
|
|
458
|
+
box-shadow: 0 8px 30px rgba(251, 191, 36, 0.3);
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
.instance-header {
|
|
@@ -519,7 +519,7 @@
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
.blotter-item {
|
|
522
|
-
border-left: 4px solid #
|
|
522
|
+
border-left: 4px solid #fbbf24;
|
|
523
523
|
background: rgba(255, 255, 255, 0.05);
|
|
524
524
|
padding: 12px 16px;
|
|
525
525
|
margin-bottom: 10px;
|
|
@@ -638,14 +638,13 @@
|
|
|
638
638
|
</div>
|
|
639
639
|
|
|
640
640
|
<div class="tabs">
|
|
641
|
-
<div class="tab active" onclick="switchTab('
|
|
642
|
-
<div class="tab" onclick="switchTab('
|
|
643
|
-
<div class="tab" onclick="switchTab('blotter')"
|
|
644
|
-
<div class="tab" onclick="switchTab('traceability')"
|
|
645
|
-
<div class="tab" onclick="switchTab('create')">Create Instance</div>
|
|
641
|
+
<div class="tab active" onclick="switchTab('diagram')">🎨 FSM Diagram</div>
|
|
642
|
+
<div class="tab" onclick="switchTab('overview')">📋 Instances</div>
|
|
643
|
+
<div class="tab" onclick="switchTab('blotter')">📊 Event Blotter</div>
|
|
644
|
+
<div class="tab" onclick="switchTab('traceability')">🔍 Traceability</div>
|
|
646
645
|
</div>
|
|
647
646
|
|
|
648
|
-
<div class="tab-content
|
|
647
|
+
<div class="tab-content" id="tab-overview">
|
|
649
648
|
<div class="filter-bar">
|
|
650
649
|
<input type="text" id="filter-instance" placeholder="Filter by instance ID..." oninput="filterInstances()">
|
|
651
650
|
<select id="filter-machine" onchange="filterInstances()">
|
|
@@ -658,22 +657,48 @@
|
|
|
658
657
|
<div class="instance-list" id="instance-list">
|
|
659
658
|
<div class="empty-state">
|
|
660
659
|
<div class="empty-state-icon">📭</div>
|
|
661
|
-
<div>No instances yet.
|
|
660
|
+
<div>No instances yet. Go to the "FSM Diagram" tab to create one!</div>
|
|
662
661
|
</div>
|
|
663
662
|
</div>
|
|
664
663
|
</div>
|
|
665
664
|
|
|
666
|
-
<div class="tab-content" id="tab-diagram">
|
|
667
|
-
<div class="
|
|
668
|
-
<
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
<div class="
|
|
676
|
-
|
|
665
|
+
<div class="tab-content active" id="tab-diagram">
|
|
666
|
+
<div class="grid-2">
|
|
667
|
+
<div>
|
|
668
|
+
<div class="form-group">
|
|
669
|
+
<label for="diagram-machine">State Machine:</label>
|
|
670
|
+
<select id="diagram-machine" onchange="renderDiagram()">
|
|
671
|
+
<option value="">Select a state machine...</option>
|
|
672
|
+
</select>
|
|
673
|
+
</div>
|
|
674
|
+
<div class="mermaid-container" id="diagram-container">
|
|
675
|
+
<div class="empty-state">
|
|
676
|
+
<div class="empty-state-icon">🎨</div>
|
|
677
|
+
<div>Select a state machine to view its diagram</div>
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
|
|
682
|
+
<div>
|
|
683
|
+
<div class="card">
|
|
684
|
+
<h2>Create New Instance</h2>
|
|
685
|
+
<div id="diagram-create-form">
|
|
686
|
+
<div class="empty-state" style="padding: 40px 20px;">
|
|
687
|
+
<div class="empty-state-icon">👈</div>
|
|
688
|
+
<div>Select a state machine to create an instance</div>
|
|
689
|
+
</div>
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
|
|
693
|
+
<div class="card" style="margin-top: 20px;" id="diagram-instances-card">
|
|
694
|
+
<h2>Active Instances</h2>
|
|
695
|
+
<div id="diagram-instances-list">
|
|
696
|
+
<div class="empty-state" style="padding: 20px;">
|
|
697
|
+
<div class="empty-state-icon">📭</div>
|
|
698
|
+
<div>No instances for this state machine</div>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
677
702
|
</div>
|
|
678
703
|
</div>
|
|
679
704
|
</div>
|
|
@@ -710,19 +735,6 @@
|
|
|
710
735
|
</div>
|
|
711
736
|
</div>
|
|
712
737
|
|
|
713
|
-
<div class="tab-content" id="tab-create">
|
|
714
|
-
<div class="card">
|
|
715
|
-
<h2>Create New Instance</h2>
|
|
716
|
-
<div class="form-group">
|
|
717
|
-
<label for="create-machine">State Machine *</label>
|
|
718
|
-
<select id="create-machine" onchange="updateCreateForm()">
|
|
719
|
-
<option value="">Select a state machine...</option>
|
|
720
|
-
</select>
|
|
721
|
-
</div>
|
|
722
|
-
<div id="create-form-fields"></div>
|
|
723
|
-
<button class="btn btn-primary" onclick="createInstance()">Create Instance</button>
|
|
724
|
-
</div>
|
|
725
|
-
</div>
|
|
726
738
|
</div>
|
|
727
739
|
|
|
728
740
|
<script>
|
|
@@ -798,6 +810,12 @@
|
|
|
798
810
|
updateStats();
|
|
799
811
|
renderInstances();
|
|
800
812
|
updateTraceSelector();
|
|
813
|
+
|
|
814
|
+
// Update diagram instances list if a machine is selected
|
|
815
|
+
const selectedMachine = document.getElementById('diagram-machine')?.value;
|
|
816
|
+
if (selectedMachine) {
|
|
817
|
+
updateDiagramInstancesList(selectedMachine);
|
|
818
|
+
}
|
|
801
819
|
}
|
|
802
820
|
|
|
803
821
|
function updateStats() {
|
|
@@ -811,7 +829,7 @@
|
|
|
811
829
|
function renderInstances() {
|
|
812
830
|
const container = document.getElementById('instance-list');
|
|
813
831
|
if (instances.length === 0) {
|
|
814
|
-
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📭</div><div>No instances yet.
|
|
832
|
+
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📭</div><div>No instances yet. Go to the "FSM Diagram" tab to create one!</div></div>';
|
|
815
833
|
return;
|
|
816
834
|
}
|
|
817
835
|
|
|
@@ -896,7 +914,11 @@
|
|
|
896
914
|
async function renderDiagram() {
|
|
897
915
|
const machineName = document.getElementById('diagram-machine').value;
|
|
898
916
|
const component = getCurrentComponent();
|
|
899
|
-
if (!machineName || !component)
|
|
917
|
+
if (!machineName || !component) {
|
|
918
|
+
document.getElementById('diagram-create-form').innerHTML = '<div class="empty-state" style="padding: 40px 20px;"><div class="empty-state-icon">👈</div><div>Select a state machine to create an instance</div></div>';
|
|
919
|
+
document.getElementById('diagram-instances-list').innerHTML = '<div class="empty-state" style="padding: 20px;"><div class="empty-state-icon">📭</div><div>No instances for this state machine</div></div>';
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
900
922
|
|
|
901
923
|
const machine = component.stateMachines.find(m => m.name === machineName);
|
|
902
924
|
if (!machine) return;
|
|
@@ -906,102 +928,154 @@
|
|
|
906
928
|
const data = await res.json();
|
|
907
929
|
|
|
908
930
|
if (data.diagram) {
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
931
|
+
try {
|
|
932
|
+
// Use Mermaid v10 API
|
|
933
|
+
const container = document.getElementById('diagram-container');
|
|
934
|
+
container.innerHTML = '<div class="mermaid">' + data.diagram + '</div>';
|
|
935
|
+
|
|
936
|
+
// Re-render mermaid diagrams
|
|
937
|
+
await mermaid.run({
|
|
938
|
+
querySelector: '#diagram-container .mermaid'
|
|
939
|
+
});
|
|
940
|
+
} catch (error) {
|
|
941
|
+
console.error('Mermaid rendering error:', error);
|
|
942
|
+
const container = document.getElementById('diagram-container');
|
|
943
|
+
container.innerHTML = `
|
|
944
|
+
<div style="color: #ef4444; padding: 20px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border: 1px solid #ef4444;">
|
|
945
|
+
<strong>Diagram Rendering Error:</strong> ${error.message || 'Unknown error'}<br><br>
|
|
946
|
+
<details style="margin-top: 10px;">
|
|
947
|
+
<summary style="cursor: pointer;">Show diagram source</summary>
|
|
948
|
+
<pre style="margin-top: 10px; padding: 10px; background: rgba(0,0,0,0.3); border-radius: 4px; overflow-x: auto;">${data.diagram}</pre>
|
|
949
|
+
</details>
|
|
950
|
+
</div>
|
|
951
|
+
`;
|
|
952
|
+
}
|
|
917
953
|
}
|
|
954
|
+
|
|
955
|
+
// Populate create instance form for this machine
|
|
956
|
+
updateDiagramCreateForm(machine);
|
|
957
|
+
|
|
958
|
+
// Show instances for this machine
|
|
959
|
+
updateDiagramInstancesList(machineName);
|
|
918
960
|
}
|
|
919
|
-
|
|
920
|
-
// Traceability
|
|
921
|
-
async function loadTrace() {
|
|
922
|
-
if (!selectedInstance) return;
|
|
923
|
-
|
|
924
|
-
const container = document.getElementById('trace-container');
|
|
925
|
-
// In real version, fetch from /api/instances/:id/history
|
|
926
|
-
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div>Traceability history would appear here</div></div>';
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
function updateTraceSelector() {
|
|
930
|
-
const select = document.getElementById('trace-instance');
|
|
931
|
-
select.innerHTML = '<option value="">Select an instance...</option>' +
|
|
932
|
-
instances.map(i => `<option value="${i.id}">${i.id.substring(0, 8)} - ${i.machineName}</option>`).join('');
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// Create Instance
|
|
936
|
-
function updateCreateForm() {
|
|
937
|
-
const machineName = document.getElementById('create-machine').value;
|
|
938
|
-
const component = getCurrentComponent();
|
|
939
|
-
if (!machineName || !component) return;
|
|
940
961
|
|
|
941
|
-
|
|
942
|
-
if (!machine || !machine.contextSchema) {
|
|
943
|
-
document.getElementById('create-form-fields').innerHTML = '<div class="form-group"><label>Context (JSON)</label><textarea id="context-json" rows="4"></textarea></div>';
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// Generate form from schema
|
|
962
|
+
function updateDiagramCreateForm(machine) {
|
|
948
963
|
let html = '';
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
html
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
964
|
+
|
|
965
|
+
if (!machine.contextSchema) {
|
|
966
|
+
html = `
|
|
967
|
+
<div class="form-group">
|
|
968
|
+
<label>Context (JSON)</label>
|
|
969
|
+
<textarea id="diagram-context-json" rows="6" placeholder='{"property": "value"}'></textarea>
|
|
970
|
+
<div class="form-help">Enter a JSON object with the context properties for this instance</div>
|
|
971
|
+
</div>
|
|
972
|
+
<button class="btn btn-primary" onclick="createInstanceFromDiagram()">Create Instance</button>
|
|
973
|
+
`;
|
|
974
|
+
} else {
|
|
975
|
+
html = '<div style="background: rgba(251, 191, 36, 0.1); border-left: 3px solid #fbbf24; padding: 12px; margin-bottom: 20px; border-radius: 4px; color: rgba(255,255,255,0.9);"><strong>📝 Context Properties:</strong> Fill in the fields below</div>';
|
|
976
|
+
|
|
977
|
+
for (const [key, field] of Object.entries(machine.contextSchema)) {
|
|
978
|
+
html += `<div class="form-group">`;
|
|
979
|
+
html += `<label for="diagram_ctx_${key}">${field.label || key}${field.required ? ' *' : ''}</label>`;
|
|
980
|
+
if (field.type === 'select') {
|
|
981
|
+
html += `<select id="diagram_ctx_${key}">`;
|
|
982
|
+
field.options.forEach(opt => html += `<option value="${opt.value}">${opt.label}</option>`);
|
|
983
|
+
html += `</select>`;
|
|
984
|
+
} else {
|
|
985
|
+
html += `<input type="${field.type || 'text'}" id="diagram_ctx_${key}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''}>`;
|
|
986
|
+
}
|
|
987
|
+
if (field.description) html += `<div class="form-help">${field.description}</div>`;
|
|
988
|
+
html += `</div>`;
|
|
958
989
|
}
|
|
959
|
-
|
|
960
|
-
html += `</div>`;
|
|
990
|
+
html += `<button class="btn btn-primary" onclick="createInstanceFromDiagram()">Create Instance</button>`;
|
|
961
991
|
}
|
|
962
|
-
|
|
992
|
+
|
|
993
|
+
document.getElementById('diagram-create-form').innerHTML = html;
|
|
963
994
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
const
|
|
995
|
+
|
|
996
|
+
function updateDiagramInstancesList(machineName) {
|
|
997
|
+
const machineInstances = instances.filter(i => i.machineName === machineName);
|
|
998
|
+
|
|
999
|
+
if (machineInstances.length === 0) {
|
|
1000
|
+
document.getElementById('diagram-instances-list').innerHTML = '<div class="empty-state" style="padding: 20px;"><div class="empty-state-icon">📭</div><div>No instances yet. Create one above!</div></div>';
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const html = machineInstances.map(inst => `
|
|
1005
|
+
<div class="instance-item" onclick="selectInstance('${inst.id}'); switchTab('overview');" style="margin-bottom: 10px;">
|
|
1006
|
+
<div class="instance-header">
|
|
1007
|
+
<span class="instance-id">${inst.id.substring(0, 8)}</span>
|
|
1008
|
+
<span class="badge ${inst.status}">${inst.currentState}</span>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
`).join('');
|
|
1012
|
+
|
|
1013
|
+
document.getElementById('diagram-instances-list').innerHTML = html;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
async function createInstanceFromDiagram() {
|
|
1017
|
+
const machineName = document.getElementById('diagram-machine').value;
|
|
967
1018
|
const component = getCurrentComponent();
|
|
968
|
-
if (!machineName) return alert('Please select a state machine');
|
|
969
|
-
if (!component) return alert('No component loaded');
|
|
1019
|
+
if (!machineName || !component) return alert('Please select a state machine');
|
|
970
1020
|
|
|
971
1021
|
const machine = component.stateMachines.find(m => m.name === machineName);
|
|
972
1022
|
let context = {};
|
|
973
1023
|
|
|
974
1024
|
if (machine?.contextSchema) {
|
|
975
1025
|
for (const key of Object.keys(machine.contextSchema)) {
|
|
976
|
-
const input = document.getElementById('
|
|
1026
|
+
const input = document.getElementById('diagram_ctx_' + key);
|
|
977
1027
|
if (input && input.value) {
|
|
978
1028
|
context[key] = input.type === 'number' ? parseFloat(input.value) : input.value;
|
|
979
1029
|
}
|
|
980
1030
|
}
|
|
981
1031
|
} else {
|
|
982
|
-
const json = document.getElementById('context-json')?.value;
|
|
983
|
-
if (json)
|
|
1032
|
+
const json = document.getElementById('diagram-context-json')?.value;
|
|
1033
|
+
if (json) {
|
|
1034
|
+
try {
|
|
1035
|
+
context = JSON.parse(json);
|
|
1036
|
+
} catch (e) {
|
|
1037
|
+
return alert('Invalid JSON: ' + e.message);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
984
1040
|
}
|
|
985
1041
|
|
|
986
|
-
|
|
987
1042
|
await fetch(`/api/components/${component.name}/instances`, {
|
|
988
1043
|
method: 'POST',
|
|
989
1044
|
headers: {'Content-Type': 'application/json'},
|
|
990
1045
|
body: JSON.stringify({machineName, context})
|
|
991
1046
|
});
|
|
992
|
-
|
|
1047
|
+
|
|
993
1048
|
// Clear form
|
|
994
1049
|
if (machine?.contextSchema) {
|
|
995
1050
|
for (const key of Object.keys(machine.contextSchema)) {
|
|
996
|
-
const input = document.getElementById('
|
|
1051
|
+
const input = document.getElementById('diagram_ctx_' + key);
|
|
997
1052
|
if (input) input.value = '';
|
|
998
1053
|
}
|
|
1054
|
+
} else {
|
|
1055
|
+
const textarea = document.getElementById('diagram-context-json');
|
|
1056
|
+
if (textarea) textarea.value = '';
|
|
999
1057
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1058
|
+
|
|
1059
|
+
// Reload instances to update the list
|
|
1002
1060
|
loadInstances();
|
|
1003
1061
|
}
|
|
1004
1062
|
|
|
1063
|
+
// Traceability
|
|
1064
|
+
async function loadTrace() {
|
|
1065
|
+
if (!selectedInstance) return;
|
|
1066
|
+
|
|
1067
|
+
const container = document.getElementById('trace-container');
|
|
1068
|
+
// In real version, fetch from /api/instances/:id/history
|
|
1069
|
+
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div>Traceability history would appear here</div></div>';
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function updateTraceSelector() {
|
|
1073
|
+
const select = document.getElementById('trace-instance');
|
|
1074
|
+
select.innerHTML = '<option value="">Select an instance...</option>' +
|
|
1075
|
+
instances.map(i => `<option value="${i.id}">${i.id.substring(0, 8)} - ${i.machineName}</option>`).join('');
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
|
|
1005
1079
|
function populateSelectors() {
|
|
1006
1080
|
if (!componentsData || componentsData.length === 0) return;
|
|
1007
1081
|
|
|
@@ -1017,7 +1091,6 @@
|
|
|
1017
1091
|
const machines = component.stateMachines.map(m => `<option value="${m.name}">${m.name}</option>`).join('');
|
|
1018
1092
|
document.getElementById('filter-machine').innerHTML = '<option value="">All Machines</option>' + machines;
|
|
1019
1093
|
document.getElementById('diagram-machine').innerHTML = '<option value="">Select...</option>' + machines;
|
|
1020
|
-
document.getElementById('create-machine').innerHTML = '<option value="">Select...</option>' + machines;
|
|
1021
1094
|
}
|
|
1022
1095
|
}
|
|
1023
1096
|
|
|
@@ -1027,8 +1100,8 @@
|
|
|
1027
1100
|
console.log('Component selected:', selected);
|
|
1028
1101
|
// Refresh selectors for the new component
|
|
1029
1102
|
populateSelectors();
|
|
1030
|
-
// Clear create form when switching components
|
|
1031
|
-
document.getElementById('create-form
|
|
1103
|
+
// Clear diagram create form when switching components
|
|
1104
|
+
document.getElementById('diagram-create-form').innerHTML = '<div class="empty-state" style="padding: 40px 20px;"><div class="empty-state-icon">👈</div><div>Select a state machine to create an instance</div></div>';
|
|
1032
1105
|
}
|
|
1033
1106
|
|
|
1034
1107
|
function exportState() {
|