@adecore/intelligent-ui 0.0.0 → 0.21.1
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 +29 -57
- package/dist/actions.d.ts +5 -0
- package/dist/actions.js +34 -0
- package/dist/catalog.d.ts +9 -1
- package/dist/catalog.js +12 -8
- package/dist/compiler.d.ts +26 -1
- package/dist/compiler.js +180 -74
- package/dist/expression.d.ts +1 -0
- package/dist/expression.js +16 -0
- package/dist/fallback.d.ts +2 -0
- package/dist/fallback.js +25 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -3
- package/dist/inputs.d.ts +5 -0
- package/dist/inputs.js +69 -0
- package/dist/links.d.ts +49 -0
- package/dist/links.js +42 -0
- package/dist/prop-list.d.ts +7 -0
- package/dist/prop-list.js +5 -0
- package/dist/protocol.js +2 -1
- package/dist/query.d.ts +7 -0
- package/dist/query.js +52 -0
- package/dist/runtime.d.ts +11 -1
- package/dist/runtime.js +139 -25
- package/dist/stream.d.ts +28 -0
- package/dist/stream.js +56 -0
- package/dist/text.d.ts +5 -2
- package/dist/text.js +24 -12
- package/package.json +91 -76
- package/src/actions.ts +39 -0
- package/src/catalog.ts +12 -8
- package/src/compiler.ts +203 -71
- package/src/expression.ts +17 -0
- package/src/fallback.ts +26 -0
- package/src/index.ts +32 -4
- package/src/inputs.ts +74 -0
- package/src/links.ts +54 -0
- package/src/prop-list.ts +7 -0
- package/src/protocol.ts +2 -1
- package/src/query.ts +67 -0
- package/src/runtime.ts +155 -25
- package/src/stream.ts +78 -0
- package/src/text.ts +31 -12
package/dist/stream.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { UiCompiler } from './compiler.js';
|
|
2
|
+
export const UI_STREAM_INTERVAL_MS = 250;
|
|
3
|
+
export class UiStream {
|
|
4
|
+
compiler;
|
|
5
|
+
options;
|
|
6
|
+
pending = null;
|
|
7
|
+
timer = null;
|
|
8
|
+
lastCompiledAt = -Infinity;
|
|
9
|
+
closed = false;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.compiler = new UiCompiler(options);
|
|
13
|
+
}
|
|
14
|
+
update(text, latestAttachment) {
|
|
15
|
+
if (this.closed) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.pending = { text, latestAttachment };
|
|
19
|
+
if (this.timer !== null) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const delay = Math.max(0, UI_STREAM_INTERVAL_MS - (this.options.clock.now() - this.lastCompiledAt));
|
|
23
|
+
if (delay === 0) {
|
|
24
|
+
this.flush();
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.timer = { handle: this.options.clock.setTimeout(() => this.flush(), delay) };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
finish(text, latestAttachment) {
|
|
31
|
+
this.dispose();
|
|
32
|
+
const blocks = this.compiler.compile(text, { final: true, latestAttachment });
|
|
33
|
+
this.compiler.clear();
|
|
34
|
+
return blocks;
|
|
35
|
+
}
|
|
36
|
+
dispose() {
|
|
37
|
+
this.closed = true;
|
|
38
|
+
if (this.timer !== null) {
|
|
39
|
+
this.options.clock.clearTimeout(this.timer.handle);
|
|
40
|
+
this.timer = null;
|
|
41
|
+
}
|
|
42
|
+
this.pending = null;
|
|
43
|
+
this.compiler.clear();
|
|
44
|
+
}
|
|
45
|
+
flush() {
|
|
46
|
+
this.timer = null;
|
|
47
|
+
const pending = this.pending;
|
|
48
|
+
if (pending === null || this.closed) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.pending = null;
|
|
52
|
+
this.lastCompiledAt = this.options.clock.now();
|
|
53
|
+
const blocks = this.compiler.compile(pending.text, { latestAttachment: pending.latestAttachment });
|
|
54
|
+
this.options.emit({ textLength: pending.text.length, blocks });
|
|
55
|
+
}
|
|
56
|
+
}
|
package/dist/text.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type UiBlock } from './compiler.ts';
|
|
2
2
|
export declare function uiCompactCatalog(): string;
|
|
3
|
-
export
|
|
4
|
-
|
|
3
|
+
export interface UiTextOptions {
|
|
4
|
+
fenceLanguage?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function uiSessionNote({ fenceLanguage }?: UiTextOptions): string;
|
|
7
|
+
export declare function uiReferenceText({ fenceLanguage }?: UiTextOptions): string;
|
|
5
8
|
export declare function uiFallbackText(text: string, blocks: readonly UiBlock[]): string;
|
package/dist/text.js
CHANGED
|
@@ -1,32 +1,44 @@
|
|
|
1
1
|
import { UI_CATALOG, UI_GROUPS } from './catalog.js';
|
|
2
|
+
import { uiPropList } from './prop-list.js';
|
|
2
3
|
import { UI_FENCE_LANGUAGE } from './compiler.js';
|
|
3
4
|
export function uiCompactCatalog() {
|
|
4
5
|
return Object.entries(UI_CATALOG)
|
|
5
6
|
.map(([name, entry]) => {
|
|
6
|
-
const props =
|
|
7
|
-
|
|
8
|
-
.join(',');
|
|
9
|
-
return `${name}(${props})`;
|
|
7
|
+
const props = uiPropList(entry).join(',');
|
|
8
|
+
return `${name}${props ? `: ${props}` : ''}`;
|
|
10
9
|
})
|
|
11
10
|
.join(' · ');
|
|
12
11
|
}
|
|
13
|
-
|
|
14
|
-
return `
|
|
12
|
+
function uiSyntax(language) {
|
|
13
|
+
return `Write XML-like component tags inside a fenced ${language} block. Put text between opening and closing tags; empty components end with />. Quote string props; put numbers, booleans and expressions in braces.`;
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
return
|
|
15
|
+
function uiExample(language) {
|
|
16
|
+
return [
|
|
17
|
+
'```' + language,
|
|
18
|
+
'$enabled = false',
|
|
19
|
+
'<Summary badge="Draft">Release overview</Summary>',
|
|
20
|
+
'<Stats><Stat label="Tests" value={42}/></Stats>',
|
|
21
|
+
'<Switch value={$enabled}>Notify me</Switch>',
|
|
22
|
+
'<Choices><Choice context="Review the release overview">Continue</Choice></Choices>',
|
|
23
|
+
'```'
|
|
24
|
+
].join('\n');
|
|
25
|
+
}
|
|
26
|
+
export function uiSessionNote({ fenceLanguage = UI_FENCE_LANGUAGE } = {}) {
|
|
27
|
+
return `${uiSyntax(fenceLanguage)} Prose outside. Only named catalog props, no styling. $name = literal JSON; input value={$name}; Button action={@Set($name, literal)}, {@Reset($name)} or {@Reset()} changes local state only; braced expressions: @Count, @Filter(list,row,predicate), @Sum, @Join, @Round. Show/Each control children. Choice labels and precise context stay visible and send once. Image: attachment or generated="latest", never URL/path. No code execution. State resets on reload. Catalog (? means optional props): ${uiCompactCatalog()}\nExample:\n${uiExample(fenceLanguage)}\n`;
|
|
28
|
+
}
|
|
29
|
+
export function uiReferenceText({ fenceLanguage = UI_FENCE_LANGUAGE } = {}) {
|
|
30
|
+
const groups = Object.entries(UI_GROUPS)
|
|
18
31
|
.map(([group, definition]) => {
|
|
19
32
|
const components = Object.entries(UI_CATALOG)
|
|
20
33
|
.filter(([, entry]) => entry.group === group)
|
|
21
34
|
.map(([name, entry]) => {
|
|
22
|
-
const props =
|
|
23
|
-
|
|
24
|
-
.join(', ');
|
|
25
|
-
return `${name}(${props}): ${entry.description}`;
|
|
35
|
+
const props = uiPropList(entry).join(', ');
|
|
36
|
+
return `${name}: ${entry.description}${props ? ` Props: ${props}.` : ''}`;
|
|
26
37
|
});
|
|
27
38
|
return [definition.description, definition.rules, ...components].join('\n');
|
|
28
39
|
})
|
|
29
40
|
.join('\n\n');
|
|
41
|
+
return `${uiSyntax(fenceLanguage)}\nExample:\n${uiExample(fenceLanguage)}\n\n${groups}`;
|
|
30
42
|
}
|
|
31
43
|
export function uiFallbackText(text, blocks) {
|
|
32
44
|
let result = '';
|
package/package.json
CHANGED
|
@@ -1,84 +1,99 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
"name": "@adecore/intelligent-ui",
|
|
3
|
+
"version": "0.21.1",
|
|
4
|
+
"description": "A streaming UI catalog, compiler and bounded expression interpreter without a framework or DOM.",
|
|
5
|
+
"license": "FSL-1.1-MIT",
|
|
6
|
+
"author": "Bas Milius",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/basmilius/adecore.git",
|
|
10
|
+
"directory": "packages/intelligent-ui"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/basmilius/adecore/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://adecore.dev/intelligent-ui/",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"!src/**/*.test.ts",
|
|
22
|
+
"!src/fixtures"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"source": "./src/index.ts",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
11
29
|
},
|
|
12
|
-
"
|
|
13
|
-
|
|
30
|
+
"./links": {
|
|
31
|
+
"source": "./src/links.ts",
|
|
32
|
+
"types": "./dist/links.d.ts",
|
|
33
|
+
"default": "./dist/links.js"
|
|
14
34
|
},
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"dist",
|
|
20
|
-
"src",
|
|
21
|
-
"!src/**/*.test.ts",
|
|
22
|
-
"!src/fixtures"
|
|
23
|
-
],
|
|
24
|
-
"exports": {
|
|
25
|
-
".": {
|
|
26
|
-
"source": "./src/index.ts",
|
|
27
|
-
"types": "./dist/index.d.ts",
|
|
28
|
-
"default": "./dist/index.js"
|
|
29
|
-
},
|
|
30
|
-
"./catalog": {
|
|
31
|
-
"source": "./src/catalog.ts",
|
|
32
|
-
"types": "./dist/catalog.d.ts",
|
|
33
|
-
"default": "./dist/catalog.js"
|
|
34
|
-
},
|
|
35
|
-
"./budget": {
|
|
36
|
-
"source": "./src/budget.ts",
|
|
37
|
-
"types": "./dist/budget.d.ts",
|
|
38
|
-
"default": "./dist/budget.js"
|
|
39
|
-
},
|
|
40
|
-
"./expression": {
|
|
41
|
-
"source": "./src/expression.ts",
|
|
42
|
-
"types": "./dist/expression.d.ts",
|
|
43
|
-
"default": "./dist/expression.js"
|
|
44
|
-
},
|
|
45
|
-
"./syntax": {
|
|
46
|
-
"source": "./src/syntax.ts",
|
|
47
|
-
"types": "./dist/syntax.d.ts",
|
|
48
|
-
"default": "./dist/syntax.js"
|
|
49
|
-
},
|
|
50
|
-
"./compiler": {
|
|
51
|
-
"source": "./src/compiler.ts",
|
|
52
|
-
"types": "./dist/compiler.d.ts",
|
|
53
|
-
"default": "./dist/compiler.js"
|
|
54
|
-
},
|
|
55
|
-
"./runtime": {
|
|
56
|
-
"source": "./src/runtime.ts",
|
|
57
|
-
"types": "./dist/runtime.d.ts",
|
|
58
|
-
"default": "./dist/runtime.js"
|
|
59
|
-
},
|
|
60
|
-
"./protocol": {
|
|
61
|
-
"source": "./src/protocol.ts",
|
|
62
|
-
"types": "./dist/protocol.d.ts",
|
|
63
|
-
"default": "./dist/protocol.js"
|
|
64
|
-
},
|
|
65
|
-
"./text": {
|
|
66
|
-
"source": "./src/text.ts",
|
|
67
|
-
"types": "./dist/text.d.ts",
|
|
68
|
-
"default": "./dist/text.js"
|
|
69
|
-
}
|
|
35
|
+
"./query": {
|
|
36
|
+
"source": "./src/query.ts",
|
|
37
|
+
"types": "./dist/query.d.ts",
|
|
38
|
+
"default": "./dist/query.js"
|
|
70
39
|
},
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
40
|
+
"./catalog": {
|
|
41
|
+
"source": "./src/catalog.ts",
|
|
42
|
+
"types": "./dist/catalog.d.ts",
|
|
43
|
+
"default": "./dist/catalog.js"
|
|
75
44
|
},
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
45
|
+
"./budget": {
|
|
46
|
+
"source": "./src/budget.ts",
|
|
47
|
+
"types": "./dist/budget.d.ts",
|
|
48
|
+
"default": "./dist/budget.js"
|
|
80
49
|
},
|
|
81
|
-
"
|
|
82
|
-
|
|
50
|
+
"./expression": {
|
|
51
|
+
"source": "./src/expression.ts",
|
|
52
|
+
"types": "./dist/expression.d.ts",
|
|
53
|
+
"default": "./dist/expression.js"
|
|
54
|
+
},
|
|
55
|
+
"./syntax": {
|
|
56
|
+
"source": "./src/syntax.ts",
|
|
57
|
+
"types": "./dist/syntax.d.ts",
|
|
58
|
+
"default": "./dist/syntax.js"
|
|
59
|
+
},
|
|
60
|
+
"./compiler": {
|
|
61
|
+
"source": "./src/compiler.ts",
|
|
62
|
+
"types": "./dist/compiler.d.ts",
|
|
63
|
+
"default": "./dist/compiler.js"
|
|
64
|
+
},
|
|
65
|
+
"./runtime": {
|
|
66
|
+
"source": "./src/runtime.ts",
|
|
67
|
+
"types": "./dist/runtime.d.ts",
|
|
68
|
+
"default": "./dist/runtime.js"
|
|
69
|
+
},
|
|
70
|
+
"./protocol": {
|
|
71
|
+
"source": "./src/protocol.ts",
|
|
72
|
+
"types": "./dist/protocol.d.ts",
|
|
73
|
+
"default": "./dist/protocol.js"
|
|
74
|
+
},
|
|
75
|
+
"./text": {
|
|
76
|
+
"source": "./src/text.ts",
|
|
77
|
+
"types": "./dist/text.d.ts",
|
|
78
|
+
"default": "./dist/text.js"
|
|
79
|
+
},
|
|
80
|
+
"./stream": {
|
|
81
|
+
"source": "./src/stream.ts",
|
|
82
|
+
"types": "./dist/stream.d.ts",
|
|
83
|
+
"default": "./dist/stream.js"
|
|
83
84
|
}
|
|
85
|
+
},
|
|
86
|
+
"scripts": {
|
|
87
|
+
"build": "bun scripts/build.ts",
|
|
88
|
+
"typecheck": "tsc --noEmit",
|
|
89
|
+
"test": "bun test --conditions=source"
|
|
90
|
+
},
|
|
91
|
+
"devDependencies": {
|
|
92
|
+
"@types/bun": "^1.4.2",
|
|
93
|
+
"@types/node": "^24.13.6",
|
|
94
|
+
"typescript": "^7.0.2"
|
|
95
|
+
},
|
|
96
|
+
"dependencies": {
|
|
97
|
+
"zod": "^4.6.5"
|
|
98
|
+
}
|
|
84
99
|
}
|
package/src/actions.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { UiFailure } from './budget.ts';
|
|
2
|
+
import { isUiComponent, UI_CATALOG } from './catalog.ts';
|
|
3
|
+
import type { UiBlock } from './compiler.ts';
|
|
4
|
+
import type { UiExpression } from './expression.ts';
|
|
5
|
+
|
|
6
|
+
/* The prop of a component that holds a state action, kept unevaluated, instead of a value. */
|
|
7
|
+
export function uiActionProp(type: string): string | undefined {
|
|
8
|
+
if (!isUiComponent(type)) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const entry = UI_CATALOG[type];
|
|
12
|
+
return 'action' in entry ? entry.action : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The variable an action writes, or null for `@Reset()`, which restores them all. Throws for anything
|
|
17
|
+
* but `@Set($name, value)`, `@Reset($name)` and `@Reset()` on a variable that `local` accepts.
|
|
18
|
+
*/
|
|
19
|
+
export function uiActionTarget(action: UiExpression, local: (name: string) => boolean): string | null {
|
|
20
|
+
if (action.kind !== 'call' || (action.name !== 'Set' && action.name !== 'Reset')) {
|
|
21
|
+
throw new UiFailure('refused_action', 'Expected @Set or @Reset.');
|
|
22
|
+
}
|
|
23
|
+
if (action.name === 'Reset' && action.args.length === 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (action.args.length !== (action.name === 'Set' ? 2 : 1)) {
|
|
27
|
+
throw new UiFailure('refused_action', 'Invalid state action arguments.');
|
|
28
|
+
}
|
|
29
|
+
const reference = action.args[0];
|
|
30
|
+
if (reference.kind !== 'reference' || !local(reference.name)) {
|
|
31
|
+
throw new UiFailure('refused_binding', 'The action needs a local state variable.');
|
|
32
|
+
}
|
|
33
|
+
return reference.name;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Whether `name` is local state of the block: declared with a default and never a query. */
|
|
37
|
+
export function uiLocalState(block: Pick<UiBlock, 'defaults' | 'queries'>): (name: string) => boolean {
|
|
38
|
+
return (name) => Object.hasOwn(block.defaults, name) && !Object.hasOwn(block.queries, name);
|
|
39
|
+
}
|
package/src/catalog.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { uiPropList } from './prop-list.ts';
|
|
2
3
|
|
|
3
4
|
export const UI_CATALOG_VERSION = 1;
|
|
4
5
|
export const UiToneSchema = z.enum(['neutral', 'info', 'success', 'warning', 'danger']);
|
|
@@ -21,7 +22,10 @@ export const UI_GROUPS = {
|
|
|
21
22
|
rules: 'Image accepts a chat attachment or generated="latest", never a URL or a local path.'
|
|
22
23
|
},
|
|
23
24
|
links: { description: 'Refer to a host-owned resource.', rules: 'Targets are checked by the host. No arbitrary navigation or file reads.' },
|
|
24
|
-
inputs: {
|
|
25
|
+
inputs: {
|
|
26
|
+
description: 'Change local block state.',
|
|
27
|
+
rules: 'Bind value={$name}. A Button sets a literal with action={@Set($name, value)} or restores a default with {@Reset($name)} or {@Reset()}. State stays local until a Choice sends visible context.'
|
|
28
|
+
},
|
|
25
29
|
choices: {
|
|
26
30
|
description: 'Ask the person to pick a next step.',
|
|
27
31
|
rules: 'Give a short label and precise visible context. A Choice sends once after streaming ends.'
|
|
@@ -120,6 +124,12 @@ export const UI_CATALOG = {
|
|
|
120
124
|
},
|
|
121
125
|
Segmented: { group: 'inputs', description: 'Choose one local value.', schema: z.strictObject({ value: scalar }), children: ['Option'], binding: 'value' },
|
|
122
126
|
Option: { group: 'inputs', description: 'One local alternative.', schema: z.strictObject({ value: scalar }), parent: 'Segmented' },
|
|
127
|
+
Button: {
|
|
128
|
+
group: 'inputs',
|
|
129
|
+
description: 'Run one local state action when pressed; children are the label.',
|
|
130
|
+
schema: z.strictObject({ disabled: z.boolean().optional() }),
|
|
131
|
+
action: 'action'
|
|
132
|
+
},
|
|
123
133
|
Show: { group: 'inputs', description: 'Show children when the condition is true.', schema: z.strictObject({ when: z.boolean() }) },
|
|
124
134
|
Each: {
|
|
125
135
|
group: 'inputs',
|
|
@@ -144,12 +154,6 @@ export function isUiComponent(name: string): name is UiComponentName {
|
|
|
144
154
|
|
|
145
155
|
export function uiCatalogText(): string {
|
|
146
156
|
return Object.entries(UI_CATALOG)
|
|
147
|
-
.map(([name, entry]) => {
|
|
148
|
-
const shape = entry.schema.shape;
|
|
149
|
-
const props = Object.entries(shape)
|
|
150
|
-
.map(([key, schema]) => `${key}${schema.isOptional() ? '?' : ''}`)
|
|
151
|
-
.join(', ');
|
|
152
|
-
return `${name}(${props}): ${entry.description}`;
|
|
153
|
-
})
|
|
157
|
+
.map(([name, entry]) => `${name}(${uiPropList(entry).join(', ')}): ${entry.description}`)
|
|
154
158
|
.join('\n');
|
|
155
159
|
}
|