@wallarm-org/gen-ui 0.0.0-stage → 0.1.0

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.
Files changed (118) hide show
  1. package/README.md +153 -2
  2. package/dist/a2ui/A2uiSurfaces.d.ts +16 -0
  3. package/dist/a2ui/A2uiSurfaces.js +61 -0
  4. package/dist/a2ui/catalog.d.ts +16 -0
  5. package/dist/a2ui/catalog.js +66 -0
  6. package/dist/a2ui/check.d.ts +4 -0
  7. package/dist/a2ui/check.js +34 -0
  8. package/dist/a2ui/express.d.ts +38 -0
  9. package/dist/a2ui/express.js +64 -0
  10. package/dist/a2ui/index.d.ts +8 -0
  11. package/dist/a2ui/index.js +4 -0
  12. package/dist/a2ui/prompt.d.ts +16 -0
  13. package/dist/a2ui/prompt.js +17 -0
  14. package/dist/a2ui/toA2uiSchema.d.ts +19 -0
  15. package/dist/a2ui/toA2uiSchema.js +61 -0
  16. package/dist/components/Alert.d.ts +11 -0
  17. package/dist/components/Alert.js +43 -0
  18. package/dist/components/AreaChart.d.ts +36 -0
  19. package/dist/components/AreaChart.js +90 -0
  20. package/dist/components/Badge.d.ts +11 -0
  21. package/dist/components/Badge.js +30 -0
  22. package/dist/components/BarList.d.ts +26 -0
  23. package/dist/components/BarList.js +66 -0
  24. package/dist/components/Button.d.ts +10 -0
  25. package/dist/components/Button.js +40 -0
  26. package/dist/components/Card.d.ts +2 -0
  27. package/dist/components/Card.js +17 -0
  28. package/dist/components/ColumnChart.d.ts +32 -0
  29. package/dist/components/ColumnChart.js +114 -0
  30. package/dist/components/Grid.d.ts +29 -0
  31. package/dist/components/Grid.js +72 -0
  32. package/dist/components/Heading.d.ts +10 -0
  33. package/dist/components/Heading.js +29 -0
  34. package/dist/components/HorizontalBarStack.d.ts +39 -0
  35. package/dist/components/HorizontalBarStack.js +67 -0
  36. package/dist/components/LineChart.d.ts +36 -0
  37. package/dist/components/LineChart.js +101 -0
  38. package/dist/components/Metric.d.ts +35 -0
  39. package/dist/components/Metric.js +85 -0
  40. package/dist/components/PieChart.d.ts +25 -0
  41. package/dist/components/PieChart.js +83 -0
  42. package/dist/components/Section.d.ts +5 -0
  43. package/dist/components/Section.js +39 -0
  44. package/dist/components/Stack.d.ts +18 -0
  45. package/dist/components/Stack.js +41 -0
  46. package/dist/components/Table.d.ts +30 -0
  47. package/dist/components/Table.js +128 -0
  48. package/dist/components/Text.d.ts +9 -0
  49. package/dist/components/Text.js +27 -0
  50. package/dist/components/index.d.ts +20 -0
  51. package/dist/components/index.js +55 -0
  52. package/dist/core/check.d.ts +35 -0
  53. package/dist/core/check.js +63 -0
  54. package/dist/core/define.d.ts +64 -0
  55. package/dist/core/define.js +7 -0
  56. package/dist/core/format.d.ts +24 -0
  57. package/dist/core/format.js +74 -0
  58. package/dist/core/guards.d.ts +2 -0
  59. package/dist/core/guards.js +2 -0
  60. package/dist/fixtures/apiInventory.d.ts +36 -0
  61. package/dist/fixtures/apiInventory.js +287 -0
  62. package/dist/fixtures/attackSummary.d.ts +13 -0
  63. package/dist/fixtures/attackSummary.js +329 -0
  64. package/dist/fixtures/dashboard.d.ts +119 -0
  65. package/dist/fixtures/dashboard.js +428 -0
  66. package/dist/fixtures/incident.d.ts +44 -0
  67. package/dist/fixtures/incident.js +331 -0
  68. package/dist/fixtures/index.d.ts +13 -0
  69. package/dist/fixtures/index.js +9 -0
  70. package/dist/fixtures/liveMonitor.d.ts +9 -0
  71. package/dist/fixtures/liveMonitor.js +180 -0
  72. package/dist/fixtures/openui.d.ts +16 -0
  73. package/dist/fixtures/openui.js +74 -0
  74. package/dist/fixtures/scenarios.d.ts +13 -0
  75. package/dist/fixtures/scenarios.js +39 -0
  76. package/dist/fixtures/stream.d.ts +79 -0
  77. package/dist/fixtures/stream.js +147 -0
  78. package/dist/fixtures/weeklyReport.d.ts +46 -0
  79. package/dist/fixtures/weeklyReport.js +266 -0
  80. package/dist/index.d.ts +8 -0
  81. package/dist/index.js +4 -0
  82. package/dist/json-render/JsonRenderSurface.d.ts +13 -0
  83. package/dist/json-render/JsonRenderSurface.js +63 -0
  84. package/dist/json-render/catalog.d.ts +64 -0
  85. package/dist/json-render/catalog.js +50 -0
  86. package/dist/json-render/check.d.ts +4 -0
  87. package/dist/json-render/check.js +13 -0
  88. package/dist/json-render/index.d.ts +6 -0
  89. package/dist/json-render/index.js +4 -0
  90. package/dist/json-render/spec.d.ts +23 -0
  91. package/dist/json-render/spec.js +77 -0
  92. package/dist/openui/OpenUiSurface.d.ts +24 -0
  93. package/dist/openui/OpenUiSurface.js +30 -0
  94. package/dist/openui/catalog.d.ts +23 -0
  95. package/dist/openui/catalog.js +65 -0
  96. package/dist/openui/check.d.ts +4 -0
  97. package/dist/openui/check.js +68 -0
  98. package/dist/openui/index.d.ts +5 -0
  99. package/dist/openui/index.js +3 -0
  100. package/dist/playground/GenUiChatDemo.d.ts +11 -0
  101. package/dist/playground/GenUiChatDemo.js +148 -0
  102. package/dist/playground/GenUiPlayground.d.ts +15 -0
  103. package/dist/playground/GenUiPlayground.js +413 -0
  104. package/dist/playground/index.d.ts +3 -0
  105. package/dist/playground/index.js +2 -0
  106. package/dist/playground/useEmulatedAgent.d.ts +37 -0
  107. package/dist/playground/useEmulatedAgent.js +221 -0
  108. package/dist/render/ChartSkeleton.d.ts +6 -0
  109. package/dist/render/ChartSkeleton.js +26 -0
  110. package/dist/render/GuardedView.d.ts +18 -0
  111. package/dist/render/GuardedView.js +24 -0
  112. package/dist/render/Placeholder.d.ts +16 -0
  113. package/dist/render/Placeholder.js +15 -0
  114. package/dist/render/SurfaceBoundary.d.ts +15 -0
  115. package/dist/render/SurfaceBoundary.js +15 -0
  116. package/dist/render/warn.d.ts +1 -0
  117. package/dist/render/warn.js +2 -0
  118. package/package.json +94 -4
@@ -0,0 +1,331 @@
1
+ import { bind, node } from "./stream.js";
2
+ const HOURS = Array.from({
3
+ length: 12
4
+ }, (_, i)=>new Date(Date.UTC(2026, 10, 12, 10 + i)).toISOString());
5
+ const ATTEMPTS = [
6
+ 140,
7
+ 152,
8
+ 168,
9
+ 1920,
10
+ 4860,
11
+ 7340,
12
+ 9410,
13
+ 10280,
14
+ 6120,
15
+ 3540,
16
+ 2310,
17
+ 1972
18
+ ];
19
+ const INCIDENT_DATA = {
20
+ incident: {
21
+ summary: 'Credential stuffing against POST /oauth/token since 13:00 UTC from a rotating pool of IPs. Most attempts were blocked; 6 logins succeeded and need review.'
22
+ },
23
+ kpi: {
24
+ attempts: ATTEMPTS.reduce((sum, v)=>sum + v, 0),
25
+ uniqueIps: 1284,
26
+ successfulLogins: 6,
27
+ blockedPercent: 97.4,
28
+ attemptsDelta: {
29
+ value: 3120,
30
+ trend: 'up',
31
+ sentiment: 'negative'
32
+ }
33
+ },
34
+ attempts: HOURS.map((x, i)=>({
35
+ x,
36
+ attempts: ATTEMPTS[i] ?? 0,
37
+ baseline: 150 + i % 3 * 10
38
+ })),
39
+ sourceIps: [
40
+ {
41
+ label: '203.0.113.77',
42
+ value: 8940,
43
+ color: 'red'
44
+ },
45
+ {
46
+ label: '198.51.100.14',
47
+ value: 6215,
48
+ color: 'red'
49
+ },
50
+ {
51
+ label: '192.0.2.201',
52
+ value: 4870,
53
+ color: 'amber'
54
+ },
55
+ {
56
+ label: '203.0.113.150',
57
+ value: 3302,
58
+ color: 'amber'
59
+ },
60
+ {
61
+ label: '198.51.100.88',
62
+ value: 2164,
63
+ color: 'slate'
64
+ }
65
+ ],
66
+ requests: [
67
+ {
68
+ id: 'r1',
69
+ time: '2026-11-12T21:58:12Z',
70
+ ip: '203.0.113.77',
71
+ method: 'POST',
72
+ path: '/oauth/token',
73
+ status: 429
74
+ },
75
+ {
76
+ id: 'r2',
77
+ time: '2026-11-12T21:58:09Z',
78
+ ip: '198.51.100.14',
79
+ method: 'POST',
80
+ path: '/oauth/token',
81
+ status: 401
82
+ },
83
+ {
84
+ id: 'r3',
85
+ time: '2026-11-12T21:57:55Z',
86
+ ip: '192.0.2.201',
87
+ method: 'POST',
88
+ path: '/oauth/token',
89
+ status: 403
90
+ },
91
+ {
92
+ id: 'r4',
93
+ time: '2026-11-12T21:57:41Z',
94
+ ip: '198.51.100.23',
95
+ method: 'POST',
96
+ path: '/oauth/token',
97
+ status: 200
98
+ },
99
+ {
100
+ id: 'r5',
101
+ time: '2026-11-12T21:57:30Z',
102
+ ip: '203.0.113.150',
103
+ method: 'POST',
104
+ path: '/oauth/token',
105
+ status: 401
106
+ }
107
+ ],
108
+ blockIps: [
109
+ '203.0.113.77',
110
+ '198.51.100.14',
111
+ '192.0.2.201',
112
+ '203.0.113.150',
113
+ '198.51.100.88'
114
+ ]
115
+ };
116
+ const INCIDENT_ACTIONS = [
117
+ {
118
+ name: 'block_ips',
119
+ description: 'Block the given source IPs (asks the user to approve first).'
120
+ },
121
+ {
122
+ name: 'create_rule',
123
+ description: 'Open the rule editor with a draft rule for the given endpoint and rule type.'
124
+ }
125
+ ];
126
+ const LAYOUT = [
127
+ [
128
+ node('root', 'Section', {
129
+ title: 'Brute force on /oauth/token',
130
+ subtitle: 'Incident · started 13:00 UTC'
131
+ }, [
132
+ 'summary',
133
+ 'kpis',
134
+ 'charts',
135
+ 'requests',
136
+ 'actions'
137
+ ]),
138
+ node('kpis', 'Grid', {
139
+ columns: '4'
140
+ }, [
141
+ 'm_attempts',
142
+ 'm_ips',
143
+ 'm_logins',
144
+ 'm_blocked'
145
+ ]),
146
+ node('charts', 'Grid', {
147
+ columns: '2'
148
+ }, [
149
+ 'attempts',
150
+ 'source_ips'
151
+ ])
152
+ ],
153
+ [
154
+ node('summary', 'Alert', {
155
+ title: 'Active brute force attack',
156
+ description: bind('/incident/summary'),
157
+ tone: 'destructive'
158
+ }),
159
+ node('m_attempts', 'Metric', {
160
+ title: 'Login attempts',
161
+ value: bind('/kpi/attempts'),
162
+ format: 'compact',
163
+ delta: bind('/kpi/attemptsDelta'),
164
+ caption: 'vs the previous hour'
165
+ }),
166
+ node('m_ips', 'Metric', {
167
+ title: 'Unique IPs',
168
+ value: bind('/kpi/uniqueIps')
169
+ }),
170
+ node('m_logins', 'Metric', {
171
+ title: 'Successful logins',
172
+ value: bind('/kpi/successfulLogins'),
173
+ caption: 'need review'
174
+ }),
175
+ node('m_blocked', 'Metric', {
176
+ title: 'Blocked',
177
+ value: bind('/kpi/blockedPercent'),
178
+ format: 'percent',
179
+ caption: 'of attempts'
180
+ })
181
+ ],
182
+ [
183
+ node('attempts', 'LineChart', {
184
+ title: 'Attempts per hour',
185
+ series: [
186
+ {
187
+ key: 'attempts',
188
+ label: 'Attempts',
189
+ color: 'red'
190
+ },
191
+ {
192
+ key: 'baseline',
193
+ label: 'Baseline',
194
+ color: 'slate',
195
+ variant: 'dashed'
196
+ }
197
+ ],
198
+ points: bind('/attempts'),
199
+ xFormat: 'time',
200
+ yFormat: 'compact'
201
+ }),
202
+ node('source_ips', 'BarList', {
203
+ title: 'Top source IPs',
204
+ items: bind('/sourceIps')
205
+ })
206
+ ],
207
+ [
208
+ node('requests', 'Table', {
209
+ title: 'Recent requests',
210
+ rowKey: 'id',
211
+ columns: [
212
+ {
213
+ key: 'time',
214
+ header: 'Time',
215
+ type: 'datetime',
216
+ format: 'datetime'
217
+ },
218
+ {
219
+ key: 'ip',
220
+ header: 'Source IP',
221
+ type: 'ip'
222
+ },
223
+ {
224
+ key: 'method',
225
+ header: 'Method',
226
+ type: 'method'
227
+ },
228
+ {
229
+ key: 'path',
230
+ header: 'Path',
231
+ type: 'code'
232
+ },
233
+ {
234
+ key: 'status',
235
+ header: 'Status',
236
+ type: 'status'
237
+ }
238
+ ],
239
+ rows: bind('/requests')
240
+ })
241
+ ],
242
+ [
243
+ node('actions', 'Stack', {
244
+ direction: 'row',
245
+ gap: '8'
246
+ }, [
247
+ 'block',
248
+ 'rule'
249
+ ]),
250
+ {
251
+ ...node('block', 'Button', {
252
+ label: 'Block the top 5 IPs',
253
+ variant: 'primary',
254
+ destructive: true
255
+ }),
256
+ press: {
257
+ action: 'block_ips',
258
+ params: {
259
+ ips: bind('/blockIps')
260
+ }
261
+ }
262
+ },
263
+ {
264
+ ...node('rule', 'Button', {
265
+ label: 'Create a rate limit rule'
266
+ }),
267
+ press: {
268
+ action: 'create_rule',
269
+ params: {
270
+ path: '/oauth/token',
271
+ type: 'rate_limit'
272
+ }
273
+ }
274
+ }
275
+ ]
276
+ ];
277
+ const INCIDENT_SCENARIO = {
278
+ id: 'incident',
279
+ prompt: 'Investigate the brute force incident on /oauth/token',
280
+ summary: 'An alert, KPI tiles, attempts per hour vs baseline, top source IPs, recent requests',
281
+ surfaceId: 'incident',
282
+ actions: INCIDENT_ACTIONS,
283
+ layout: LAYOUT,
284
+ data: [
285
+ {
286
+ after: 1,
287
+ values: [
288
+ {
289
+ path: '/incident',
290
+ value: INCIDENT_DATA.incident
291
+ },
292
+ {
293
+ path: '/kpi',
294
+ value: INCIDENT_DATA.kpi
295
+ }
296
+ ]
297
+ },
298
+ {
299
+ after: 2,
300
+ values: [
301
+ {
302
+ path: '/attempts',
303
+ value: INCIDENT_DATA.attempts
304
+ },
305
+ {
306
+ path: '/sourceIps',
307
+ value: INCIDENT_DATA.sourceIps
308
+ }
309
+ ]
310
+ },
311
+ {
312
+ after: 3,
313
+ values: [
314
+ {
315
+ path: '/requests',
316
+ value: INCIDENT_DATA.requests
317
+ }
318
+ ]
319
+ },
320
+ {
321
+ after: 4,
322
+ values: [
323
+ {
324
+ path: '/blockIps',
325
+ value: INCIDENT_DATA.blockIps
326
+ }
327
+ ]
328
+ }
329
+ ]
330
+ };
331
+ export { INCIDENT_ACTIONS, INCIDENT_DATA, INCIDENT_SCENARIO };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@wallarm-org/gen-ui/fixtures`: recorded agent answers to replay in tests and demos, in both
3
+ * formats and in arrival order, and the scenario builder that writes them.
4
+ */
5
+ export * from './apiInventory.js';
6
+ export * from './attackSummary.js';
7
+ export * from './dashboard.js';
8
+ export * from './incident.js';
9
+ export * from './liveMonitor.js';
10
+ export * from './openui.js';
11
+ export * from './scenarios.js';
12
+ export * from './stream.js';
13
+ export * from './weeklyReport.js';
@@ -0,0 +1,9 @@
1
+ export * from "./apiInventory.js";
2
+ export * from "./attackSummary.js";
3
+ export * from "./dashboard.js";
4
+ export * from "./incident.js";
5
+ export * from "./liveMonitor.js";
6
+ export * from "./openui.js";
7
+ export * from "./scenarios.js";
8
+ export * from "./stream.js";
9
+ export * from "./weeklyReport.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Live traffic: the agent lays the page out once, then the app keeps it current on its own,
3
+ * sending new values for the same bound paths every 2 s. No agent call per update.
4
+ */
5
+ import type { GenUiAction } from '../core/define.js';
6
+ import { type Scenario } from './stream.js';
7
+ export declare const LIVE_MONITOR_ACTIONS: readonly GenUiAction[];
8
+ /** Live traffic monitor: per-minute KPIs, a moving chart and top endpoints, kept current by the app. */
9
+ export declare const LIVE_MONITOR_SCENARIO: Scenario;
@@ -0,0 +1,180 @@
1
+ import { bind, node } from "./stream.js";
2
+ const wave = (m, base, amp, phase = 0)=>Math.round(base + amp * Math.sin((m + phase) / 4) + 0.4 * amp * Math.sin(1.3 * m));
3
+ const minutes = (shift)=>Array.from({
4
+ length: 30
5
+ }, (_, i)=>{
6
+ const m = i + shift;
7
+ return {
8
+ x: new Date(Date.UTC(2026, 10, 12, 14, m)).toISOString(),
9
+ requests: wave(m, 9000, 1500),
10
+ baseline: 9000,
11
+ attacks: wave(m, 40, 15, 3)
12
+ };
13
+ });
14
+ const ENDPOINTS = [
15
+ 'POST /api/v2/auth/login',
16
+ 'GET /api/v2/orders/{id}',
17
+ 'POST /graphql',
18
+ 'GET /search'
19
+ ];
20
+ const COLORS = [
21
+ 'red',
22
+ 'amber',
23
+ 'blue',
24
+ 'slate'
25
+ ];
26
+ const snapshot = (shift)=>{
27
+ const window = minutes(shift);
28
+ const last = window[window.length - 1];
29
+ return [
30
+ {
31
+ path: '/now',
32
+ value: {
33
+ requests: last?.requests ?? 0,
34
+ attacks: last?.attacks ?? 0,
35
+ blocked: Math.round((96 + 2 * Math.sin(shift / 5)) * 10) / 10
36
+ }
37
+ },
38
+ {
39
+ path: '/minutes',
40
+ value: window.map(({ x, requests, baseline })=>({
41
+ x,
42
+ requests,
43
+ baseline
44
+ }))
45
+ },
46
+ {
47
+ path: '/attacksSpark',
48
+ value: window.map(({ x, attacks })=>({
49
+ x,
50
+ y: attacks
51
+ }))
52
+ },
53
+ {
54
+ path: '/top',
55
+ value: ENDPOINTS.map((label, i)=>({
56
+ label,
57
+ value: wave(shift + 7 * i, 2400 - 500 * i, 300 - 50 * i),
58
+ color: COLORS[i]
59
+ })).sort((a, b)=>b.value - a.value)
60
+ }
61
+ ];
62
+ };
63
+ const LIVE_MONITOR_ACTIONS = [
64
+ {
65
+ name: 'open_traffic',
66
+ description: 'Open the traffic page for the last hour.'
67
+ }
68
+ ];
69
+ const LIVE_MONITOR_SCENARIO = {
70
+ id: 'live-monitor',
71
+ prompt: 'Watch traffic live for the next few minutes',
72
+ summary: 'Per-minute KPIs, a moving requests chart and top endpoints, refreshed by the app every 2 s',
73
+ surfaceId: 'live_monitor',
74
+ actions: LIVE_MONITOR_ACTIONS,
75
+ layout: [
76
+ [
77
+ node('root', 'Section', {
78
+ title: 'Live traffic',
79
+ subtitle: 'Per minute · the app refreshes it every 2 s'
80
+ }, [
81
+ 'kpis',
82
+ 'charts',
83
+ 'actions'
84
+ ]),
85
+ node('kpis', 'Grid', {
86
+ columns: '3'
87
+ }, [
88
+ 'rpm',
89
+ 'apm',
90
+ 'blocked'
91
+ ])
92
+ ],
93
+ [
94
+ node('rpm', 'Metric', {
95
+ title: 'Requests / min',
96
+ value: bind('/now/requests'),
97
+ format: 'compact',
98
+ caption: 'right now'
99
+ }),
100
+ node('apm', 'Metric', {
101
+ title: 'Attacks / min',
102
+ value: bind('/now/attacks'),
103
+ caption: 'last 30 minutes',
104
+ sparkline: bind('/attacksSpark'),
105
+ sparklineColor: 'negative'
106
+ }),
107
+ node('blocked', 'Metric', {
108
+ title: 'Blocked',
109
+ value: bind('/now/blocked'),
110
+ format: 'percent',
111
+ caption: 'of attacks this minute'
112
+ })
113
+ ],
114
+ [
115
+ node('charts', 'Grid', {
116
+ columns: '2'
117
+ }, [
118
+ 'traffic',
119
+ 'top'
120
+ ]),
121
+ node('traffic', 'LineChart', {
122
+ title: 'Requests per minute',
123
+ series: [
124
+ {
125
+ key: 'requests',
126
+ label: 'Requests',
127
+ color: 'brand'
128
+ },
129
+ {
130
+ key: 'baseline',
131
+ label: 'Usual',
132
+ color: 'slate',
133
+ variant: 'dashed'
134
+ }
135
+ ],
136
+ points: bind('/minutes'),
137
+ xFormat: 'time',
138
+ yFormat: 'compact'
139
+ }),
140
+ node('top', 'BarList', {
141
+ title: 'Most requested right now',
142
+ items: bind('/top')
143
+ })
144
+ ],
145
+ [
146
+ node('actions', 'Stack', {
147
+ direction: 'row',
148
+ gap: '8'
149
+ }, [
150
+ 'open'
151
+ ]),
152
+ {
153
+ ...node('open', 'Button', {
154
+ label: 'Open traffic'
155
+ }),
156
+ press: {
157
+ action: 'open_traffic',
158
+ params: {
159
+ range: 'last:1h'
160
+ }
161
+ }
162
+ }
163
+ ]
164
+ ],
165
+ data: [
166
+ {
167
+ after: 1,
168
+ values: snapshot(0).filter((d)=>'/now' === d.path || '/attacksSpark' === d.path)
169
+ },
170
+ {
171
+ after: 2,
172
+ values: snapshot(0).filter((d)=>'/minutes' === d.path || '/top' === d.path)
173
+ }
174
+ ],
175
+ live: {
176
+ every: 2000,
177
+ update: (tick)=>snapshot(tick)
178
+ }
179
+ };
180
+ export { LIVE_MONITOR_ACTIONS, LIVE_MONITOR_SCENARIO };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A recorded A2UI answer as the OpenUI Lang a model would write: a statement per component, the
3
+ * arguments in the entry's prop order, bindings as `$variables`. The app's data stays out of the
4
+ * program: its lines are data writes the surface gets as `data`. Data the agent sends itself
5
+ * becomes a `$variable` declaration.
6
+ */
7
+ import type { A2uiMessage } from '../a2ui/catalog.js';
8
+ import type { DataValue, StreamLine } from './stream.js';
9
+ /** One line of an OpenUI answer: a statement from the agent, or a data write from the app. */
10
+ export type OpenUiLine = string | DataValue;
11
+ export declare const toOpenUiLines: (lines: readonly StreamLine<A2uiMessage>[]) => StreamLine<OpenUiLine>[];
12
+ /** The program and the app's data so far, as `OpenUiSurface` takes them. */
13
+ export declare const openUiAnswer: (lines: readonly StreamLine<OpenUiLine>[]) => {
14
+ program: string;
15
+ data: Record<string, unknown>;
16
+ };
@@ -0,0 +1,74 @@
1
+ import { wallarmComponents } from "../components/index.js";
2
+ import { isRecord } from "../core/guards.js";
3
+ const toOpenUiLines = (lines)=>lines.flatMap(({ origin, line })=>{
4
+ if ('updateDataModel' in line) {
5
+ const { path = '/', value } = line.updateDataModel;
6
+ return 'app' === origin ? [
7
+ {
8
+ origin,
9
+ line: {
10
+ path,
11
+ value
12
+ }
13
+ }
14
+ ] : [
15
+ {
16
+ origin,
17
+ line: `${variable(path)} = ${literal(value)}`
18
+ }
19
+ ];
20
+ }
21
+ const components = 'createSurface' in line ? line.createSurface.components : 'updateComponents' in line ? line.updateComponents.components : void 0;
22
+ return (components ?? []).map((c)=>({
23
+ origin,
24
+ line: statement(c)
25
+ }));
26
+ });
27
+ const openUiAnswer = (lines)=>{
28
+ const program = [];
29
+ let data = {};
30
+ for (const { line } of lines)if ('string' == typeof line) program.push(line);
31
+ else data = setAt(data, line.path.split('/').filter(Boolean), line.value);
32
+ return {
33
+ program: program.join('\n'),
34
+ data
35
+ };
36
+ };
37
+ const statement = ({ id, component, children, ...props })=>{
38
+ const entry = wallarmComponents.find((c)=>c.name === component);
39
+ if (!entry) throw new Error(`no catalog entry "${component}"`);
40
+ const keys = [
41
+ ...Object.keys(entry.props.shape),
42
+ ...entry.events ?? [],
43
+ ...entry.hasChildren ? [
44
+ 'children'
45
+ ] : []
46
+ ];
47
+ const args = keys.map((key)=>'children' === key ? Array.isArray(children) && `[${children.join(', ')}]` : (entry.events ?? []).includes(key) ? step(props[key]) : void 0 !== props[key] && literal(props[key]));
48
+ while(args.length && !args[args.length - 1])args.pop();
49
+ return `${id} = ${component}(${args.map((arg)=>arg || 'null').join(', ')})`;
50
+ };
51
+ const step = (value)=>{
52
+ if (!isRecord(value) || !isRecord(value.event)) return false;
53
+ const { name, context } = value.event;
54
+ return `@${String(name)}(${isRecord(context) && Object.keys(context).length ? literal(context) : ''})`;
55
+ };
56
+ const literal = (value)=>{
57
+ if (isRecord(value) && 'string' == typeof value['@path']) return variable(value['@path']);
58
+ if (Array.isArray(value)) return `[${value.map(literal).join(', ')}]`;
59
+ if (isRecord(value)) return `{${Object.entries(value).map(([k, v])=>`${/^[A-Za-z_]\w*$/.test(k) ? k : JSON.stringify(k)}: ${literal(v)}`).join(', ')}}`;
60
+ return JSON.stringify(value);
61
+ };
62
+ const variable = (path)=>{
63
+ const [first, ...rest] = path.split('/').filter(Boolean);
64
+ return `$${first}${rest.map((s)=>/^\d+$/.test(s) ? `[${s}]` : `.${s}`).join('')}`;
65
+ };
66
+ const setAt = (target, [key, ...rest], value)=>{
67
+ if (void 0 === key) return isRecord(value) ? value : target;
68
+ const current = target[key];
69
+ return {
70
+ ...target,
71
+ [key]: rest.length ? setAt(isRecord(current) ? current : {}, rest, value) : value
72
+ };
73
+ };
74
+ export { openUiAnswer, toOpenUiLines };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Every recorded answer a demo can replay, in picker order: what the user asked, a line about what
3
+ * the answer shows, and its streams in every format.
4
+ */
5
+ import type { GenUiAction } from '../core/define.js';
6
+ import { type ScenarioStreams } from './stream.js';
7
+ export interface ReplayScenario extends ScenarioStreams {
8
+ id: string;
9
+ prompt: string;
10
+ summary: string;
11
+ actions: readonly GenUiAction[];
12
+ }
13
+ export declare const SCENARIOS: readonly ReplayScenario[];
@@ -0,0 +1,39 @@
1
+ import { API_INVENTORY_SCENARIO } from "./apiInventory.js";
2
+ import { ATTACK_SUMMARY_A2UI, ATTACK_SUMMARY_ACTIONS, ATTACK_SUMMARY_JSON_RENDER } from "./attackSummary.js";
3
+ import { DASHBOARD_SCENARIO } from "./dashboard.js";
4
+ import { INCIDENT_SCENARIO } from "./incident.js";
5
+ import { LIVE_MONITOR_SCENARIO } from "./liveMonitor.js";
6
+ import { toOpenUiLines } from "./openui.js";
7
+ import { toStreams } from "./stream.js";
8
+ import { WEEKLY_REPORT_SCENARIO } from "./weeklyReport.js";
9
+ const SCENARIOS = [
10
+ {
11
+ id: 'attack-summary',
12
+ prompt: 'Show attacks for the last 24 hours',
13
+ summary: 'Two metrics, the top attacked endpoints, an alert and two actions',
14
+ actions: ATTACK_SUMMARY_ACTIONS,
15
+ a2ui: ATTACK_SUMMARY_A2UI.map((line)=>({
16
+ origin: 'agent',
17
+ line
18
+ })),
19
+ openui: toOpenUiLines(ATTACK_SUMMARY_A2UI.map((line)=>({
20
+ origin: 'agent',
21
+ line
22
+ }))),
23
+ jsonRender: ATTACK_SUMMARY_JSON_RENDER.map((line)=>({
24
+ origin: 'agent',
25
+ line
26
+ }))
27
+ },
28
+ ...[
29
+ DASHBOARD_SCENARIO,
30
+ LIVE_MONITOR_SCENARIO,
31
+ INCIDENT_SCENARIO,
32
+ API_INVENTORY_SCENARIO,
33
+ WEEKLY_REPORT_SCENARIO
34
+ ].map((scenario)=>({
35
+ ...scenario,
36
+ ...toStreams(scenario)
37
+ }))
38
+ ];
39
+ export { SCENARIOS };