@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,79 @@
1
+ /**
2
+ * Writes a recorded answer once and streams it in every format. A scenario is the agent's layout,
3
+ * in the chunks it would send them, with every figure bound to a path, plus the app's data for
4
+ * those paths and when it arrives. `toStreams` turns it into A2UI messages and json-render patches,
5
+ * each line marked with who sent it.
6
+ */
7
+ import type { SpecStreamLine } from '@json-render/core';
8
+ import { type A2uiMessage } from '../a2ui/catalog.js';
9
+ import type { GenUiAction } from '../core/define.js';
10
+ import { type OpenUiLine } from './openui.js';
11
+ /** A value the app fills: becomes `{ "@path" }` in A2UI and `{ "$state" }` in json-render. */
12
+ export declare const bind: (path: string) => {
13
+ bind: string;
14
+ };
15
+ /** One component in the agent's layout. */
16
+ export interface LayoutNode {
17
+ id: string;
18
+ type: string;
19
+ props?: Record<string, unknown>;
20
+ children?: string[];
21
+ /** For a Button: the action and its context, which may hold bindings too. */
22
+ press?: {
23
+ action: string;
24
+ params: Record<string, unknown>;
25
+ };
26
+ }
27
+ export declare const node: (id: string, type: string, props?: LayoutNode['props'], children?: string[]) => LayoutNode;
28
+ export interface Scenario {
29
+ /** Stable id, e.g. for a URL. */
30
+ id: string;
31
+ /** What the user asked. */
32
+ prompt: string;
33
+ /** One line under the prompt in a picker: what the answer shows. */
34
+ summary: string;
35
+ surfaceId: string;
36
+ actions: readonly GenUiAction[];
37
+ /** The agent's components in arrival order, one array per chunk it sends. */
38
+ layout: LayoutNode[][];
39
+ /** The app's data: each batch arrives right after the layout chunk at `after` (its index). */
40
+ data: {
41
+ after: number;
42
+ values: DataValue[];
43
+ }[];
44
+ /** Data the app keeps sending once the answer is complete: no agent involved. */
45
+ live?: LiveData;
46
+ }
47
+ /** One write to the data model: a bound path and its new value. */
48
+ export interface DataValue {
49
+ path: string;
50
+ value: unknown;
51
+ }
52
+ /**
53
+ * Live data: the app sends new values for paths the layout already binds, every `every` ms, and
54
+ * the page updates without asking the agent again. `update(tick)` gives the values of update
55
+ * number `tick` (1, 2, …); it must be pure, so a replay is the same every time.
56
+ */
57
+ export interface LiveData {
58
+ every: number;
59
+ update: (tick: number) => DataValue[];
60
+ }
61
+ /** Who sent a line: the agent (layout) or the app (data). */
62
+ export type StreamOrigin = 'agent' | 'app';
63
+ export interface StreamLine<T> {
64
+ origin: StreamOrigin;
65
+ line: T;
66
+ }
67
+ export interface ScenarioStreams {
68
+ a2ui: StreamLine<A2uiMessage>[];
69
+ jsonRender: StreamLine<SpecStreamLine>[];
70
+ /** OpenUI Lang statements from the agent, data writes from the app (`toOpenUiLines`). */
71
+ openui: StreamLine<OpenUiLine>[];
72
+ /** The app's live updates, as lines of both formats, for update number `tick`. */
73
+ liveStreams?: {
74
+ every: number;
75
+ lines: (tick: number) => Omit<ScenarioStreams, 'liveStreams'>;
76
+ };
77
+ }
78
+ /** The scenario as both formats stream it. The first layout chunk's first node is the root. */
79
+ export declare const toStreams: (s: Scenario) => ScenarioStreams;
@@ -0,0 +1,147 @@
1
+ import { WALLARM_A2UI_CATALOG_ID } from "../a2ui/catalog.js";
2
+ import { toOpenUiLines } from "./openui.js";
3
+ const bind = (path)=>({
4
+ bind: path
5
+ });
6
+ const isBound = (v)=>'object' == typeof v && null !== v && 'bind' in v && 1 === Object.keys(v).length;
7
+ const node = (id, type, props = {}, children)=>({
8
+ id,
9
+ type,
10
+ props,
11
+ children
12
+ });
13
+ const mapBindings = (value, to)=>{
14
+ if (isBound(value)) return to(value.bind);
15
+ if (Array.isArray(value)) return value.map((v)=>mapBindings(v, to));
16
+ if ('object' == typeof value && null !== value) return Object.fromEntries(Object.entries(value).map(([k, v])=>[
17
+ k,
18
+ mapBindings(v, to)
19
+ ]));
20
+ return value;
21
+ };
22
+ const interleave = (start, chunks, dataAfter)=>[
23
+ ...start.map((line)=>({
24
+ origin: 'agent',
25
+ line
26
+ })),
27
+ ...chunks.flatMap((chunk, i)=>[
28
+ ...chunk.map((line)=>({
29
+ origin: 'agent',
30
+ line
31
+ })),
32
+ ...dataAfter(i).map((line)=>({
33
+ origin: 'app',
34
+ line
35
+ }))
36
+ ])
37
+ ];
38
+ const toA2uiPath = (path)=>({
39
+ '@path': path
40
+ });
41
+ const toJsonRenderPath = (path)=>({
42
+ $state: path
43
+ });
44
+ const a2uiComponent = (n)=>({
45
+ id: n.id,
46
+ component: n.type,
47
+ ...mapBindings(n.props, toA2uiPath),
48
+ ...n.children ? {
49
+ children: n.children
50
+ } : {},
51
+ ...n.press ? {
52
+ press: {
53
+ event: {
54
+ name: n.press.action,
55
+ context: mapBindings(n.press.params, toA2uiPath)
56
+ }
57
+ }
58
+ } : {}
59
+ });
60
+ const jsonRenderElement = (n)=>({
61
+ op: 'add',
62
+ path: `/elements/${n.id}`,
63
+ value: {
64
+ type: n.type,
65
+ props: mapBindings(n.props, toJsonRenderPath),
66
+ children: n.children ?? [],
67
+ ...n.press ? {
68
+ on: {
69
+ press: {
70
+ action: n.press.action,
71
+ params: mapBindings(n.press.params, toJsonRenderPath)
72
+ }
73
+ }
74
+ } : {}
75
+ }
76
+ });
77
+ const a2uiData = (surfaceId, d)=>({
78
+ version: '1.0',
79
+ updateDataModel: {
80
+ surfaceId,
81
+ path: d.path,
82
+ value: d.value
83
+ }
84
+ });
85
+ const jsonRenderData = (d)=>({
86
+ op: 'add',
87
+ path: `/state${d.path}`,
88
+ value: d.value
89
+ });
90
+ const fromApp = (line)=>({
91
+ origin: 'app',
92
+ line
93
+ });
94
+ const toStreams = (s)=>{
95
+ const dataAt = (i)=>s.data.filter((d)=>d.after === i).flatMap((d)=>d.values);
96
+ const rootId = s.layout[0]?.[0]?.id ?? 'root';
97
+ const a2ui = interleave([
98
+ {
99
+ version: '1.0',
100
+ createSurface: {
101
+ surfaceId: s.surfaceId,
102
+ catalogId: WALLARM_A2UI_CATALOG_ID
103
+ }
104
+ }
105
+ ], s.layout.map((chunk)=>[
106
+ {
107
+ version: '1.0',
108
+ updateComponents: {
109
+ surfaceId: s.surfaceId,
110
+ components: chunk.map(a2uiComponent)
111
+ }
112
+ }
113
+ ]), (i)=>dataAt(i).map((d)=>a2uiData(s.surfaceId, d)));
114
+ return {
115
+ a2ui,
116
+ openui: toOpenUiLines(a2ui),
117
+ jsonRender: [
118
+ {
119
+ origin: 'app',
120
+ line: {
121
+ op: 'add',
122
+ path: '/state',
123
+ value: {}
124
+ }
125
+ },
126
+ ...interleave([
127
+ {
128
+ op: 'add',
129
+ path: '/root',
130
+ value: rootId
131
+ }
132
+ ], s.layout.map((chunk)=>chunk.map(jsonRenderElement)), (i)=>dataAt(i).map(jsonRenderData))
133
+ ],
134
+ liveStreams: s.live && {
135
+ every: s.live.every,
136
+ lines: (tick)=>{
137
+ const values = s.live?.update(tick) ?? [];
138
+ return {
139
+ a2ui: values.map((d)=>fromApp(a2uiData(s.surfaceId, d))),
140
+ jsonRender: values.map((d)=>fromApp(jsonRenderData(d))),
141
+ openui: values.map(fromApp)
142
+ };
143
+ }
144
+ }
145
+ };
146
+ };
147
+ export { bind, node, toStreams };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A weekly security report as an agent would write it: a summary, KPIs, the attack trend, a few
3
+ * breakdowns and recommendations. The agent writes the words, the app fills every figure.
4
+ */
5
+ import type { GenUiAction } from '../core/define.js';
6
+ import { type Scenario } from './stream.js';
7
+ export declare const WEEKLY_REPORT_DATA: {
8
+ kpi: {
9
+ requests: number;
10
+ requestsDelta: {
11
+ value: number;
12
+ trend: string;
13
+ sentiment: string;
14
+ };
15
+ attacks: number;
16
+ attacksDelta: {
17
+ value: number;
18
+ trend: string;
19
+ sentiment: string;
20
+ };
21
+ blockedPercent: number;
22
+ blockedDelta: {
23
+ value: number;
24
+ trend: string;
25
+ sentiment: string;
26
+ };
27
+ };
28
+ daily: {
29
+ x: string;
30
+ attacks: number;
31
+ blocked: number;
32
+ }[];
33
+ responseCodes: {
34
+ name: string;
35
+ value: number;
36
+ color: string;
37
+ }[];
38
+ attackTypes: {
39
+ label: string;
40
+ value: number;
41
+ color: string;
42
+ }[];
43
+ };
44
+ export declare const WEEKLY_REPORT_ACTIONS: readonly GenUiAction[];
45
+ /** Weekly security report: summary, KPIs, attack trend, breakdowns and recommendations. */
46
+ export declare const WEEKLY_REPORT_SCENARIO: Scenario;
@@ -0,0 +1,266 @@
1
+ import { bind, node } from "./stream.js";
2
+ const DAYS = Array.from({
3
+ length: 7
4
+ }, (_, i)=>new Date(Date.UTC(2026, 10, 5 + i)).toISOString());
5
+ const daily = DAYS.map((x, i)=>{
6
+ const attacks = 52000 + 4000 * i + (i % 2 ? 3000 : 0);
7
+ return {
8
+ x,
9
+ attacks,
10
+ blocked: Math.round(0.964 * attacks)
11
+ };
12
+ });
13
+ const WEEKLY_REPORT_DATA = {
14
+ kpi: {
15
+ requests: 92418306,
16
+ requestsDelta: {
17
+ value: 4,
18
+ trend: 'up',
19
+ sentiment: 'neutral'
20
+ },
21
+ attacks: daily.reduce((sum, d)=>sum + d.attacks, 0),
22
+ attacksDelta: {
23
+ value: 18,
24
+ trend: 'up',
25
+ sentiment: 'negative'
26
+ },
27
+ blockedPercent: 96.4,
28
+ blockedDelta: {
29
+ value: 1.2,
30
+ trend: 'up',
31
+ sentiment: 'positive'
32
+ }
33
+ },
34
+ daily: daily,
35
+ responseCodes: [
36
+ {
37
+ name: '2xx Success',
38
+ value: 88604020,
39
+ color: 'green'
40
+ },
41
+ {
42
+ name: '3xx Redirect',
43
+ value: 2114870,
44
+ color: 'blue'
45
+ },
46
+ {
47
+ name: '4xx Blocked',
48
+ value: 1486210,
49
+ color: 'amber'
50
+ },
51
+ {
52
+ name: '5xx Error',
53
+ value: 213206,
54
+ color: 'red'
55
+ }
56
+ ],
57
+ attackTypes: [
58
+ {
59
+ label: 'SQL injection',
60
+ value: 164520,
61
+ color: 'red'
62
+ },
63
+ {
64
+ label: "Cross-site scripting",
65
+ value: 121300,
66
+ color: 'amber'
67
+ },
68
+ {
69
+ label: 'BOLA',
70
+ value: 78440,
71
+ color: 'purple'
72
+ },
73
+ {
74
+ label: 'Brute force',
75
+ value: 52180,
76
+ color: 'blue'
77
+ },
78
+ {
79
+ label: 'Path traversal',
80
+ value: 40560,
81
+ color: 'slate'
82
+ }
83
+ ]
84
+ };
85
+ const WEEKLY_REPORT_ACTIONS = [
86
+ {
87
+ name: 'share_report',
88
+ description: 'Share the report with the given team (asks the user to pick recipients first).'
89
+ }
90
+ ];
91
+ const LAYOUT = [
92
+ [
93
+ node('root', 'Section', {
94
+ title: 'Weekly security report',
95
+ subtitle: '5-11 Nov · all applications'
96
+ }, [
97
+ 'summary',
98
+ 'kpis',
99
+ 'charts',
100
+ 'response_codes',
101
+ 'recommendations',
102
+ 'actions'
103
+ ]),
104
+ node('summary', 'Text', {
105
+ text: 'Attacks rose again this week, led by SQL injection against the login endpoint. Nearly all of them were blocked, and error rates stayed flat.'
106
+ }),
107
+ node('kpis', 'Grid', {
108
+ columns: '3'
109
+ }, [
110
+ 'm_requests',
111
+ 'm_attacks',
112
+ 'm_blocked'
113
+ ]),
114
+ node('charts', 'Grid', {
115
+ columns: '2'
116
+ }, [
117
+ 'attack_trend',
118
+ 'attack_types'
119
+ ])
120
+ ],
121
+ [
122
+ node('m_requests', 'Metric', {
123
+ title: 'Requests',
124
+ value: bind('/kpi/requests'),
125
+ format: 'compact',
126
+ delta: bind('/kpi/requestsDelta'),
127
+ caption: 'vs the previous week'
128
+ }),
129
+ node('m_attacks', 'Metric', {
130
+ title: 'Attacks',
131
+ value: bind('/kpi/attacks'),
132
+ format: 'compact',
133
+ delta: bind('/kpi/attacksDelta'),
134
+ caption: 'vs the previous week'
135
+ }),
136
+ node('m_blocked', 'Metric', {
137
+ title: 'Blocked',
138
+ value: bind('/kpi/blockedPercent'),
139
+ format: 'percent',
140
+ delta: bind('/kpi/blockedDelta'),
141
+ caption: 'of attacks'
142
+ })
143
+ ],
144
+ [
145
+ node('attack_trend', 'AreaChart', {
146
+ title: 'Attacks vs blocked',
147
+ variant: 'standard',
148
+ series: [
149
+ {
150
+ key: 'attacks',
151
+ label: 'Attacks',
152
+ color: 'red'
153
+ },
154
+ {
155
+ key: 'blocked',
156
+ label: 'Blocked',
157
+ color: 'green'
158
+ }
159
+ ],
160
+ points: bind('/daily'),
161
+ xFormat: 'date',
162
+ yFormat: 'compact'
163
+ }),
164
+ node('attack_types', 'BarList', {
165
+ title: 'Top attack types',
166
+ items: bind('/attackTypes')
167
+ })
168
+ ],
169
+ [
170
+ node('response_codes', 'HorizontalBarStack', {
171
+ title: 'Response codes',
172
+ items: bind('/responseCodes'),
173
+ legend: true
174
+ })
175
+ ],
176
+ [
177
+ node('recommendations', 'Stack', {
178
+ gap: '8'
179
+ }, [
180
+ 'rec_title',
181
+ 'rec_1',
182
+ 'rec_2',
183
+ 'rec_3'
184
+ ]),
185
+ node('rec_title', 'Heading', {
186
+ text: 'Recommendations',
187
+ size: 'md'
188
+ }),
189
+ node('rec_1', 'Alert', {
190
+ tone: 'info',
191
+ title: 'Turn on brute-force protection',
192
+ description: 'POST /api/v2/auth/login takes most of the credential attacks.'
193
+ }),
194
+ node('rec_2', 'Alert', {
195
+ tone: 'info',
196
+ title: 'Enforce object ownership checks',
197
+ description: 'The BOLA hits on /api/v2/orders read orders that belong to other users.'
198
+ }),
199
+ node('rec_3', 'Alert', {
200
+ tone: 'info',
201
+ title: 'Block path traversal',
202
+ description: 'Move the path traversal rules from monitoring to blocking mode.'
203
+ }),
204
+ node('actions', 'Stack', {
205
+ direction: 'row',
206
+ gap: '8'
207
+ }, [
208
+ 'share'
209
+ ]),
210
+ {
211
+ ...node('share', 'Button', {
212
+ label: 'Share with the team',
213
+ variant: 'primary'
214
+ }),
215
+ press: {
216
+ action: 'share_report',
217
+ params: {
218
+ report: 'weekly-security',
219
+ week: '2026-W45'
220
+ }
221
+ }
222
+ }
223
+ ]
224
+ ];
225
+ const WEEKLY_REPORT_SCENARIO = {
226
+ id: 'weekly-report',
227
+ prompt: 'Write a weekly security report for my team',
228
+ summary: 'A summary, KPI tiles, attacks vs blocked by day, breakdowns and recommendations',
229
+ surfaceId: 'weekly_report',
230
+ actions: WEEKLY_REPORT_ACTIONS,
231
+ layout: LAYOUT,
232
+ data: [
233
+ {
234
+ after: 1,
235
+ values: [
236
+ {
237
+ path: '/kpi',
238
+ value: WEEKLY_REPORT_DATA.kpi
239
+ }
240
+ ]
241
+ },
242
+ {
243
+ after: 2,
244
+ values: [
245
+ {
246
+ path: '/daily',
247
+ value: WEEKLY_REPORT_DATA.daily
248
+ },
249
+ {
250
+ path: '/attackTypes',
251
+ value: WEEKLY_REPORT_DATA.attackTypes
252
+ }
253
+ ]
254
+ },
255
+ {
256
+ after: 3,
257
+ values: [
258
+ {
259
+ path: '/responseCodes',
260
+ value: WEEKLY_REPORT_DATA.responseCodes
261
+ }
262
+ ]
263
+ }
264
+ ]
265
+ };
266
+ export { WEEKLY_REPORT_ACTIONS, WEEKLY_REPORT_DATA, WEEKLY_REPORT_SCENARIO };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@wallarm-org/gen-ui`: the catalog and the API to define entries. The formats live in their own
3
+ * entry points, `@wallarm-org/gen-ui/a2ui` and `@wallarm-org/gen-ui/json-render`.
4
+ */
5
+ export * from './components/index.js';
6
+ export { type AnswerNode, type CheckOptions, describeIssues, type GenUiIssue, } from './core/check.js';
7
+ export { allOf, defineComponent, type GenUiAction, type GenUiActionHandler, type GenUiComponent, type GenUiViewProps, seriesKey, subsetOf, unique, type Values, } from './core/define.js';
8
+ export { formatNumber, formatTime, NUMBER_FORMATS, type NumberFormat, numberFormat, TIME_FORMATS, type TimeFormat, timeFormat, } from './core/format.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./components/index.js";
2
+ export { describeIssues } from "./core/check.js";
3
+ export { allOf, defineComponent, seriesKey, subsetOf, unique } from "./core/define.js";
4
+ export { NUMBER_FORMATS, TIME_FORMATS, formatNumber, formatTime, numberFormat, timeFormat } from "./core/format.js";
@@ -0,0 +1,13 @@
1
+ import { type FC } from 'react';
2
+ import { type Spec } from '@json-render/core';
3
+ import type { GenUiActionHandler } from '../core/define.js';
4
+ import type { JsonRenderCatalog } from './catalog.js';
5
+ export interface JsonRenderSurfaceProps {
6
+ catalog: JsonRenderCatalog;
7
+ /** The spec so far (`specFromLines`); it grows while the model streams. */
8
+ spec: Spec | null;
9
+ streaming?: boolean;
10
+ onAction: GenUiActionHandler;
11
+ }
12
+ /** Renders one json-render spec through the catalog: actions allowlisted, state kept in step. */
13
+ export declare const JsonRenderSurface: FC<JsonRenderSurfaceProps>;
@@ -0,0 +1,63 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { createStateStore } from "@json-render/core";
4
+ import { JSONUIProvider, Renderer } from "@json-render/react";
5
+ import { isRecord } from "../core/guards.js";
6
+ import { Placeholder } from "../render/Placeholder.js";
7
+ import { SurfaceBoundary } from "../render/SurfaceBoundary.js";
8
+ import { sanitizeSpec } from "./spec.js";
9
+ const JsonRenderSurface = ({ catalog, spec, streaming, onAction })=>{
10
+ const onActionRef = useRef(onAction);
11
+ useEffect(()=>{
12
+ onActionRef.current = onAction;
13
+ });
14
+ const [handlers] = useState(()=>Object.fromEntries(catalog.actions.map((a)=>[
15
+ a.name,
16
+ (params)=>onActionRef.current(a.name, params)
17
+ ])));
18
+ const [store] = useState(()=>createStateStore(spec?.state ?? {}));
19
+ const applied = useRef({});
20
+ useEffect(()=>{
21
+ const next = leaves(spec?.state ?? {});
22
+ const updates = {};
23
+ for (const [pointer, value] of Object.entries(next)){
24
+ const json = JSON.stringify(value);
25
+ if (applied.current[pointer] !== json) updates[pointer] = value;
26
+ applied.current[pointer] = json;
27
+ }
28
+ for (const pointer of Object.keys(applied.current))if (!(pointer in next)) {
29
+ updates[pointer] = void 0;
30
+ delete applied.current[pointer];
31
+ }
32
+ if (Object.keys(updates).length) store.update(updates);
33
+ }, [
34
+ spec?.state,
35
+ store
36
+ ]);
37
+ return /*#__PURE__*/ jsx(JSONUIProvider, {
38
+ registry: catalog.registry,
39
+ store: store,
40
+ handlers: handlers,
41
+ children: /*#__PURE__*/ jsx(SurfaceBoundary, {
42
+ children: /*#__PURE__*/ jsx(Renderer, {
43
+ spec: sanitizeSpec(spec, catalog.actions),
44
+ registry: catalog.registry,
45
+ loading: streaming,
46
+ fallback: PendingElement
47
+ })
48
+ })
49
+ });
50
+ };
51
+ const PendingElement = ({ element })=>/*#__PURE__*/ jsx(Placeholder, {
52
+ id: String(element.props.id ?? element.type)
53
+ });
54
+ const leaves = (value, prefix = '')=>Object.fromEntries(Object.entries(value).flatMap(([key, v])=>{
55
+ const pointer = `${prefix}/${key.replace(/~/g, '~0').replace(/\//g, '~1')}`;
56
+ return isRecord(v) ? Object.entries(leaves(v, pointer)) : [
57
+ [
58
+ pointer,
59
+ v
60
+ ]
61
+ ];
62
+ }));
63
+ export { JsonRenderSurface };