@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.
- package/README.md +153 -2
- package/dist/a2ui/A2uiSurfaces.d.ts +16 -0
- package/dist/a2ui/A2uiSurfaces.js +61 -0
- package/dist/a2ui/catalog.d.ts +16 -0
- package/dist/a2ui/catalog.js +66 -0
- package/dist/a2ui/check.d.ts +4 -0
- package/dist/a2ui/check.js +34 -0
- package/dist/a2ui/express.d.ts +38 -0
- package/dist/a2ui/express.js +64 -0
- package/dist/a2ui/index.d.ts +8 -0
- package/dist/a2ui/index.js +4 -0
- package/dist/a2ui/prompt.d.ts +16 -0
- package/dist/a2ui/prompt.js +17 -0
- package/dist/a2ui/toA2uiSchema.d.ts +19 -0
- package/dist/a2ui/toA2uiSchema.js +61 -0
- package/dist/components/Alert.d.ts +11 -0
- package/dist/components/Alert.js +43 -0
- package/dist/components/AreaChart.d.ts +36 -0
- package/dist/components/AreaChart.js +90 -0
- package/dist/components/Badge.d.ts +11 -0
- package/dist/components/Badge.js +30 -0
- package/dist/components/BarList.d.ts +26 -0
- package/dist/components/BarList.js +66 -0
- package/dist/components/Button.d.ts +10 -0
- package/dist/components/Button.js +40 -0
- package/dist/components/Card.d.ts +2 -0
- package/dist/components/Card.js +17 -0
- package/dist/components/ColumnChart.d.ts +32 -0
- package/dist/components/ColumnChart.js +114 -0
- package/dist/components/Grid.d.ts +29 -0
- package/dist/components/Grid.js +72 -0
- package/dist/components/Heading.d.ts +10 -0
- package/dist/components/Heading.js +29 -0
- package/dist/components/HorizontalBarStack.d.ts +39 -0
- package/dist/components/HorizontalBarStack.js +67 -0
- package/dist/components/LineChart.d.ts +36 -0
- package/dist/components/LineChart.js +101 -0
- package/dist/components/Metric.d.ts +35 -0
- package/dist/components/Metric.js +85 -0
- package/dist/components/PieChart.d.ts +25 -0
- package/dist/components/PieChart.js +83 -0
- package/dist/components/Section.d.ts +5 -0
- package/dist/components/Section.js +39 -0
- package/dist/components/Stack.d.ts +18 -0
- package/dist/components/Stack.js +41 -0
- package/dist/components/Table.d.ts +30 -0
- package/dist/components/Table.js +128 -0
- package/dist/components/Text.d.ts +9 -0
- package/dist/components/Text.js +27 -0
- package/dist/components/index.d.ts +20 -0
- package/dist/components/index.js +55 -0
- package/dist/core/check.d.ts +35 -0
- package/dist/core/check.js +63 -0
- package/dist/core/define.d.ts +64 -0
- package/dist/core/define.js +7 -0
- package/dist/core/format.d.ts +24 -0
- package/dist/core/format.js +74 -0
- package/dist/core/guards.d.ts +2 -0
- package/dist/core/guards.js +2 -0
- package/dist/fixtures/apiInventory.d.ts +36 -0
- package/dist/fixtures/apiInventory.js +287 -0
- package/dist/fixtures/attackSummary.d.ts +13 -0
- package/dist/fixtures/attackSummary.js +329 -0
- package/dist/fixtures/dashboard.d.ts +119 -0
- package/dist/fixtures/dashboard.js +428 -0
- package/dist/fixtures/incident.d.ts +44 -0
- package/dist/fixtures/incident.js +331 -0
- package/dist/fixtures/index.d.ts +13 -0
- package/dist/fixtures/index.js +9 -0
- package/dist/fixtures/liveMonitor.d.ts +9 -0
- package/dist/fixtures/liveMonitor.js +180 -0
- package/dist/fixtures/openui.d.ts +16 -0
- package/dist/fixtures/openui.js +74 -0
- package/dist/fixtures/scenarios.d.ts +13 -0
- package/dist/fixtures/scenarios.js +39 -0
- package/dist/fixtures/stream.d.ts +79 -0
- package/dist/fixtures/stream.js +147 -0
- package/dist/fixtures/weeklyReport.d.ts +46 -0
- package/dist/fixtures/weeklyReport.js +266 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/json-render/JsonRenderSurface.d.ts +13 -0
- package/dist/json-render/JsonRenderSurface.js +63 -0
- package/dist/json-render/catalog.d.ts +64 -0
- package/dist/json-render/catalog.js +50 -0
- package/dist/json-render/check.d.ts +4 -0
- package/dist/json-render/check.js +13 -0
- package/dist/json-render/index.d.ts +6 -0
- package/dist/json-render/index.js +4 -0
- package/dist/json-render/spec.d.ts +23 -0
- package/dist/json-render/spec.js +77 -0
- package/dist/openui/OpenUiSurface.d.ts +24 -0
- package/dist/openui/OpenUiSurface.js +30 -0
- package/dist/openui/catalog.d.ts +23 -0
- package/dist/openui/catalog.js +65 -0
- package/dist/openui/check.d.ts +4 -0
- package/dist/openui/check.js +68 -0
- package/dist/openui/index.d.ts +5 -0
- package/dist/openui/index.js +3 -0
- package/dist/playground/GenUiChatDemo.d.ts +11 -0
- package/dist/playground/GenUiChatDemo.js +148 -0
- package/dist/playground/GenUiPlayground.d.ts +15 -0
- package/dist/playground/GenUiPlayground.js +413 -0
- package/dist/playground/index.d.ts +3 -0
- package/dist/playground/index.js +2 -0
- package/dist/playground/useEmulatedAgent.d.ts +37 -0
- package/dist/playground/useEmulatedAgent.js +221 -0
- package/dist/render/ChartSkeleton.d.ts +6 -0
- package/dist/render/ChartSkeleton.js +26 -0
- package/dist/render/GuardedView.d.ts +18 -0
- package/dist/render/GuardedView.js +24 -0
- package/dist/render/Placeholder.d.ts +16 -0
- package/dist/render/Placeholder.js +15 -0
- package/dist/render/SurfaceBoundary.d.ts +15 -0
- package/dist/render/SurfaceBoundary.js +15 -0
- package/dist/render/warn.d.ts +1 -0
- package/dist/render/warn.js +2 -0
- package/package.json +94 -4
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type ComponentRegistry } from '@json-render/react';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import type { GenUiAction, GenUiComponent } from '../core/define.js';
|
|
4
|
+
/**
|
|
5
|
+
* The json-render catalog (what the model is prompted with: `catalog.prompt()`) and the registry
|
|
6
|
+
* that renders it. The zod props pass through untouched: `$state`/`$bindState` and the other
|
|
7
|
+
* expressions are resolved by the json-render runtime before a View sees them.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createJsonRenderCatalog: (components: readonly GenUiComponent[], actions: readonly GenUiAction[]) => {
|
|
10
|
+
catalog: import("@json-render/core").Catalog<{
|
|
11
|
+
spec: import("@json-render/core").SchemaType<"object", {
|
|
12
|
+
root: import("@json-render/core").SchemaType<"string", unknown>;
|
|
13
|
+
elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
|
|
14
|
+
type: import("@json-render/core").SchemaType<"ref", string>;
|
|
15
|
+
props: import("@json-render/core").SchemaType<"propsOf", string>;
|
|
16
|
+
children: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
|
|
17
|
+
slots: {
|
|
18
|
+
optional: true;
|
|
19
|
+
kind: "record";
|
|
20
|
+
inner?: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>> | undefined;
|
|
21
|
+
};
|
|
22
|
+
visible: {
|
|
23
|
+
optional: true;
|
|
24
|
+
kind: "any";
|
|
25
|
+
inner?: unknown;
|
|
26
|
+
};
|
|
27
|
+
repeat: {
|
|
28
|
+
optional: true;
|
|
29
|
+
kind: "any";
|
|
30
|
+
inner?: unknown;
|
|
31
|
+
};
|
|
32
|
+
}>>;
|
|
33
|
+
}>;
|
|
34
|
+
catalog: import("@json-render/core").SchemaType<"object", {
|
|
35
|
+
components: import("@json-render/core").SchemaType<"map", {
|
|
36
|
+
props: import("@json-render/core").SchemaType<"zod", unknown>;
|
|
37
|
+
slots: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
|
|
38
|
+
description: import("@json-render/core").SchemaType<"string", unknown>;
|
|
39
|
+
example: import("@json-render/core").SchemaType<"any", unknown>;
|
|
40
|
+
}>;
|
|
41
|
+
actions: import("@json-render/core").SchemaType<"map", {
|
|
42
|
+
params: import("@json-render/core").SchemaType<"zod", unknown>;
|
|
43
|
+
description: import("@json-render/core").SchemaType<"string", unknown>;
|
|
44
|
+
}>;
|
|
45
|
+
}>;
|
|
46
|
+
}, {
|
|
47
|
+
components: {
|
|
48
|
+
[k: string]: {
|
|
49
|
+
props: z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>;
|
|
50
|
+
description: string;
|
|
51
|
+
slots?: string[] | undefined;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
actions: {
|
|
55
|
+
[k: string]: {
|
|
56
|
+
description: string;
|
|
57
|
+
params: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
}>;
|
|
61
|
+
registry: ComponentRegistry;
|
|
62
|
+
actions: readonly GenUiAction[];
|
|
63
|
+
};
|
|
64
|
+
export type JsonRenderCatalog = ReturnType<typeof createJsonRenderCatalog>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { defineCatalog } from "@json-render/core";
|
|
3
|
+
import { defineRegistry } from "@json-render/react";
|
|
4
|
+
import { schema } from "@json-render/react/schema";
|
|
5
|
+
import { GuardedView } from "../render/GuardedView.js";
|
|
6
|
+
import * as __rspack_external_zod from "zod";
|
|
7
|
+
const createJsonRenderCatalog = (components, actions)=>{
|
|
8
|
+
const catalog = defineCatalog(schema, {
|
|
9
|
+
components: Object.fromEntries(components.map((c)=>[
|
|
10
|
+
c.name,
|
|
11
|
+
{
|
|
12
|
+
props: c.props,
|
|
13
|
+
description: c.description,
|
|
14
|
+
...c.hasChildren ? {
|
|
15
|
+
slots: [
|
|
16
|
+
'default'
|
|
17
|
+
]
|
|
18
|
+
} : {}
|
|
19
|
+
}
|
|
20
|
+
])),
|
|
21
|
+
actions: Object.fromEntries(actions.map((a)=>[
|
|
22
|
+
a.name,
|
|
23
|
+
{
|
|
24
|
+
description: a.description,
|
|
25
|
+
params: __rspack_external_zod.record(__rspack_external_zod.string(), __rspack_external_zod.unknown())
|
|
26
|
+
}
|
|
27
|
+
]))
|
|
28
|
+
});
|
|
29
|
+
const { registry } = defineRegistry(catalog, {
|
|
30
|
+
components: Object.fromEntries(components.map((component)=>[
|
|
31
|
+
component.name,
|
|
32
|
+
({ props, children, emit })=>/*#__PURE__*/ jsx(GuardedView, {
|
|
33
|
+
component: component,
|
|
34
|
+
props: props,
|
|
35
|
+
emit: emit,
|
|
36
|
+
children: children
|
|
37
|
+
})
|
|
38
|
+
])),
|
|
39
|
+
actions: Object.fromEntries(actions.map((a)=>[
|
|
40
|
+
a.name,
|
|
41
|
+
async ()=>void 0
|
|
42
|
+
]))
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
catalog,
|
|
46
|
+
registry: registry,
|
|
47
|
+
actions
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export { createJsonRenderCatalog };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Spec } from '@json-render/core';
|
|
2
|
+
import { type CheckOptions, type GenUiIssue } from '../core/check.js';
|
|
3
|
+
/** The agent's mistakes in a finished json-render spec, per element. */
|
|
4
|
+
export declare const checkJsonRenderAnswer: (spec: Spec, options: CheckOptions) => GenUiIssue[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { checkNodes } from "../core/check.js";
|
|
2
|
+
import { isRecord } from "../core/guards.js";
|
|
3
|
+
const isBinding = (value)=>isRecord(value) && Object.keys(value).some((key)=>key.startsWith('$'));
|
|
4
|
+
const checkJsonRenderAnswer = (spec, options)=>checkNodes(Object.entries(spec.elements).map(([id, element])=>({
|
|
5
|
+
id,
|
|
6
|
+
type: element.type,
|
|
7
|
+
props: element.props ?? {},
|
|
8
|
+
children: element.children ?? [],
|
|
9
|
+
actions: Object.values(element.on ?? {}).flatMap((binding)=>(Array.isArray(binding) ? binding : [
|
|
10
|
+
binding
|
|
11
|
+
]).map((b)=>b.action))
|
|
12
|
+
})), spec.root, isBinding, options);
|
|
13
|
+
export { checkJsonRenderAnswer };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** `@wallarm-org/gen-ui/json-render`: render the catalog from json-render specs. */
|
|
2
|
+
export type { Spec, SpecStreamLine } from '@json-render/core';
|
|
3
|
+
export { createJsonRenderCatalog, type JsonRenderCatalog } from './catalog.js';
|
|
4
|
+
export { checkJsonRenderAnswer } from './check.js';
|
|
5
|
+
export { JsonRenderSurface, type JsonRenderSurfaceProps } from './JsonRenderSurface.js';
|
|
6
|
+
export { sanitizeSpec, specFromLines } from './spec.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The json-render spec as it streams in: folding SpecStream lines into a spec, and keeping an
|
|
3
|
+
* agent's spec inside the catalog's contract. No React here.
|
|
4
|
+
*/
|
|
5
|
+
import { type Spec, type SpecStreamLine } from '@json-render/core';
|
|
6
|
+
import type { GenUiAction } from '../core/define.js';
|
|
7
|
+
/** Not a catalog type: the model never sees it, so only `specFromLines` can put it in a spec. */
|
|
8
|
+
export declare const PENDING = "gen-ui:pending";
|
|
9
|
+
/**
|
|
10
|
+
* Folds SpecStream patch lines (in arrival order) into the spec so far. It starts from an empty
|
|
11
|
+
* spec (mid-stream the patches may not have added `root` or `elements` yet, and the Renderer needs
|
|
12
|
+
* both), puts a pending element wherever an element names a child that hasn't arrived yet, and
|
|
13
|
+
* never throws: a patch it can't apply is dropped, and a value that isn't an element is pending.
|
|
14
|
+
*/
|
|
15
|
+
export declare const specFromLines: (lines: readonly SpecStreamLine[]) => Spec;
|
|
16
|
+
/**
|
|
17
|
+
* Keeps an agent's spec inside the catalog's contract. json-render runs its own built-in actions
|
|
18
|
+
* (setState, pushState, removeState, push, pop, validateForm) without a handler, fires `watch`
|
|
19
|
+
* bindings without a click and opens a `confirm` dialog of its own, so every `on` and `watch`
|
|
20
|
+
* binding keeps only `{ action, params }` for an action in the allowlist; the rest is dropped
|
|
21
|
+
* with a warning. Approval stays with the app.
|
|
22
|
+
*/
|
|
23
|
+
export declare const sanitizeSpec: (spec: Spec | null, actions: readonly GenUiAction[]) => Spec | null;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { compileSpecStream } from "@json-render/core";
|
|
2
|
+
import { isRecord } from "../core/guards.js";
|
|
3
|
+
import { warn } from "../render/warn.js";
|
|
4
|
+
const PENDING = 'gen-ui:pending';
|
|
5
|
+
const pending = (id)=>({
|
|
6
|
+
type: PENDING,
|
|
7
|
+
props: {
|
|
8
|
+
id
|
|
9
|
+
},
|
|
10
|
+
children: []
|
|
11
|
+
});
|
|
12
|
+
const specFromLines = (lines)=>{
|
|
13
|
+
let compiled;
|
|
14
|
+
try {
|
|
15
|
+
compiled = compileSpecStream(lines.map((line)=>JSON.stringify(line)).join('\n'), {
|
|
16
|
+
root: '',
|
|
17
|
+
elements: {}
|
|
18
|
+
});
|
|
19
|
+
} catch {
|
|
20
|
+
return lines.length ? specFromLines(lines.slice(0, -1)) : {
|
|
21
|
+
root: '',
|
|
22
|
+
elements: {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const raw = isRecord(compiled) ? compiled : {};
|
|
26
|
+
const source = isRecord(raw.elements) ? raw.elements : {};
|
|
27
|
+
const elements = {};
|
|
28
|
+
for (const [key, value] of Object.entries(source))elements[key] = isRecord(value) && 'string' == typeof value.type ? {
|
|
29
|
+
...value,
|
|
30
|
+
type: value.type,
|
|
31
|
+
props: isRecord(value.props) ? value.props : {},
|
|
32
|
+
children: Array.isArray(value.children) ? value.children.filter((child)=>'string' == typeof child) : []
|
|
33
|
+
} : pending(key);
|
|
34
|
+
for (const element of Object.values(elements))for (const key of element.children ?? [])elements[key] ??= pending(key);
|
|
35
|
+
return {
|
|
36
|
+
root: 'string' == typeof raw.root ? raw.root : '',
|
|
37
|
+
elements,
|
|
38
|
+
...isRecord(raw.state) ? {
|
|
39
|
+
state: raw.state
|
|
40
|
+
} : {}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const sanitizeSpec = (spec, actions)=>{
|
|
44
|
+
if (!spec) return spec;
|
|
45
|
+
const allowed = new Set(actions.map((a)=>a.name));
|
|
46
|
+
const keep = (bindings)=>(Array.isArray(bindings) ? bindings : [
|
|
47
|
+
bindings
|
|
48
|
+
]).flatMap((binding)=>{
|
|
49
|
+
const action = isRecord(binding) ? binding.action : void 0;
|
|
50
|
+
if ('string' == typeof action && allowed.has(action)) return [
|
|
51
|
+
{
|
|
52
|
+
action,
|
|
53
|
+
...isRecord(binding.params) ? {
|
|
54
|
+
params: binding.params
|
|
55
|
+
} : {}
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
warn(`dropped json-render action '${String(action)}', not in the allowlist`);
|
|
59
|
+
return [];
|
|
60
|
+
});
|
|
61
|
+
const filter = (map)=>isRecord(map) ? Object.fromEntries(Object.entries(map).map(([event, bindings])=>[
|
|
62
|
+
event,
|
|
63
|
+
keep(bindings)
|
|
64
|
+
]).filter(([, kept])=>kept.length > 0)) : void 0;
|
|
65
|
+
return {
|
|
66
|
+
...spec,
|
|
67
|
+
elements: Object.fromEntries(Object.entries(spec.elements).map(([key, element])=>[
|
|
68
|
+
key,
|
|
69
|
+
{
|
|
70
|
+
...element,
|
|
71
|
+
on: filter(element.on),
|
|
72
|
+
watch: filter(element.watch)
|
|
73
|
+
}
|
|
74
|
+
]))
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export { PENDING, sanitizeSpec, specFromLines };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import { type OpenUIError } from '@openuidev/react-lang';
|
|
3
|
+
import type { GenUiActionHandler } from '../core/define.js';
|
|
4
|
+
import type { OpenUiLibrary } from './catalog.js';
|
|
5
|
+
export interface OpenUiSurfaceProps {
|
|
6
|
+
library: OpenUiLibrary;
|
|
7
|
+
/** The OpenUI Lang program so far; it grows while the model streams. */
|
|
8
|
+
program: string;
|
|
9
|
+
/**
|
|
10
|
+
* The app's data. Each top-level key is a `$variable` the program reads (`kpi` → `$kpi.attacks`);
|
|
11
|
+
* pass a new object when it changes and the page follows, with no call to the agent.
|
|
12
|
+
*/
|
|
13
|
+
data?: Record<string, unknown>;
|
|
14
|
+
streaming?: boolean;
|
|
15
|
+
onAction: GenUiActionHandler;
|
|
16
|
+
/** What OpenUI couldn't parse or render, once the stream is done. Default: console.warn. */
|
|
17
|
+
onError?: (errors: readonly OpenUIError[]) => void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Renders one OpenUI Lang program through the catalog. Only the app's actions reach `onAction`:
|
|
21
|
+
* OpenUI's own steps that leave the page (`@ToAssistant`, `@OpenUrl`) are dropped with a warning,
|
|
22
|
+
* and with no tool provider `Query` and `Mutation` reach nothing.
|
|
23
|
+
*/
|
|
24
|
+
export declare const OpenUiSurface: FC<OpenUiSurfaceProps>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Renderer } from "@openuidev/react-lang";
|
|
3
|
+
import { isRecord } from "../core/guards.js";
|
|
4
|
+
import { SurfaceBoundary } from "../render/SurfaceBoundary.js";
|
|
5
|
+
import { warn } from "../render/warn.js";
|
|
6
|
+
const OpenUiSurface = ({ library, program, data = {}, streaming = false, onAction, onError = warnErrors })=>{
|
|
7
|
+
const handleAction = ({ type, params })=>{
|
|
8
|
+
if (!library.actions.some((a)=>a.name === type)) return void warn(`action "${type}" is not in the allowlist, dropped`);
|
|
9
|
+
onAction(type, isRecord(params.context) ? params.context : {});
|
|
10
|
+
};
|
|
11
|
+
const handleErrors = (errors)=>{
|
|
12
|
+
if (!streaming && errors.length) onError(errors);
|
|
13
|
+
};
|
|
14
|
+
return /*#__PURE__*/ jsx(SurfaceBoundary, {
|
|
15
|
+
children: /*#__PURE__*/ jsx(Renderer, {
|
|
16
|
+
response: program,
|
|
17
|
+
library: library.library,
|
|
18
|
+
isStreaming: streaming,
|
|
19
|
+
initialState: Object.fromEntries(Object.entries(data).map(([k, v])=>[
|
|
20
|
+
`$${k}`,
|
|
21
|
+
v
|
|
22
|
+
])),
|
|
23
|
+
onAction: handleAction,
|
|
24
|
+
onError: handleErrors,
|
|
25
|
+
publishObservability: false
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const warnErrors = (errors)=>warn('OpenUI rejected the answer', errors);
|
|
30
|
+
export { OpenUiSurface };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import type { GenUiAction, GenUiComponent } from '../core/define.js';
|
|
3
|
+
/**
|
|
4
|
+
* The OpenUI library for the catalog (what the model is prompted with: `library.prompt()`). Each
|
|
5
|
+
* entry's props pass through in their order, which is the positional order in OpenUI Lang; an
|
|
6
|
+
* entry's events become action props after them, and a container takes `children` last. The
|
|
7
|
+
* app's actions are library actions, written `@block_ips({ips: …})`: the map is the action's
|
|
8
|
+
* context, as in A2UI.
|
|
9
|
+
*/
|
|
10
|
+
export declare const createOpenUiLibrary: (components: readonly GenUiComponent[], actions: readonly GenUiAction[]) => {
|
|
11
|
+
library: Required<import("@openuidev/react-lang").Library<import("@openuidev/react-lang").DefinedAction<z.ZodObject<{
|
|
12
|
+
context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
13
|
+
}, z.core.$strip>, string>>>;
|
|
14
|
+
actions: readonly GenUiAction[];
|
|
15
|
+
};
|
|
16
|
+
export type OpenUiLibrary = ReturnType<typeof createOpenUiLibrary>;
|
|
17
|
+
/**
|
|
18
|
+
* An element's data props: no children, no action props, and no `null`, which is how OpenUI Lang
|
|
19
|
+
* skips a positional argument.
|
|
20
|
+
*/
|
|
21
|
+
export declare const dataProps: (component: GenUiComponent, props: Record<string, unknown>) => {
|
|
22
|
+
[k: string]: unknown;
|
|
23
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from "react";
|
|
3
|
+
import { action, createLibrary, defineAction, defineComponent, useTriggerAction } from "@openuidev/react-lang";
|
|
4
|
+
import { isRecord } from "../core/guards.js";
|
|
5
|
+
import { GuardedView } from "../render/GuardedView.js";
|
|
6
|
+
import * as __rspack_external_zod from "zod";
|
|
7
|
+
const createOpenUiLibrary = (components, actions)=>{
|
|
8
|
+
const defined = [];
|
|
9
|
+
const child = __rspack_external_zod.lazy(()=>__rspack_external_zod.union(defined.map((d)=>d.ref)));
|
|
10
|
+
for (const component of components){
|
|
11
|
+
const { name, description, props, events = [], hasChildren } = component;
|
|
12
|
+
const schema = props.extend(Object.fromEntries(events.map((event)=>[
|
|
13
|
+
event,
|
|
14
|
+
action().optional()
|
|
15
|
+
]))).extend(hasChildren ? {
|
|
16
|
+
children: __rspack_external_zod.array(child).optional()
|
|
17
|
+
} : {});
|
|
18
|
+
defined.push(defineComponent({
|
|
19
|
+
name,
|
|
20
|
+
description,
|
|
21
|
+
props: schema,
|
|
22
|
+
component: renderer(component)
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
const library = createLibrary({
|
|
26
|
+
components: defined,
|
|
27
|
+
actions: actions.map((a)=>defineAction({
|
|
28
|
+
name: a.name,
|
|
29
|
+
description: a.description,
|
|
30
|
+
params: __rspack_external_zod.object({
|
|
31
|
+
context: __rspack_external_zod.record(__rspack_external_zod.string(), __rspack_external_zod.unknown()).optional()
|
|
32
|
+
})
|
|
33
|
+
}))
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
library,
|
|
37
|
+
actions
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const dataProps = (component, props)=>{
|
|
41
|
+
const { events = [] } = component;
|
|
42
|
+
return Object.fromEntries(Object.entries(props).filter(([key, value])=>null !== value && 'children' !== key && !events.includes(key)));
|
|
43
|
+
};
|
|
44
|
+
const renderer = (component)=>{
|
|
45
|
+
const Render = ({ props, renderNode })=>{
|
|
46
|
+
const triggerAction = useTriggerAction();
|
|
47
|
+
const data = dataProps(component, props);
|
|
48
|
+
const emit = (event)=>{
|
|
49
|
+
const plan = props[event];
|
|
50
|
+
if (isRecord(plan)) triggerAction('', void 0, plan);
|
|
51
|
+
};
|
|
52
|
+
const children = Array.isArray(props.children) ? props.children : [];
|
|
53
|
+
return /*#__PURE__*/ jsx(GuardedView, {
|
|
54
|
+
component: component,
|
|
55
|
+
props: data,
|
|
56
|
+
emit: emit,
|
|
57
|
+
children: component.hasChildren && children.map((node, i)=>/*#__PURE__*/ jsx(Fragment, {
|
|
58
|
+
children: renderNode(node)
|
|
59
|
+
}, isRecord(node) && 'string' == typeof node.statementId ? node.statementId : i))
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
Render.displayName = `OpenUi${component.name}`;
|
|
63
|
+
return Render;
|
|
64
|
+
};
|
|
65
|
+
export { createOpenUiLibrary, dataProps };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type CheckOptions, type GenUiIssue } from '../core/check.js';
|
|
2
|
+
import { type OpenUiLibrary } from './catalog.js';
|
|
3
|
+
/** The agent's mistakes in a finished OpenUI Lang program, per statement. */
|
|
4
|
+
export declare const checkOpenUiAnswer: (program: string, library: OpenUiLibrary, options: CheckOptions) => GenUiIssue[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createParser } from "@openuidev/react-lang";
|
|
2
|
+
import { checkNodes } from "../core/check.js";
|
|
3
|
+
import { isRecord } from "../core/guards.js";
|
|
4
|
+
import { dataProps } from "./catalog.js";
|
|
5
|
+
const isBinding = (value)=>isRecord(value) && 'string' == typeof value.k;
|
|
6
|
+
const isElement = (value)=>isRecord(value) && 'element' === value.type && 'string' == typeof value.typeName;
|
|
7
|
+
const checkOpenUiAnswer = (program, library, options)=>{
|
|
8
|
+
const { library: lib } = library;
|
|
9
|
+
const result = createParser(lib.toJSONSchema(), lib.root).parse(program);
|
|
10
|
+
const nodes = [];
|
|
11
|
+
const visit = (element, fallbackId)=>{
|
|
12
|
+
const id = element.statementId ?? fallbackId;
|
|
13
|
+
const component = options.components.find((c)=>c.name === element.typeName);
|
|
14
|
+
const children = Array.isArray(element.props.children) ? element.props.children.filter(isElement) : [];
|
|
15
|
+
nodes.push({
|
|
16
|
+
id,
|
|
17
|
+
type: element.typeName,
|
|
18
|
+
props: component ? dataProps(component, element.props) : element.props,
|
|
19
|
+
children: children.map((child, i)=>child.statementId ?? `${id}_children_${i}`),
|
|
20
|
+
actions: (component?.events ?? []).flatMap((event)=>actionNames(element.props[event]))
|
|
21
|
+
});
|
|
22
|
+
children.forEach((child, i)=>visit(child, `${id}_children_${i}`));
|
|
23
|
+
};
|
|
24
|
+
if (isElement(result.root)) visit(result.root, 'root');
|
|
25
|
+
const reported = [
|
|
26
|
+
...result.meta.unresolved.map((name)=>({
|
|
27
|
+
id: referrer(program, name) ?? name,
|
|
28
|
+
message: `Child "${name}" was never sent.`
|
|
29
|
+
})),
|
|
30
|
+
...result.meta.errors.map(({ code, statementId, component, path, message })=>{
|
|
31
|
+
const at = {
|
|
32
|
+
id: statementId ?? 'root',
|
|
33
|
+
component
|
|
34
|
+
};
|
|
35
|
+
if ('unknown-component' === code) return {
|
|
36
|
+
...at,
|
|
37
|
+
message: `Unknown component "${component}".`
|
|
38
|
+
};
|
|
39
|
+
if ('unknown-function' === code) return {
|
|
40
|
+
...at,
|
|
41
|
+
message: `Action "${component}" is not allowed.`
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
...at,
|
|
45
|
+
prop: path?.split('/').filter(Boolean).join('.') || void 0,
|
|
46
|
+
message
|
|
47
|
+
};
|
|
48
|
+
})
|
|
49
|
+
];
|
|
50
|
+
const issues = [
|
|
51
|
+
...checkNodes(nodes, result.root?.statementId, isBinding, options),
|
|
52
|
+
...reported
|
|
53
|
+
];
|
|
54
|
+
return issues.filter((issue, i)=>!issue.prop || issues.findIndex((other)=>other.id === issue.id && other.prop === issue.prop) === i);
|
|
55
|
+
};
|
|
56
|
+
const actionNames = (value)=>{
|
|
57
|
+
if (Array.isArray(value)) return value.flatMap(actionNames);
|
|
58
|
+
if (!isRecord(value)) return [];
|
|
59
|
+
const own = 'Comp' === value.k && 'string' == typeof value.name && 'Action' !== value.name;
|
|
60
|
+
return [
|
|
61
|
+
...own ? [
|
|
62
|
+
String(value.name)
|
|
63
|
+
] : [],
|
|
64
|
+
...Object.values(value).flatMap(actionNames)
|
|
65
|
+
];
|
|
66
|
+
};
|
|
67
|
+
const referrer = (program, ref)=>program.split('\n').map((line)=>line.match(/^\s*(\w+)\s*=(.*)$/)).find((match)=>match?.[2] && new RegExp(`\\b${ref}\\b`).test(match[2]))?.[1];
|
|
68
|
+
export { checkOpenUiAnswer };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** `@wallarm-org/gen-ui/openui`: render the catalog from OpenUI Lang programs. */
|
|
2
|
+
export type { OpenUIError } from '@openuidev/react-lang';
|
|
3
|
+
export { createOpenUiLibrary, type OpenUiLibrary } from './catalog.js';
|
|
4
|
+
export { checkOpenUiAnswer } from './check.js';
|
|
5
|
+
export { OpenUiSurface, type OpenUiSurfaceProps } from './OpenUiSurface.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
export interface GenUiChatDemoProps {
|
|
3
|
+
/** Sizes the demo; it fills its box (give the host a height). */
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Generative UI inside the Wally chat: an emulated agent answers with a surface (in the format
|
|
8
|
+
* picked for the next answer) through WallyAssistant's `renderPart`. Its Block button asks for
|
|
9
|
+
* approval before the agent acts.
|
|
10
|
+
*/
|
|
11
|
+
export declare const GenUiChatDemo: FC<GenUiChatDemoProps>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { SegmentedControl, SegmentedControlItem } from "@wallarm-org/design-system/SegmentedControl";
|
|
4
|
+
import { VStack } from "@wallarm-org/design-system/Stack";
|
|
5
|
+
import { Text } from "@wallarm-org/design-system/Text";
|
|
6
|
+
import { cn } from "@wallarm-org/design-system/utils/cn";
|
|
7
|
+
import { WallyAssistant } from "@wallarm-org/design-system/WallyAssistant";
|
|
8
|
+
import { A2uiSurfaces } from "../a2ui/A2uiSurfaces.js";
|
|
9
|
+
import { createA2uiCatalog } from "../a2ui/catalog.js";
|
|
10
|
+
import { ExpressSurfaces } from "../a2ui/express.js";
|
|
11
|
+
import { wallarmComponents } from "../components/index.js";
|
|
12
|
+
import { ATTACK_SUMMARY_ACTIONS } from "../fixtures/attackSummary.js";
|
|
13
|
+
import { createJsonRenderCatalog } from "../json-render/catalog.js";
|
|
14
|
+
import { JsonRenderSurface } from "../json-render/JsonRenderSurface.js";
|
|
15
|
+
import { specFromLines } from "../json-render/spec.js";
|
|
16
|
+
import { createOpenUiLibrary } from "../openui/catalog.js";
|
|
17
|
+
import { OpenUiSurface } from "../openui/OpenUiSurface.js";
|
|
18
|
+
import { isGenUiFormat, isGenUiPart, useEmulatedAgent } from "./useEmulatedAgent.js";
|
|
19
|
+
const A2UI_CATALOG = createA2uiCatalog(wallarmComponents);
|
|
20
|
+
const JSON_RENDER_CATALOG = createJsonRenderCatalog(wallarmComponents, ATTACK_SUMMARY_ACTIONS);
|
|
21
|
+
const OPENUI_LIBRARY = createOpenUiLibrary(wallarmComponents, ATTACK_SUMMARY_ACTIONS);
|
|
22
|
+
const SUGGESTIONS = [
|
|
23
|
+
{
|
|
24
|
+
id: 'summary',
|
|
25
|
+
label: 'Show attacks for the last 24 hours'
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
const GenUiSurface = ({ part, streaming, onAction })=>{
|
|
29
|
+
const { data } = part;
|
|
30
|
+
switch(data.format){
|
|
31
|
+
case 'a2ui':
|
|
32
|
+
return /*#__PURE__*/ jsx(A2uiSurfaces, {
|
|
33
|
+
catalog: A2UI_CATALOG,
|
|
34
|
+
messages: data.lines,
|
|
35
|
+
actions: ATTACK_SUMMARY_ACTIONS,
|
|
36
|
+
onAction: onAction
|
|
37
|
+
});
|
|
38
|
+
case 'express':
|
|
39
|
+
return /*#__PURE__*/ jsx(ExpressSurfaces, {
|
|
40
|
+
catalog: A2UI_CATALOG,
|
|
41
|
+
surfaceId: data.surfaceId,
|
|
42
|
+
chunks: data.lines,
|
|
43
|
+
actions: ATTACK_SUMMARY_ACTIONS,
|
|
44
|
+
onAction: onAction
|
|
45
|
+
});
|
|
46
|
+
case 'json-render':
|
|
47
|
+
return /*#__PURE__*/ jsx(JsonRenderSurface, {
|
|
48
|
+
catalog: JSON_RENDER_CATALOG,
|
|
49
|
+
spec: specFromLines(data.lines),
|
|
50
|
+
streaming: streaming,
|
|
51
|
+
onAction: onAction
|
|
52
|
+
});
|
|
53
|
+
default:
|
|
54
|
+
return /*#__PURE__*/ jsx(OpenUiSurface, {
|
|
55
|
+
library: OPENUI_LIBRARY,
|
|
56
|
+
program: data.lines.join('\n'),
|
|
57
|
+
streaming: streaming,
|
|
58
|
+
onAction: onAction
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const GenUiChatDemo = ({ className })=>{
|
|
63
|
+
const [format, setFormat] = useState('a2ui');
|
|
64
|
+
const [log, setLog] = useState([]);
|
|
65
|
+
const agent = useEmulatedAgent(format);
|
|
66
|
+
const handleAction = (name, params)=>{
|
|
67
|
+
setLog((entries)=>[
|
|
68
|
+
{
|
|
69
|
+
seq: entries.length,
|
|
70
|
+
text: `${name} ${JSON.stringify(params)}`
|
|
71
|
+
},
|
|
72
|
+
...entries
|
|
73
|
+
]);
|
|
74
|
+
agent.act(name, params);
|
|
75
|
+
};
|
|
76
|
+
const renderPart = (part, ctx)=>isGenUiPart(part) ? /*#__PURE__*/ jsx(GenUiSurface, {
|
|
77
|
+
part: part,
|
|
78
|
+
streaming: ctx.streaming,
|
|
79
|
+
onAction: handleAction
|
|
80
|
+
}) : void 0;
|
|
81
|
+
const handleFormatChange = (value)=>{
|
|
82
|
+
if (isGenUiFormat(value)) setFormat(value);
|
|
83
|
+
};
|
|
84
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
85
|
+
className: cn('flex min-h-0 gap-24 bg-bg-surface-1 p-16', className),
|
|
86
|
+
children: [
|
|
87
|
+
/*#__PURE__*/ jsx("div", {
|
|
88
|
+
className: "h-full min-w-0 flex-1",
|
|
89
|
+
children: /*#__PURE__*/ jsx(WallyAssistant, {
|
|
90
|
+
messages: agent.messages,
|
|
91
|
+
status: agent.status,
|
|
92
|
+
suggestions: agent.messages.length ? void 0 : SUGGESTIONS,
|
|
93
|
+
onSubmit: agent.send,
|
|
94
|
+
onStop: agent.stop,
|
|
95
|
+
onApprovalRespond: agent.respond,
|
|
96
|
+
approvalPlacement: "inline",
|
|
97
|
+
renderPart: renderPart
|
|
98
|
+
})
|
|
99
|
+
}),
|
|
100
|
+
/*#__PURE__*/ jsxs(VStack, {
|
|
101
|
+
gap: 12,
|
|
102
|
+
align: "stretch",
|
|
103
|
+
className: "w-320 shrink-0",
|
|
104
|
+
children: [
|
|
105
|
+
/*#__PURE__*/ jsx(Text, {
|
|
106
|
+
weight: "medium",
|
|
107
|
+
children: "Format of the next answer"
|
|
108
|
+
}),
|
|
109
|
+
/*#__PURE__*/ jsxs(SegmentedControl, {
|
|
110
|
+
value: format,
|
|
111
|
+
onChange: handleFormatChange,
|
|
112
|
+
children: [
|
|
113
|
+
/*#__PURE__*/ jsx(SegmentedControlItem, {
|
|
114
|
+
value: "a2ui",
|
|
115
|
+
children: "A2UI"
|
|
116
|
+
}),
|
|
117
|
+
/*#__PURE__*/ jsx(SegmentedControlItem, {
|
|
118
|
+
value: "express",
|
|
119
|
+
children: "Express"
|
|
120
|
+
}),
|
|
121
|
+
/*#__PURE__*/ jsx(SegmentedControlItem, {
|
|
122
|
+
value: "json-render",
|
|
123
|
+
children: "json-render"
|
|
124
|
+
}),
|
|
125
|
+
/*#__PURE__*/ jsx(SegmentedControlItem, {
|
|
126
|
+
value: "openui",
|
|
127
|
+
children: "OpenUI"
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
}),
|
|
131
|
+
/*#__PURE__*/ jsx(Text, {
|
|
132
|
+
weight: "medium",
|
|
133
|
+
children: "Actions sent to the agent"
|
|
134
|
+
}),
|
|
135
|
+
0 === log.length ? /*#__PURE__*/ jsx(Text, {
|
|
136
|
+
size: "sm",
|
|
137
|
+
color: "secondary",
|
|
138
|
+
children: "Ask for the attacks, then press a button in the answer."
|
|
139
|
+
}) : log.slice(0, 6).map((entry)=>/*#__PURE__*/ jsx("pre", {
|
|
140
|
+
className: "m-0 whitespace-pre-wrap break-all font-mono text-xs text-text-primary",
|
|
141
|
+
children: entry.text
|
|
142
|
+
}, entry.seq))
|
|
143
|
+
]
|
|
144
|
+
})
|
|
145
|
+
]
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
export { GenUiChatDemo };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import type { ReplayScenario } from '../fixtures/scenarios.js';
|
|
3
|
+
export interface GenUiPlaygroundProps {
|
|
4
|
+
/** The prompts to pick from, in order; the first one plays on mount. */
|
|
5
|
+
scenarios: readonly ReplayScenario[];
|
|
6
|
+
/** Sizes the playground; it fills its box (give the host a height). */
|
|
7
|
+
className?: string;
|
|
8
|
+
/** Show the stream panel at first; a narrow host starts with it closed. Default true. */
|
|
9
|
+
defaultStreamOpen?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Replays recorded agent answers: pick a prompt, watch the agent stream the layout and the app
|
|
13
|
+
* fill its data, in either format, with the stream itself beside the page.
|
|
14
|
+
*/
|
|
15
|
+
export declare const GenUiPlayground: FC<GenUiPlaygroundProps>;
|