@ggui-ai/preview-a2ui 0.1.0-rc.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/LICENSE +201 -0
- package/README.md +87 -0
- package/dist/catalog.d.ts +51 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +57 -0
- package/dist/components.d.ts +143 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +177 -0
- package/dist/emitters/deterministic.d.ts +94 -0
- package/dist/emitters/deterministic.d.ts.map +1 -0
- package/dist/emitters/deterministic.js +184 -0
- package/dist/emitters/index.d.ts +15 -0
- package/dist/emitters/index.d.ts.map +1 -0
- package/dist/emitters/index.js +14 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/messages.d.ts +308 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +117 -0
- package/package.json +62 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2UI component shapes for ggui's V1 provisional subset.
|
|
3
|
+
*
|
|
4
|
+
* Each component has:
|
|
5
|
+
* - `id`: stable string identifier. Replace-by-id is the state model;
|
|
6
|
+
* re-emitting a component with the same id replaces it.
|
|
7
|
+
* - `component`: discriminator naming the catalog type.
|
|
8
|
+
* - Type-specific fields per the A2UI Basic Catalog.
|
|
9
|
+
*
|
|
10
|
+
* Schema discipline:
|
|
11
|
+
* - Unknown component names reject (discriminated union).
|
|
12
|
+
* - Unknown fields on a known component are stripped silently
|
|
13
|
+
* (Zod default). Tolerant because A2UI Basic Catalog evolves
|
|
14
|
+
* upstream and Haiku may emit extra hints we simply don't need.
|
|
15
|
+
* - Children references are plain strings — A2UI's "flat adjacency
|
|
16
|
+
* list" model. We don't enforce reference integrity at parse time;
|
|
17
|
+
* that's a renderer concern.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
/** Shared base: every component carries a stable string id. */
|
|
21
|
+
const ComponentBase = z.object({
|
|
22
|
+
id: z.string().min(1),
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Stable-id string reference to another component.
|
|
26
|
+
* Narrow alias so consumers can spot refs in the schema.
|
|
27
|
+
*/
|
|
28
|
+
const ComponentRef = z.string().min(1);
|
|
29
|
+
/** Shared layout hints accepted by container components. */
|
|
30
|
+
const Align = z.enum(['start', 'center', 'end', 'stretch']);
|
|
31
|
+
const Justify = z.enum([
|
|
32
|
+
'start',
|
|
33
|
+
'center',
|
|
34
|
+
'end',
|
|
35
|
+
'between',
|
|
36
|
+
'around',
|
|
37
|
+
'evenly',
|
|
38
|
+
]);
|
|
39
|
+
/**
|
|
40
|
+
* Horizontal container. Children are referenced by id, in order.
|
|
41
|
+
* Non-interactive in V1 — a provisional Row visually mirrors the real
|
|
42
|
+
* final layout without wiring interaction.
|
|
43
|
+
*/
|
|
44
|
+
const RowComponent = ComponentBase.extend({
|
|
45
|
+
component: z.literal('Row'),
|
|
46
|
+
children: z.array(ComponentRef).optional(),
|
|
47
|
+
gap: z.string().optional(),
|
|
48
|
+
align: Align.optional(),
|
|
49
|
+
justify: Justify.optional(),
|
|
50
|
+
});
|
|
51
|
+
/** Vertical container. Same shape as Row, different axis. */
|
|
52
|
+
const ColumnComponent = ComponentBase.extend({
|
|
53
|
+
component: z.literal('Column'),
|
|
54
|
+
children: z.array(ComponentRef).optional(),
|
|
55
|
+
gap: z.string().optional(),
|
|
56
|
+
align: Align.optional(),
|
|
57
|
+
justify: Justify.optional(),
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Card — single-child container with surface treatment. A2UI's
|
|
61
|
+
* canonical example uses a singular `child` key (not `children`)
|
|
62
|
+
* for Card; we honor that shape.
|
|
63
|
+
*/
|
|
64
|
+
const CardComponent = ComponentBase.extend({
|
|
65
|
+
component: z.literal('Card'),
|
|
66
|
+
child: ComponentRef.optional(),
|
|
67
|
+
});
|
|
68
|
+
/** Generic ordered list of components referenced by id. */
|
|
69
|
+
const ListComponent = ComponentBase.extend({
|
|
70
|
+
component: z.literal('List'),
|
|
71
|
+
children: z.array(ComponentRef).optional(),
|
|
72
|
+
});
|
|
73
|
+
/** Visual separator. No children, no payload. */
|
|
74
|
+
const DividerComponent = ComponentBase.extend({
|
|
75
|
+
component: z.literal('Divider'),
|
|
76
|
+
orientation: z.enum(['horizontal', 'vertical']).optional(),
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Text block. A2UI supports Markdown inside `text` and uses `variant`
|
|
80
|
+
* to carry semantic level (`h1`..`h6`, `body`, `caption`, `label`).
|
|
81
|
+
* We accept any variant string since Haiku may produce variants we
|
|
82
|
+
* hadn't anticipated — unknown variants degrade to default body text
|
|
83
|
+
* in the renderer, not a parse failure.
|
|
84
|
+
*/
|
|
85
|
+
const TextComponent = ComponentBase.extend({
|
|
86
|
+
component: z.literal('Text'),
|
|
87
|
+
text: z.string(),
|
|
88
|
+
variant: z.string().optional(),
|
|
89
|
+
});
|
|
90
|
+
/** Image. `alt` is recommended; not required for non-interactive preview. */
|
|
91
|
+
const ImageComponent = ComponentBase.extend({
|
|
92
|
+
component: z.literal('Image'),
|
|
93
|
+
src: z.string().min(1),
|
|
94
|
+
alt: z.string().optional(),
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Icon. `name` targets the upstream icon set (Material / Lucide
|
|
98
|
+
* family); the renderer maps it to its own icon registry.
|
|
99
|
+
*/
|
|
100
|
+
const IconComponent = ComponentBase.extend({
|
|
101
|
+
component: z.literal('Icon'),
|
|
102
|
+
name: z.string().min(1),
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Button shell — always disabled/non-interactive in V1. The `label`
|
|
106
|
+
* is rendered; `action` (if any A2UI emits) is intentionally dropped
|
|
107
|
+
* at the renderer since we don't process interactions on the
|
|
108
|
+
* provisional surface.
|
|
109
|
+
*/
|
|
110
|
+
const ButtonComponent = ComponentBase.extend({
|
|
111
|
+
component: z.literal('Button'),
|
|
112
|
+
label: z.string(),
|
|
113
|
+
});
|
|
114
|
+
/** Text input shell. Non-interactive in V1. */
|
|
115
|
+
const TextFieldComponent = ComponentBase.extend({
|
|
116
|
+
component: z.literal('TextField'),
|
|
117
|
+
label: z.string().optional(),
|
|
118
|
+
placeholder: z.string().optional(),
|
|
119
|
+
value: z.string().optional(),
|
|
120
|
+
});
|
|
121
|
+
/** Checkbox shell. Non-interactive in V1. */
|
|
122
|
+
const CheckBoxComponent = ComponentBase.extend({
|
|
123
|
+
component: z.literal('CheckBox'),
|
|
124
|
+
label: z.string().optional(),
|
|
125
|
+
checked: z.boolean().optional(),
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* Single-choice picker shell. Options are tuples of label + value.
|
|
129
|
+
* Non-interactive in V1 — rendered as a disabled select affordance.
|
|
130
|
+
*/
|
|
131
|
+
const ChoicePickerOption = z.object({
|
|
132
|
+
label: z.string(),
|
|
133
|
+
value: z.string(),
|
|
134
|
+
});
|
|
135
|
+
const ChoicePickerComponent = ComponentBase.extend({
|
|
136
|
+
component: z.literal('ChoicePicker'),
|
|
137
|
+
label: z.string().optional(),
|
|
138
|
+
options: z.array(ChoicePickerOption).optional(),
|
|
139
|
+
value: z.string().optional(),
|
|
140
|
+
});
|
|
141
|
+
/**
|
|
142
|
+
* Component schema — discriminated union over `component`. Unknown
|
|
143
|
+
* discriminator values reject at parse time. This is the catalog
|
|
144
|
+
* gate.
|
|
145
|
+
*/
|
|
146
|
+
export const ComponentSchema = z.discriminatedUnion('component', [
|
|
147
|
+
RowComponent,
|
|
148
|
+
ColumnComponent,
|
|
149
|
+
CardComponent,
|
|
150
|
+
ListComponent,
|
|
151
|
+
DividerComponent,
|
|
152
|
+
TextComponent,
|
|
153
|
+
ImageComponent,
|
|
154
|
+
IconComponent,
|
|
155
|
+
ButtonComponent,
|
|
156
|
+
TextFieldComponent,
|
|
157
|
+
CheckBoxComponent,
|
|
158
|
+
ChoicePickerComponent,
|
|
159
|
+
]);
|
|
160
|
+
/**
|
|
161
|
+
* Safe-parse a single component. Returns a discriminated result so
|
|
162
|
+
* callers don't have to choose between throwing and inspecting
|
|
163
|
+
* `ZodError`. Kept narrow on purpose — we don't re-export Zod's
|
|
164
|
+
* internals.
|
|
165
|
+
*/
|
|
166
|
+
export function parseComponent(input) {
|
|
167
|
+
const result = ComponentSchema.safeParse(input);
|
|
168
|
+
if (result.success)
|
|
169
|
+
return { ok: true, value: result.data };
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
issues: result.error.issues.map((issue) => ({
|
|
173
|
+
path: issue.path,
|
|
174
|
+
message: issue.message,
|
|
175
|
+
})),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-safe payload shape. Mirrors `@ggui-ai/protocol.JsonValue`
|
|
3
|
+
* structurally (mutable arrays, optional-undefined index entries)
|
|
4
|
+
* without importing it — keeps this package's zero cross-package
|
|
5
|
+
* dep boundary intact. The producer's A2UI frames are plain
|
|
6
|
+
* objects with string/number/boolean/null leaves, so every
|
|
7
|
+
* concrete emission satisfies this shape AND assigns into the
|
|
8
|
+
* orchestrator's `JsonValue`-typed `emit` sink.
|
|
9
|
+
*/
|
|
10
|
+
interface JsonPayloadObject {
|
|
11
|
+
[key: string]: JsonPayload | undefined;
|
|
12
|
+
}
|
|
13
|
+
type JsonPayload = string | number | boolean | null | JsonPayload[] | JsonPayloadObject;
|
|
14
|
+
/**
|
|
15
|
+
* Minimum fields the producer reads from the orchestrator's context.
|
|
16
|
+
* Structurally compatible with `ProvisionalPreviewContext` so callers
|
|
17
|
+
* wrap with a one-line `{run: (ctx) => produceDeterministicPreview(ctx)}`.
|
|
18
|
+
*/
|
|
19
|
+
export interface DeterministicPreviewContext {
|
|
20
|
+
/**
|
|
21
|
+
* A2UI surface id. Defaults to `ctx.stackItemId` when
|
|
22
|
+
* {@link DeterministicPreviewOptions.surfaceId} is absent.
|
|
23
|
+
*/
|
|
24
|
+
readonly stackItemId: string;
|
|
25
|
+
/**
|
|
26
|
+
* Push story. `intent` is the primary signal the producer reads;
|
|
27
|
+
* additional fields are accepted but ignored.
|
|
28
|
+
*/
|
|
29
|
+
readonly story: {
|
|
30
|
+
readonly intent: string;
|
|
31
|
+
} & Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Emit sink from the orchestrator. Declared over {@link JsonPayload}
|
|
34
|
+
* so callers whose `emit` narrows to `@ggui-ai/protocol.JsonValue`
|
|
35
|
+
* (the handler's `ProvisionalPreviewEmit` shape) assign in without
|
|
36
|
+
* a cast — `JsonPayload` is the same value-space. Return value is
|
|
37
|
+
* the wrapped `{seq?}`; the producer ignores it.
|
|
38
|
+
*/
|
|
39
|
+
readonly emit: (payload: JsonPayload) => Promise<unknown>;
|
|
40
|
+
/**
|
|
41
|
+
* Cancellation signal from the orchestrator. Checked between
|
|
42
|
+
* frames; on aborted the producer returns without further emits.
|
|
43
|
+
*/
|
|
44
|
+
readonly signal: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
export interface DeterministicPreviewOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Override the surface id. Defaults to `ctx.stackItemId` — keeping the
|
|
49
|
+
* surface id aligned with the stackItemId makes the registry's
|
|
50
|
+
* stackItemId-keyed cancellation reach the right client surface when
|
|
51
|
+
* the renderer buffers on `createSurface.surfaceId`.
|
|
52
|
+
*/
|
|
53
|
+
readonly surfaceId?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Override the catalog id referenced in `createSurface`. Defaults
|
|
56
|
+
* to `ggui.preview.v1` — the published manifest this package's
|
|
57
|
+
* subset targets. Only override if the caller is pointing at a
|
|
58
|
+
* different deployed catalog.
|
|
59
|
+
*/
|
|
60
|
+
readonly catalogId?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Produce a provisional preview for the supplied story. Emits 3
|
|
64
|
+
* frames on the happy path; returns early on cancellation.
|
|
65
|
+
*
|
|
66
|
+
* **No `deleteSurface` on happy path.** The provisional preview is
|
|
67
|
+
* meant to stay visible until the authoritative final UI replaces
|
|
68
|
+
* it. Tearing down the surface in the producer would clear the
|
|
69
|
+
* rendered fragments and leave the viewer blank between
|
|
70
|
+
* "preview-done" and "final-code-arrives" — the wrong UX.
|
|
71
|
+
*
|
|
72
|
+
* Authoritative handoff is the cancellation site: when real
|
|
73
|
+
* component code lands, the orchestrator's
|
|
74
|
+
* `finalizeProvisionalPreview` aborts the runner with reason
|
|
75
|
+
* `'handoff'` and the renderer swaps in the real component. Until
|
|
76
|
+
* then (e.g. on OSS today, where final generation isn't wired),
|
|
77
|
+
* the assembled surface stays painted as the user-facing preview.
|
|
78
|
+
*
|
|
79
|
+
* The orchestrator still owns the channel-level close
|
|
80
|
+
* (`{payload: null, complete: true}`); that just latches the
|
|
81
|
+
* channel as drained without affecting the rendered surface.
|
|
82
|
+
*/
|
|
83
|
+
export declare function produceDeterministicPreview(ctx: DeterministicPreviewContext, options?: DeterministicPreviewOptions): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Default export as a factory that adapts the producer into the
|
|
86
|
+
* orchestrator's `ProvisionalPreviewEmitter` shape without forcing
|
|
87
|
+
* consumers to write the one-line wrapper. Typed structurally so no
|
|
88
|
+
* import from `@ggui-ai/mcp-server-handlers` is required.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createDeterministicPreviewEmitter(options?: DeterministicPreviewOptions): {
|
|
91
|
+
run: (ctx: DeterministicPreviewContext) => Promise<void>;
|
|
92
|
+
};
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=deterministic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deterministic.d.ts","sourceRoot":"","sources":["../../src/emitters/deterministic.ts"],"names":[],"mappings":"AAuCA;;;;;;;;GAQG;AACH,UAAU,iBAAiB;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;CACxC;AACD,KAAK,WAAW,GACZ,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,WAAW,EAAE,GACb,iBAAiB,CAAC;AAEtB;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtE;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,WAAW,2BAA2B;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAmBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,2BAA2B,EAChC,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,IAAI,CAAC,CAqEf;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,CAAC,EAAE,2BAA2B,GACpC;IAAE,GAAG,EAAE,CAAC,GAAG,EAAE,2BAA2B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAI9D"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical catalog id for the V1 ggui preview subset. Kept in
|
|
3
|
+
* alignment with {@link GGUI_PREVIEW_CATALOG_V1_ID} via a type-level
|
|
4
|
+
* parity assertion below so a rename in one place can't silently
|
|
5
|
+
* desync.
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_CATALOG_ID = 'ggui.preview.v1';
|
|
8
|
+
/**
|
|
9
|
+
* Compile-time parity guard: the producer's default catalog id
|
|
10
|
+
* matches the canonical catalog manifest id. If the manifest id
|
|
11
|
+
* ever changes, this assignment fails to typecheck — the producer
|
|
12
|
+
* must be updated in lockstep.
|
|
13
|
+
*/
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
15
|
+
const _CATALOG_ID_PARITY = DEFAULT_CATALOG_ID;
|
|
16
|
+
/**
|
|
17
|
+
* Produce a provisional preview for the supplied story. Emits 3
|
|
18
|
+
* frames on the happy path; returns early on cancellation.
|
|
19
|
+
*
|
|
20
|
+
* **No `deleteSurface` on happy path.** The provisional preview is
|
|
21
|
+
* meant to stay visible until the authoritative final UI replaces
|
|
22
|
+
* it. Tearing down the surface in the producer would clear the
|
|
23
|
+
* rendered fragments and leave the viewer blank between
|
|
24
|
+
* "preview-done" and "final-code-arrives" — the wrong UX.
|
|
25
|
+
*
|
|
26
|
+
* Authoritative handoff is the cancellation site: when real
|
|
27
|
+
* component code lands, the orchestrator's
|
|
28
|
+
* `finalizeProvisionalPreview` aborts the runner with reason
|
|
29
|
+
* `'handoff'` and the renderer swaps in the real component. Until
|
|
30
|
+
* then (e.g. on OSS today, where final generation isn't wired),
|
|
31
|
+
* the assembled surface stays painted as the user-facing preview.
|
|
32
|
+
*
|
|
33
|
+
* The orchestrator still owns the channel-level close
|
|
34
|
+
* (`{payload: null, complete: true}`); that just latches the
|
|
35
|
+
* channel as drained without affecting the rendered surface.
|
|
36
|
+
*/
|
|
37
|
+
export async function produceDeterministicPreview(ctx, options) {
|
|
38
|
+
const surfaceId = options?.surfaceId ?? ctx.stackItemId;
|
|
39
|
+
const catalogId = options?.catalogId ?? DEFAULT_CATALOG_ID;
|
|
40
|
+
// Frame 1 — surface creation.
|
|
41
|
+
await ctx.emit({
|
|
42
|
+
version: 'v0.9',
|
|
43
|
+
createSurface: { surfaceId, catalogId },
|
|
44
|
+
});
|
|
45
|
+
if (ctx.signal.aborted)
|
|
46
|
+
return;
|
|
47
|
+
// Frame 2 — root skeleton. Shows a heading derived from the
|
|
48
|
+
// intent so the user sees "what is being built" before the body
|
|
49
|
+
// + shells fill in.
|
|
50
|
+
const heading = deriveHeading(ctx.story.intent);
|
|
51
|
+
await ctx.emit({
|
|
52
|
+
version: 'v0.9',
|
|
53
|
+
updateComponents: {
|
|
54
|
+
surfaceId,
|
|
55
|
+
components: [
|
|
56
|
+
{
|
|
57
|
+
id: 'root',
|
|
58
|
+
component: 'Column',
|
|
59
|
+
children: ['heading'],
|
|
60
|
+
gap: '12',
|
|
61
|
+
align: 'stretch',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'heading',
|
|
65
|
+
component: 'Text',
|
|
66
|
+
variant: 'h2',
|
|
67
|
+
text: heading,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
if (ctx.signal.aborted)
|
|
73
|
+
return;
|
|
74
|
+
// Frame 3 — enriched layout. Adds a body caption + keyword-driven
|
|
75
|
+
// shells (form / list / …) so the surface reads as the rough
|
|
76
|
+
// outline of the final UI rather than a lone heading. This is
|
|
77
|
+
// the terminal frame on the happy path; the surface stays
|
|
78
|
+
// painted in the viewer until authoritative handoff aborts the
|
|
79
|
+
// runner (or the user navigates away).
|
|
80
|
+
const body = deriveBody(ctx.story.intent);
|
|
81
|
+
const shell = pickShell(ctx.story.intent);
|
|
82
|
+
const rootChildren = ['heading', 'body', ...shell.ids];
|
|
83
|
+
await ctx.emit({
|
|
84
|
+
version: 'v0.9',
|
|
85
|
+
updateComponents: {
|
|
86
|
+
surfaceId,
|
|
87
|
+
components: [
|
|
88
|
+
{
|
|
89
|
+
id: 'root',
|
|
90
|
+
component: 'Column',
|
|
91
|
+
children: rootChildren,
|
|
92
|
+
gap: '12',
|
|
93
|
+
align: 'stretch',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'body',
|
|
97
|
+
component: 'Text',
|
|
98
|
+
variant: 'caption',
|
|
99
|
+
text: body,
|
|
100
|
+
},
|
|
101
|
+
...shell.fragments,
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Default export as a factory that adapts the producer into the
|
|
108
|
+
* orchestrator's `ProvisionalPreviewEmitter` shape without forcing
|
|
109
|
+
* consumers to write the one-line wrapper. Typed structurally so no
|
|
110
|
+
* import from `@ggui-ai/mcp-server-handlers` is required.
|
|
111
|
+
*/
|
|
112
|
+
export function createDeterministicPreviewEmitter(options) {
|
|
113
|
+
return {
|
|
114
|
+
run: (ctx) => produceDeterministicPreview(ctx, options),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// ─── Heuristics ────────────────────────────────────────────────────────
|
|
118
|
+
/**
|
|
119
|
+
* First-sentence heading derivation. Takes the first
|
|
120
|
+
* sentence-terminating punctuation as the boundary, falls back to
|
|
121
|
+
* the first ~80 chars. Capitalizes the leading character so
|
|
122
|
+
* lowercase intents ("build a chat app") render as titles.
|
|
123
|
+
*/
|
|
124
|
+
function deriveHeading(intent) {
|
|
125
|
+
const trimmed = intent.trim();
|
|
126
|
+
if (trimmed.length === 0)
|
|
127
|
+
return 'Preparing your view…';
|
|
128
|
+
const firstSentence = trimmed.split(/[.!?\n]/)[0]?.trim() ?? trimmed;
|
|
129
|
+
const clipped = firstSentence.length <= 80 ? firstSentence : firstSentence.slice(0, 80);
|
|
130
|
+
const leading = clipped.charAt(0).toUpperCase();
|
|
131
|
+
return leading + clipped.slice(1);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Body caption. Tight clip so the provisional surface doesn't try
|
|
135
|
+
* to render the full intent — the final UI handles rich content;
|
|
136
|
+
* the preview just sets expectations.
|
|
137
|
+
*/
|
|
138
|
+
function deriveBody(intent) {
|
|
139
|
+
const trimmed = intent.trim();
|
|
140
|
+
if (trimmed.length === 0)
|
|
141
|
+
return '';
|
|
142
|
+
if (trimmed.length <= 120)
|
|
143
|
+
return trimmed;
|
|
144
|
+
return trimmed.slice(0, 117) + '…';
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Keyword-driven shell selection. Tiny heuristics — not an NLP
|
|
148
|
+
* pass. The goal is "provisional looks broadly right for common
|
|
149
|
+
* intents", not "provisional is 95% accurate". When the heuristics
|
|
150
|
+
* miss, the rendered surface degrades to heading + caption, which
|
|
151
|
+
* is still honest about what's coming.
|
|
152
|
+
*/
|
|
153
|
+
function pickShell(intent) {
|
|
154
|
+
const lower = intent.toLowerCase();
|
|
155
|
+
if (/\b(form|input|sign\s?up|log\s?in|register|submit|feedback)\b/.test(lower)) {
|
|
156
|
+
return {
|
|
157
|
+
ids: ['form-card'],
|
|
158
|
+
fragments: [
|
|
159
|
+
{ id: 'form-card', component: 'Card', child: 'form-col' },
|
|
160
|
+
{
|
|
161
|
+
id: 'form-col',
|
|
162
|
+
component: 'Column',
|
|
163
|
+
children: ['tf', 'btn'],
|
|
164
|
+
gap: '8',
|
|
165
|
+
},
|
|
166
|
+
{ id: 'tf', component: 'TextField', label: 'Input' },
|
|
167
|
+
{ id: 'btn', component: 'Button', label: 'Submit' },
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (/\b(list|items|todos?|feed|posts?|grid|dashboard|table)\b/.test(lower)) {
|
|
172
|
+
return {
|
|
173
|
+
ids: ['list-card'],
|
|
174
|
+
fragments: [
|
|
175
|
+
{ id: 'list-card', component: 'Card', child: 'list' },
|
|
176
|
+
{ id: 'list', component: 'List', children: ['l1', 'l2', 'l3'] },
|
|
177
|
+
{ id: 'l1', component: 'Text', variant: 'body', text: '—' },
|
|
178
|
+
{ id: 'l2', component: 'Text', variant: 'body', text: '—' },
|
|
179
|
+
{ id: 'l3', component: 'Text', variant: 'body', text: '—' },
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return { ids: [], fragments: [] };
|
|
184
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ggui-ai/preview-a2ui/emitters` — reference producers for the
|
|
3
|
+
* provisional preview pipeline.
|
|
4
|
+
*
|
|
5
|
+
* V1 ships a deterministic producer (`produceDeterministicPreview`
|
|
6
|
+
* + `createDeterministicPreviewEmitter`) that makes the server
|
|
7
|
+
* orchestration path real without requiring a fast-model LLM. A
|
|
8
|
+
* future fast-model-backed producer can layer onto the same contract.
|
|
9
|
+
*
|
|
10
|
+
* Keeping producers in a dedicated subpath lets consumers import
|
|
11
|
+
* exactly the bundle they need — hosted pods pulling a Haiku
|
|
12
|
+
* producer don't pay for the deterministic one, and vice-versa.
|
|
13
|
+
*/
|
|
14
|
+
export { createDeterministicPreviewEmitter, produceDeterministicPreview, type DeterministicPreviewContext, type DeterministicPreviewOptions, } from './deterministic';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/emitters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,iCAAiC,EACjC,2BAA2B,EAC3B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,GACjC,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ggui-ai/preview-a2ui/emitters` — reference producers for the
|
|
3
|
+
* provisional preview pipeline.
|
|
4
|
+
*
|
|
5
|
+
* V1 ships a deterministic producer (`produceDeterministicPreview`
|
|
6
|
+
* + `createDeterministicPreviewEmitter`) that makes the server
|
|
7
|
+
* orchestration path real without requiring a fast-model LLM. A
|
|
8
|
+
* future fast-model-backed producer can layer onto the same contract.
|
|
9
|
+
*
|
|
10
|
+
* Keeping producers in a dedicated subpath lets consumers import
|
|
11
|
+
* exactly the bundle they need — hosted pods pulling a Haiku
|
|
12
|
+
* producer don't pay for the deterministic one, and vice-versa.
|
|
13
|
+
*/
|
|
14
|
+
export { createDeterministicPreviewEmitter, produceDeterministicPreview, } from './deterministic.js';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ggui-ai/preview-a2ui` — A2UI boundary for ggui's provisional
|
|
3
|
+
* assembly channel.
|
|
4
|
+
*
|
|
5
|
+
* V1 scope:
|
|
6
|
+
* - Server → client write path only
|
|
7
|
+
* - Three messages: createSurface / updateComponents / deleteSurface
|
|
8
|
+
* - Narrow catalog (12 Basic Catalog components — see `./catalog`)
|
|
9
|
+
* - Non-interactive provisional rendering
|
|
10
|
+
*
|
|
11
|
+
* Anything outside this scope (data model, interactive actions,
|
|
12
|
+
* broader catalog coverage) is deliberately deferred. Widen the
|
|
13
|
+
* surface intentionally, not accidentally.
|
|
14
|
+
*
|
|
15
|
+
* Deps: `zod` only. No React, no React Native, no `@ggui-ai/protocol`
|
|
16
|
+
* import — this package is framework-neutral and protocol-neutral.
|
|
17
|
+
* The reserved `_ggui:preview` channel constant lives in
|
|
18
|
+
* `@ggui-ai/protocol/validation/reserved-channels`; this package is
|
|
19
|
+
* the other side of the boundary and must not couple to the protocol
|
|
20
|
+
* root.
|
|
21
|
+
*/
|
|
22
|
+
export * from './catalog';
|
|
23
|
+
export * from './components';
|
|
24
|
+
export * from './messages';
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ggui-ai/preview-a2ui` — A2UI boundary for ggui's provisional
|
|
3
|
+
* assembly channel.
|
|
4
|
+
*
|
|
5
|
+
* V1 scope:
|
|
6
|
+
* - Server → client write path only
|
|
7
|
+
* - Three messages: createSurface / updateComponents / deleteSurface
|
|
8
|
+
* - Narrow catalog (12 Basic Catalog components — see `./catalog`)
|
|
9
|
+
* - Non-interactive provisional rendering
|
|
10
|
+
*
|
|
11
|
+
* Anything outside this scope (data model, interactive actions,
|
|
12
|
+
* broader catalog coverage) is deliberately deferred. Widen the
|
|
13
|
+
* surface intentionally, not accidentally.
|
|
14
|
+
*
|
|
15
|
+
* Deps: `zod` only. No React, no React Native, no `@ggui-ai/protocol`
|
|
16
|
+
* import — this package is framework-neutral and protocol-neutral.
|
|
17
|
+
* The reserved `_ggui:preview` channel constant lives in
|
|
18
|
+
* `@ggui-ai/protocol/validation/reserved-channels`; this package is
|
|
19
|
+
* the other side of the boundary and must not couple to the protocol
|
|
20
|
+
* root.
|
|
21
|
+
*/
|
|
22
|
+
export * from './catalog.js';
|
|
23
|
+
export * from './components.js';
|
|
24
|
+
export * from './messages.js';
|