@ggui-ai/ui-gen 0.15.0 → 0.17.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/dist/adapters/generation-dispatch.d.ts +24 -4
- package/dist/adapters/generation-dispatch.js +5188 -3920
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +33 -26
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +22 -13
- package/dist/advanced/index.js +4889 -3561
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +8 -12
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +19 -1
- package/dist/boilerplate.js +671 -254
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +17 -3
- package/dist/check/index.js +34 -25
- package/dist/check/index.js.map +1 -1
- package/dist/compose.js +1 -1
- package/dist/compose.js.map +1 -1
- package/dist/create-ui-generator-C8Ki9Wz1.d.ts +189 -0
- package/dist/design-mode-DLufhrNp.d.ts +61 -0
- package/dist/{dispatch-BuZfTh9r.d.ts → dispatch-D2htzi9h.d.ts} +4 -1
- package/dist/evaluation/axis-checks/index.d.ts +4 -3
- package/dist/evaluation/axis-checks/index.js +300 -14
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +2 -1
- package/dist/evaluation/axis-checks/registry.js +253 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +297 -5
- package/dist/evaluation/index.js +4515 -120
- package/dist/evaluation/index.js.map +1 -1
- package/dist/fragments/index.js +1 -1
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-BTfvELgl.d.ts → generate-9IeNT4ZY.d.ts} +10 -1
- package/dist/harness/check/runtime-render/index.d.ts +3 -1
- package/dist/harness/check/runtime-render/index.js +8 -12
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/index.d.ts +3 -1
- package/dist/harness/index.js +913 -441
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.d.ts +3 -3
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -1
- package/dist/harness/prompts.js +6 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +17 -5
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +28 -3
- package/dist/harness/runtime.js +702 -274
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +39 -1
- package/dist/harness/types-public.js +913 -441
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +2 -1
- package/dist/index.d.ts +32 -6
- package/dist/index.js +4899 -3560
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +3 -1
- package/dist/{prompts-BVhtH3M1.d.ts → prompts-YU9piOgs.d.ts} +21 -45
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.js.map +1 -1
- package/dist/styling-profile-CxfvV48g.d.ts +35 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +1 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/{types-public-CkNWEov9.d.ts → types-public-DVSssdc-.d.ts} +85 -5
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +4 -4
- package/dist/validation/index.js.map +1 -1
- package/dist/workflows.d.ts +3 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +62 -4
- package/src/adapters/provider-router.ts +7 -6
- package/src/adapters/tools.ts +5 -5
- package/src/advanced/generator.ts +11 -10
- package/src/advanced/index.ts +5 -4
- package/src/boilerplate/free-design-prompt.ts +336 -0
- package/src/boilerplate/generate.ts +10 -1
- package/src/boilerplate/hard-sections.ts +257 -0
- package/src/boilerplate/render.ts +32 -8
- package/src/boilerplate/styling-profile.ts +166 -0
- package/src/boilerplate/system-prompt.ts +101 -237
- package/src/boilerplate/templates/base-free.tsx.tmpl +24 -0
- package/src/check/run-tier0.ts +82 -20
- package/src/coding-agent/tools.ts +8 -2
- package/src/contract-context.ts +43 -14
- package/src/create-harness.ts +15 -0
- package/src/create-ui-generator.ts +112 -27
- package/src/design-mode.ts +192 -0
- package/src/design-system-docs.ts +24 -17
- package/src/effort.ts +48 -0
- package/src/evaluation/axis-checks/checks/grid.ts +148 -0
- package/src/evaluation/axis-checks/checks/universal.ts +244 -0
- package/src/evaluation/axis-checks/dispatch.ts +82 -13
- package/src/evaluation/axis-checks/extras.ts +5 -0
- package/src/evaluation/axis-checks/helpers.ts +12 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/cost-tracker.ts +2 -3
- package/src/evaluation/design-identity.ts +96 -0
- package/src/evaluation/index.ts +63 -2
- package/src/evaluation/llm-evaluator.ts +89 -21
- package/src/evaluation/theme-css.ts +14 -0
- package/src/evaluation/types-public.ts +137 -8
- package/src/evaluation/visual-evaluator.ts +759 -54
- package/src/fragments/render.ts +1 -1
- package/src/harness/check/runtime-render/prepare-mockup.ts +18 -11
- package/src/harness/coding/dupe-break.ts +0 -0
- package/src/harness/coding/init-session.ts +2 -0
- package/src/harness/coding/run-coding-turn.ts +9 -14
- package/src/harness/coding/run-eval-round.ts +46 -5
- package/src/harness/llm-trace-sink.ts +3 -3
- package/src/harness/result-types.ts +15 -3
- package/src/harness/runtime.ts +28 -0
- package/src/harness/types-public.ts +38 -0
- package/src/index.ts +28 -0
- package/src/internal/extract-call-signatures.ts +2 -2
- package/src/pin-fixtures.ts +98 -0
- package/src/providers/bedrock.ts +4 -4
- package/src/run-check.ts +11 -10
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools.ts +2 -1
- package/src/validation/allowed-imports.ts +8 -2
- package/src/validation/primitives.ts +1 -1
- package/dist/create-ui-generator-D6TKuv9V.d.ts +0 -106
|
@@ -31,11 +31,15 @@
|
|
|
31
31
|
* Callers who want a lightweight single-shot path can build it
|
|
32
32
|
* themselves.
|
|
33
33
|
*/
|
|
34
|
+
import type { SingleComponentParams } from './harness/runtime.js';
|
|
34
35
|
import type {
|
|
35
36
|
GadgetDescriptor,
|
|
36
37
|
GenerationError,
|
|
37
38
|
JsonObject,
|
|
39
|
+
GeneratorId,
|
|
40
|
+
ModelRef,
|
|
38
41
|
} from '@ggui-ai/protocol';
|
|
42
|
+
import { isGeneratorId, modelRefOfRoute } from '@ggui-ai/protocol';
|
|
39
43
|
import type { GadgetCatalogAdapter } from '@ggui-ai/gadgets';
|
|
40
44
|
import type {
|
|
41
45
|
GenerationMetadata,
|
|
@@ -52,20 +56,26 @@ import {
|
|
|
52
56
|
} from '@ggui-ai/mcp-server-core';
|
|
53
57
|
import { createGeneratorTools } from './adapters/index.js';
|
|
54
58
|
import { dispatchGeneration } from './adapters/generation-dispatch.js';
|
|
59
|
+
import { hasProfile } from './boilerplate/styling-profile.js';
|
|
60
|
+
import { effortDials } from './effort.js';
|
|
55
61
|
import type { ProviderName } from './adapters/types.js';
|
|
62
|
+
import type { GenerationResult } from './harness/result-types.js';
|
|
56
63
|
import {
|
|
64
|
+
canvasForRenderingContext,
|
|
57
65
|
injectContracts,
|
|
58
66
|
injectRenderingContext,
|
|
59
67
|
injectVariance,
|
|
60
68
|
} from './contract-context.js';
|
|
61
69
|
import type { RenderingContext } from './contract-context.js';
|
|
70
|
+
import type { DesignMode } from './design-mode.js';
|
|
62
71
|
import { resolveRoute, applyRouteToEnv } from './adapters/provider-router.js';
|
|
63
72
|
import type { QualityConfig } from './evaluation/types-public.js';
|
|
73
|
+
import type { EvaluationConfig } from './evaluation/types.js';
|
|
64
74
|
import type { AgentConfig } from './harness/llm-router.js';
|
|
65
75
|
|
|
66
76
|
/** The slug for the OSS default seed generator. */
|
|
67
77
|
const DEFAULT_TIER: GeneratorTier = 'default';
|
|
68
|
-
const DEFAULT_MODEL = 'haiku-4-5';
|
|
78
|
+
const DEFAULT_MODEL: ModelRef = 'anthropic/claude-haiku-4-5';
|
|
69
79
|
|
|
70
80
|
export interface CreateUiGeneratorOptions {
|
|
71
81
|
/**
|
|
@@ -83,27 +93,42 @@ export interface CreateUiGeneratorOptions {
|
|
|
83
93
|
readonly maxAttempts?: number;
|
|
84
94
|
/** Maximum evaluation rounds. */
|
|
85
95
|
readonly maxEvalRounds?: number;
|
|
96
|
+
/**
|
|
97
|
+
* In-loop LLM evaluation config (ggui#1001). When set, every generation
|
|
98
|
+
* runs the evaluator tiers after the coding leg (`maxRounds` defaults to
|
|
99
|
+
* `maxEvalRounds`) and the harness result carries `evalResult` — the
|
|
100
|
+
* probe verdict a downstream judge reads. Default: none, as today —
|
|
101
|
+
* a serving deployment leaves it unset; offline minting jobs set a round.
|
|
102
|
+
*/
|
|
103
|
+
readonly evaluation?: EvaluationConfig;
|
|
104
|
+
/**
|
|
105
|
+
* In-loop visual round config (per-canvas judging, a sample, the
|
|
106
|
+
* stylesheet to render under). Forwarded verbatim; never part of the
|
|
107
|
+
* prompt or the identity. Default: none — the round runs as today.
|
|
108
|
+
*/
|
|
109
|
+
readonly visualEvaluation?: SingleComponentParams['visualEvaluation'];
|
|
86
110
|
/** Quality config controlling eval tiers + improvement behavior. */
|
|
87
111
|
readonly qualityConfig?: QualityConfig;
|
|
88
112
|
/**
|
|
89
|
-
* Generator identity — registry key +
|
|
113
|
+
* Generator identity — registry key + declared model.
|
|
90
114
|
*
|
|
91
|
-
* The tier
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* `ui-gen-default
|
|
115
|
+
* The tier alone determines the {@link UiGenerator.slug} the factory
|
|
116
|
+
* bakes onto the returned generator (`ui-gen-<tier>`); `model` is the
|
|
117
|
+
* generator's declared model field and is never part of the slug
|
|
118
|
+
* (ggui#923). Defaults to tier `'default'` → slug `ui-gen-default`,
|
|
119
|
+
* the OSS seed, with the factory's default model.
|
|
95
120
|
*
|
|
96
121
|
* The actual model used per request still comes from
|
|
97
122
|
* `UiGenerateInput.llm.model` (operators may override via BYOK).
|
|
98
123
|
* The identity here is the registry-level handle, not a runtime
|
|
99
124
|
* model constraint.
|
|
100
125
|
*
|
|
101
|
-
* Mutually-exclusive shortcut: pass `slug` instead and the
|
|
102
|
-
* parses
|
|
103
|
-
* factory throws if
|
|
126
|
+
* Mutually-exclusive shortcut: pass `slug` instead of `tier` and the
|
|
127
|
+
* factory parses the tier from it; `model` may accompany either form.
|
|
128
|
+
* The factory throws if `slug` and `tier` are both supplied.
|
|
104
129
|
*/
|
|
105
130
|
readonly tier?: GeneratorTier;
|
|
106
|
-
readonly model?:
|
|
131
|
+
readonly model?: ModelRef;
|
|
107
132
|
readonly slug?: string;
|
|
108
133
|
/**
|
|
109
134
|
* Per-deployment gadget descriptor source. Wired once at factory
|
|
@@ -163,6 +188,35 @@ export interface CreateUiGeneratorOptions {
|
|
|
163
188
|
* still log via `console.warn`, just without a caller-side hook.
|
|
164
189
|
*/
|
|
165
190
|
readonly onRetry?: AgentConfig['onRetry'];
|
|
191
|
+
/**
|
|
192
|
+
* Generation observer. Invoked once per successful `generate()` with
|
|
193
|
+
* the FULL harness result — source, compiled code, token counters,
|
|
194
|
+
* timing breakdown, evaluation rounds and (when configured) the
|
|
195
|
+
* per-canvas visual scores — before it is projected onto the
|
|
196
|
+
* `UiGenerateResult` envelope, which carries only what the protocol
|
|
197
|
+
* needs. Lets a host record or forward the complete run without
|
|
198
|
+
* re-deriving it from the envelope. Bound ONCE at construction
|
|
199
|
+
* (mirrors `onRetry`); not invoked when generation fails. An
|
|
200
|
+
* exception thrown by the observer is the caller's own and fails
|
|
201
|
+
* that generation like any other error on the path — keep it
|
|
202
|
+
* non-throwing.
|
|
203
|
+
*/
|
|
204
|
+
readonly onGenerated?: (result: GenerationResult) => void;
|
|
205
|
+
/**
|
|
206
|
+
* Which generation triad to run. `constrained` (default) is the
|
|
207
|
+
* design-system triad — byte-identical to the generator's behaviour
|
|
208
|
+
* before this option existed. `free` relaxes only the design
|
|
209
|
+
* vocabulary (raw HTML, inline CSS, literal spacing / typography /
|
|
210
|
+
* radius; the design package importable but optional) while keeping
|
|
211
|
+
* every hard contract: the boilerplate-typed wire hooks, the import
|
|
212
|
+
* allowlist + security bans, the host envelope, the budgets, the
|
|
213
|
+
* blueprint-reuse shape, and brand-bearing color on the closed
|
|
214
|
+
* `--ggui-color-*` token manifest. Reaches the prompt, boilerplate,
|
|
215
|
+
* tier-0 gate, axis checks and evaluator criteria through the harness.
|
|
216
|
+
* The rendering canvas the `free` prompt states is derived from
|
|
217
|
+
* `input.rendering` (device × shell, refined by viewport width).
|
|
218
|
+
*/
|
|
219
|
+
readonly designMode?: DesignMode;
|
|
166
220
|
}
|
|
167
221
|
|
|
168
222
|
export function createUiGenerator(
|
|
@@ -172,10 +226,14 @@ export function createUiGenerator(
|
|
|
172
226
|
enableRuntimeRender = false,
|
|
173
227
|
maxAttempts,
|
|
174
228
|
maxEvalRounds,
|
|
229
|
+
evaluation,
|
|
230
|
+
visualEvaluation,
|
|
175
231
|
qualityConfig,
|
|
176
232
|
gadgetCatalog,
|
|
177
233
|
disableEnvMutation = false,
|
|
178
234
|
onRetry,
|
|
235
|
+
onGenerated,
|
|
236
|
+
designMode,
|
|
179
237
|
} = options;
|
|
180
238
|
|
|
181
239
|
const identity = resolveIdentity(options);
|
|
@@ -268,9 +326,14 @@ export function createUiGenerator(
|
|
|
268
326
|
const promptWithRendering = rendering
|
|
269
327
|
? injectRenderingContext(input.request.prompt, rendering)
|
|
270
328
|
: input.request.prompt;
|
|
329
|
+
// Canvas class for the `free` prompt — TOLD, never guessed.
|
|
330
|
+
// Ignored by the constrained prompt (its shell/screen
|
|
331
|
+
// descriptors stay byte-identical).
|
|
332
|
+
const canvas = rendering ? canvasForRenderingContext(rendering) : undefined;
|
|
271
333
|
const promptWithVariance = injectVariance(
|
|
272
334
|
promptWithRendering,
|
|
273
335
|
input.variance,
|
|
336
|
+
{ profileDeclared: hasProfile(input.profile) },
|
|
274
337
|
);
|
|
275
338
|
|
|
276
339
|
// Resolve appGadgets by precedence:
|
|
@@ -293,6 +356,19 @@ export function createUiGenerator(
|
|
|
293
356
|
resolvedAppGadgets,
|
|
294
357
|
);
|
|
295
358
|
|
|
359
|
+
// `effort` (ggui#1059): a named level's dials override the
|
|
360
|
+
// deployment's options for THIS generation; absent ⇒ untouched.
|
|
361
|
+
// Prompt bytes never move with it (dials only).
|
|
362
|
+
const dials = effortDials(input.profile?.effort);
|
|
363
|
+
const effectiveMaxAttempts = dials?.maxAttempts ?? maxAttempts;
|
|
364
|
+
const effectiveMaxEvalRounds = dials?.maxEvalRounds ?? maxEvalRounds;
|
|
365
|
+
const effectiveEvaluation =
|
|
366
|
+
dials !== undefined && evaluation !== undefined ? { ...evaluation, maxRounds: dials.maxEvalRounds } : evaluation;
|
|
367
|
+
const effectiveVisualEvaluation =
|
|
368
|
+
dials !== undefined && visualEvaluation !== undefined
|
|
369
|
+
? { ...visualEvaluation, passThreshold: dials.selfEvalPassThreshold }
|
|
370
|
+
: visualEvaluation;
|
|
371
|
+
|
|
296
372
|
const result = await dispatchGeneration({
|
|
297
373
|
provider,
|
|
298
374
|
userPrompt,
|
|
@@ -300,8 +376,10 @@ export function createUiGenerator(
|
|
|
300
376
|
tools,
|
|
301
377
|
...(input.contract ? { contract: input.contract } : {}),
|
|
302
378
|
originalPrompt: input.request.prompt,
|
|
303
|
-
...(
|
|
304
|
-
...(
|
|
379
|
+
...(effectiveMaxAttempts !== undefined ? { maxAttempts: effectiveMaxAttempts } : {}),
|
|
380
|
+
...(effectiveMaxEvalRounds !== undefined ? { maxEvalRounds: effectiveMaxEvalRounds } : {}),
|
|
381
|
+
...(effectiveEvaluation !== undefined ? { evaluation: effectiveEvaluation } : {}),
|
|
382
|
+
...(effectiveVisualEvaluation !== undefined ? { visualEvaluation: effectiveVisualEvaluation } : {}),
|
|
305
383
|
...(qualityConfig !== undefined ? { qualityConfig } : {}),
|
|
306
384
|
...(resolvedAppGadgets !== undefined
|
|
307
385
|
? { appGadgets: resolvedAppGadgets }
|
|
@@ -316,12 +394,19 @@ export function createUiGenerator(
|
|
|
316
394
|
enableRuntimeRender,
|
|
317
395
|
...(routeOverride !== undefined ? { routeOverride } : {}),
|
|
318
396
|
...(onRetry !== undefined ? { onRetry } : {}),
|
|
397
|
+
...(designMode !== undefined ? { designMode } : {}),
|
|
398
|
+
...(canvas !== undefined ? { canvas } : {}),
|
|
399
|
+
...(input.profile !== undefined ? { profile: input.profile } : {}),
|
|
400
|
+
// Fixture props reach the in-loop probe + visual round only (never the prompt, never identity).
|
|
401
|
+
...(input.fixtureProps !== undefined ? { fixtureProps: input.fixtureProps } : {}),
|
|
319
402
|
});
|
|
320
403
|
|
|
404
|
+
onGenerated?.(result);
|
|
405
|
+
|
|
321
406
|
const metadata: GenerationMetadata = {
|
|
322
407
|
provider: input.llm.provider,
|
|
323
408
|
generator: identity.slug,
|
|
324
|
-
model: input.llm
|
|
409
|
+
model: modelRefOfRoute(input.llm),
|
|
325
410
|
inputTokens: result.tokens.input,
|
|
326
411
|
outputTokens: result.tokens.output,
|
|
327
412
|
latencyMs: Date.now() - startedAt,
|
|
@@ -362,7 +447,7 @@ export function createUiGenerator(
|
|
|
362
447
|
metadata: {
|
|
363
448
|
provider: input.llm.provider,
|
|
364
449
|
generator: identity.slug,
|
|
365
|
-
model: input.llm
|
|
450
|
+
model: modelRefOfRoute(input.llm),
|
|
366
451
|
inputTokens: 0,
|
|
367
452
|
outputTokens: 0,
|
|
368
453
|
latencyMs: Date.now() - startedAt,
|
|
@@ -381,29 +466,29 @@ export function createUiGenerator(
|
|
|
381
466
|
}
|
|
382
467
|
|
|
383
468
|
function resolveIdentity(opts: CreateUiGeneratorOptions): {
|
|
384
|
-
slug:
|
|
469
|
+
slug: GeneratorId;
|
|
385
470
|
tier: GeneratorTier;
|
|
386
|
-
model:
|
|
471
|
+
model: ModelRef;
|
|
387
472
|
} {
|
|
388
473
|
const hasSlug = typeof opts.slug === 'string' && opts.slug.length > 0;
|
|
389
|
-
|
|
390
|
-
if (hasSlug && hasTierOrModel) {
|
|
474
|
+
if (hasSlug && opts.tier !== undefined) {
|
|
391
475
|
throw new Error(
|
|
392
|
-
'createUiGenerator: pass either { slug } or { tier
|
|
476
|
+
'createUiGenerator: pass either { slug } or { tier } — not both.',
|
|
393
477
|
);
|
|
394
478
|
}
|
|
479
|
+
const model = opts.model ?? DEFAULT_MODEL;
|
|
395
480
|
if (hasSlug) {
|
|
396
|
-
const
|
|
397
|
-
|
|
481
|
+
const slug = opts.slug!;
|
|
482
|
+
const parsed = parseGeneratorSlug(slug);
|
|
483
|
+
if (!parsed || !isGeneratorId(slug)) {
|
|
398
484
|
throw new Error(
|
|
399
|
-
`createUiGenerator: slug ${JSON.stringify(
|
|
485
|
+
`createUiGenerator: slug ${JSON.stringify(slug)} is not a valid ui-gen-<tier> identifier.`,
|
|
400
486
|
);
|
|
401
487
|
}
|
|
402
|
-
return { slug
|
|
488
|
+
return { slug, tier: parsed.tier, model };
|
|
403
489
|
}
|
|
404
490
|
const tier = opts.tier ?? DEFAULT_TIER;
|
|
405
|
-
const
|
|
406
|
-
const slug = formatGeneratorSlug({ tier, model });
|
|
491
|
+
const slug = formatGeneratorSlug({ tier });
|
|
407
492
|
if (!isValidGeneratorSlug(slug)) {
|
|
408
493
|
// Unreachable: formatGeneratorSlug throws on malformed components,
|
|
409
494
|
// so a slug it returns must parse. Defensive check kept to surface
|
|
@@ -442,7 +527,7 @@ function mapRendering(
|
|
|
442
527
|
|
|
443
528
|
function failWithoutMetadata(
|
|
444
529
|
input: UiGenerateInput,
|
|
445
|
-
generatorSlug:
|
|
530
|
+
generatorSlug: GeneratorId,
|
|
446
531
|
startedAt: number,
|
|
447
532
|
error: GenerationError,
|
|
448
533
|
): UiGenerateResult {
|
|
@@ -452,7 +537,7 @@ function failWithoutMetadata(
|
|
|
452
537
|
metadata: {
|
|
453
538
|
provider: input.llm.provider,
|
|
454
539
|
generator: generatorSlug,
|
|
455
|
-
model: input.llm
|
|
540
|
+
model: modelRefOfRoute(input.llm),
|
|
456
541
|
inputTokens: 0,
|
|
457
542
|
outputTokens: 0,
|
|
458
543
|
latencyMs: Date.now() - startedAt,
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// packages/ui-gen/src/design-mode.ts
|
|
2
|
+
//
|
|
3
|
+
// The ONE definition of the `designMode` option and the rendering-canvas
|
|
4
|
+
// vocabulary. Every triad surface (system prompt, boilerplate, tier-0
|
|
5
|
+
// gate, axis checks, evaluator criteria) AND the visual evaluator import
|
|
6
|
+
// these types from here — nothing re-declares the unions.
|
|
7
|
+
//
|
|
8
|
+
// - `constrained` (default) — today's triad: the component composes
|
|
9
|
+
// from the `@ggui-ai/design` primitives, prop enums and spacing
|
|
10
|
+
// scale, and tier-0 enforces the design vocabulary. Byte-identical
|
|
11
|
+
// to the pre-`designMode` behaviour (pinned by
|
|
12
|
+
// `design-mode.pin.test.ts`).
|
|
13
|
+
// - `free` — the design vocabulary is relaxed: raw HTML elements,
|
|
14
|
+
// inline styles and `<style>` blocks are allowed; the design package
|
|
15
|
+
// is importable but optional. Every HARD contract stays: the data
|
|
16
|
+
// contract via the boilerplate-typed wire hooks, the import
|
|
17
|
+
// allowlist + security bans, the host envelope, the budgets, the
|
|
18
|
+
// blueprint-reuse shape, and brand-bearing color via the closed
|
|
19
|
+
// `--ggui-color-*` token manifest (no literals, no fallbacks).
|
|
20
|
+
|
|
21
|
+
export const DESIGN_MODES = ["constrained", "free"] as const;
|
|
22
|
+
|
|
23
|
+
export type DesignMode = (typeof DESIGN_MODES)[number];
|
|
24
|
+
|
|
25
|
+
export const DEFAULT_DESIGN_MODE: DesignMode = "constrained";
|
|
26
|
+
|
|
27
|
+
// ─── Rendering canvas ───────────────────────────────────────────────────────
|
|
28
|
+
//
|
|
29
|
+
// ONE canvas vocabulary shared by the prompt input (the `free` prompt
|
|
30
|
+
// TELLS the agent its canvas; the `constrained` prompt keeps its
|
|
31
|
+
// shell/screen descriptors byte-for-byte) and the visual evaluator (one
|
|
32
|
+
// screenshot + judge score per class at `CANVAS_VIEWPORTS[class]`).
|
|
33
|
+
//
|
|
34
|
+
// A class is derived from DISPLAY MODE + width, never from width alone:
|
|
35
|
+
// an inline chat card is `xs-chat-card` whatever the host window
|
|
36
|
+
// measures (the bubble, not the window, bounds the component); a
|
|
37
|
+
// fullscreen-class surface follows its width through the three
|
|
38
|
+
// breakpoints. `canvasForViewportWidth` is monotonic in width per
|
|
39
|
+
// display mode (pinned by `design-mode.test.ts`).
|
|
40
|
+
|
|
41
|
+
export const CANVAS_CLASSES = [
|
|
42
|
+
"xs-chat-card",
|
|
43
|
+
"mobile-fullscreen-small",
|
|
44
|
+
"md",
|
|
45
|
+
"lg",
|
|
46
|
+
"xl",
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export type CanvasClass = (typeof CANVAS_CLASSES)[number];
|
|
50
|
+
|
|
51
|
+
export interface CanvasViewport {
|
|
52
|
+
readonly width: number;
|
|
53
|
+
readonly height: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The viewport (CSS px) each class is rendered + judged at. */
|
|
57
|
+
export const CANVAS_VIEWPORTS: Readonly<Record<CanvasClass, CanvasViewport>> = {
|
|
58
|
+
"xs-chat-card": { width: 400, height: 640 },
|
|
59
|
+
"mobile-fullscreen-small": { width: 390, height: 844 },
|
|
60
|
+
md: { width: 768, height: 1024 },
|
|
61
|
+
lg: { width: 1024, height: 768 },
|
|
62
|
+
xl: { width: 1440, height: 900 },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The two display modes the canvas derivation distinguishes. `inline`
|
|
67
|
+
* is the chat card (MCP Apps `inline` display mode / the `chat` shell);
|
|
68
|
+
* `fullscreen` is every surface the component owns edge to edge or as
|
|
69
|
+
* a panel (`fullscreen`, `pip`, the `spatial` and `partial` shells) —
|
|
70
|
+
* there the width decides the class.
|
|
71
|
+
*/
|
|
72
|
+
export type CanvasDisplayMode = "inline" | "fullscreen";
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Map a shell name (`chat` / `fullscreen` / `spatial` / `partial`) or an
|
|
76
|
+
* MCP Apps display mode (`inline` / `fullscreen` / `pip`) onto the
|
|
77
|
+
* canvas display mode. Only `chat` and `inline` are the inline card;
|
|
78
|
+
* everything else is width-governed.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* The display mode a JUDGE canvas stands for (ggui#1100): the inline chat
|
|
82
|
+
* card is `xs-chat-card`; every other canvas is a surface the component
|
|
83
|
+
* owns edge to edge, which the served runtime composes with `fit: 'fill'`
|
|
84
|
+
* — so the judge must too, or it scores a picture the user never sees.
|
|
85
|
+
*/
|
|
86
|
+
export function displayModeForCanvas(canvas: CanvasClass): CanvasDisplayMode {
|
|
87
|
+
return canvas === "xs-chat-card" ? "inline" : "fullscreen";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function canvasDisplayModeForShell(shell: string | undefined): CanvasDisplayMode {
|
|
91
|
+
return shell === "chat" || shell === "inline" ? "inline" : "fullscreen";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Fullscreen-class breakpoints (inclusive lower bounds), CSS px. */
|
|
95
|
+
export const CANVAS_BREAKPOINTS = { md: 600, lg: 1024, xl: 1440 } as const;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Derive the canvas class from display mode + container width.
|
|
99
|
+
*
|
|
100
|
+
* inline → `xs-chat-card` at every width
|
|
101
|
+
* fullscreen → width < 600 → `mobile-fullscreen-small`
|
|
102
|
+
* 600 – 1023 → `md`
|
|
103
|
+
* 1024 – 1439 → `lg`
|
|
104
|
+
* ≥ 1440 → `xl`
|
|
105
|
+
*/
|
|
106
|
+
export function canvasForViewportWidth(mode: CanvasDisplayMode, width: number): CanvasClass {
|
|
107
|
+
if (mode === "inline") return "xs-chat-card";
|
|
108
|
+
if (width < CANVAS_BREAKPOINTS.md) return "mobile-fullscreen-small";
|
|
109
|
+
if (width < CANVAS_BREAKPOINTS.lg) return "md";
|
|
110
|
+
if (width < CANVAS_BREAKPOINTS.xl) return "lg";
|
|
111
|
+
return "xl";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Map today's shell × screen descriptors onto a canvas class when no
|
|
116
|
+
* width is known. `chat` is always the inline card; `spatial` is a
|
|
117
|
+
* ~600px floating panel (`md`); `fullscreen` follows the screen. Unknown
|
|
118
|
+
* values fall back to `lg` (the widest common fullscreen), matching the
|
|
119
|
+
* skeleton's `fullscreen` / `universal` defaults.
|
|
120
|
+
*/
|
|
121
|
+
export function canvasForRendering(
|
|
122
|
+
shellType: string | undefined,
|
|
123
|
+
screen: string | undefined,
|
|
124
|
+
): CanvasClass {
|
|
125
|
+
const shell = shellType ?? "fullscreen";
|
|
126
|
+
if (canvasDisplayModeForShell(shell) === "inline") return "xs-chat-card";
|
|
127
|
+
if (shell === "spatial") return "md";
|
|
128
|
+
switch (screen ?? "universal") {
|
|
129
|
+
case "mobile":
|
|
130
|
+
return "mobile-fullscreen-small";
|
|
131
|
+
case "tablet":
|
|
132
|
+
return "md";
|
|
133
|
+
default:
|
|
134
|
+
return "lg";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface CanvasDescriptor {
|
|
139
|
+
/** Short human label. */
|
|
140
|
+
readonly label: string;
|
|
141
|
+
/** Inclusive width range the component must look right across, CSS px. */
|
|
142
|
+
readonly minWidthPx: number;
|
|
143
|
+
readonly maxWidthPx: number;
|
|
144
|
+
/** What the host draws around the component at this canvas. */
|
|
145
|
+
readonly host: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const CANVAS_DESCRIPTORS: Readonly<Record<CanvasClass, CanvasDescriptor>> = {
|
|
149
|
+
"xs-chat-card": {
|
|
150
|
+
label: "extra-small chat card",
|
|
151
|
+
minWidthPx: 320,
|
|
152
|
+
maxWidthPx: 480,
|
|
153
|
+
host: "an inline card inside a chat message bubble — the host draws the card border, shadow and rounded corners; the component fills the bubble width and takes its natural height",
|
|
154
|
+
},
|
|
155
|
+
"mobile-fullscreen-small": {
|
|
156
|
+
label: "small mobile fullscreen",
|
|
157
|
+
minWidthPx: 320,
|
|
158
|
+
maxWidthPx: 599,
|
|
159
|
+
host: "a phone-sized viewport the component owns edge to edge — single column, touch targets at least 44px, the component manages its own scroll",
|
|
160
|
+
},
|
|
161
|
+
md: {
|
|
162
|
+
label: "medium panel",
|
|
163
|
+
minWidthPx: 600,
|
|
164
|
+
maxWidthPx: 1023,
|
|
165
|
+
host: "a tablet viewport or a floating panel (spatial / picture-in-picture) — one or two columns, medium density",
|
|
166
|
+
},
|
|
167
|
+
lg: {
|
|
168
|
+
label: "large fullscreen",
|
|
169
|
+
minWidthPx: 1024,
|
|
170
|
+
maxWidthPx: 1439,
|
|
171
|
+
host: "a desktop viewport the component owns edge to edge — multi-column layouts, hover states and pointer interactions are available",
|
|
172
|
+
},
|
|
173
|
+
xl: {
|
|
174
|
+
label: "extra-large fullscreen",
|
|
175
|
+
minWidthPx: 1440,
|
|
176
|
+
maxWidthPx: 1920,
|
|
177
|
+
host: "a wide desktop viewport the component owns edge to edge — cap content width or use the extra room deliberately; never stretch a single column across the whole width",
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The explicit sentence the `free` prompt renders for a canvas — the
|
|
183
|
+
* agent is TOLD the canvas, it never guesses it.
|
|
184
|
+
*/
|
|
185
|
+
export function describeCanvas(canvas: CanvasClass): string {
|
|
186
|
+
const d = CANVAS_DESCRIPTORS[canvas];
|
|
187
|
+
return (
|
|
188
|
+
`This component renders on the \`${canvas}\` canvas (${d.label}): ` +
|
|
189
|
+
`${d.minWidthPx}–${d.maxWidthPx}px wide, ${d.host}. ` +
|
|
190
|
+
`Design for the whole range — the host may size the iframe anywhere inside it, and the host owns the height: size to your content, never to the viewport.`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -26,7 +26,7 @@ export const DEFAULT_DESIGN_SYSTEM_DOCS = `# Design System - Default Light Theme
|
|
|
26
26
|
- var(--ggui-color-primary-300) - #7dd3fc
|
|
27
27
|
- var(--ggui-color-primary-400) - #38bdf8
|
|
28
28
|
- var(--ggui-color-primary-500) - #0ea5e9
|
|
29
|
-
- var(--ggui-color-primary-600) - #0284c7 (main action
|
|
29
|
+
- var(--ggui-color-primary-600) - #0284c7 (main action FILL — buttons, borders, icon fills; accent TEXT takes the link role below)
|
|
30
30
|
- var(--ggui-color-primary-700) - #0369a1
|
|
31
31
|
- var(--ggui-color-primary-800) - #075985
|
|
32
32
|
- var(--ggui-color-primary-900) - #0c4a6e
|
|
@@ -36,20 +36,26 @@ These tokens adapt automatically to any theme (light, dark, branded). **ALWAYS u
|
|
|
36
36
|
|
|
37
37
|
| Token | CSS Variable | Default | Role |
|
|
38
38
|
|-------|-------------|---------|------|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
39
|
+
| ground | var(--ggui-color-ground) | The page / canvas behind everything |
|
|
40
|
+
| onGround | var(--ggui-color-onGround) | Text on the canvas |
|
|
41
|
+
| container | var(--ggui-color-container) | A card, a bubble, a panel — the unit of content |
|
|
42
|
+
| onContainer | var(--ggui-color-onContainer) | Body text on a card |
|
|
43
|
+
| elevated | var(--ggui-color-elevated) | Menus, popovers, modals, toasts — floats above a card |
|
|
44
|
+
| onElevated | var(--ggui-color-onElevated) | Text on an elevated surface |
|
|
45
|
+
| heroGround | var(--ggui-color-heroGround) | A hero / greeting panel's ground — brand-tinted on a light host, the ink pair on a dark one (Card surface="hero") |
|
|
46
|
+
| onHeroGround | var(--ggui-color-onHeroGround) | Text on the hero ground |
|
|
47
|
+
| sunken | var(--ggui-color-sunken) | Wells, inputs at rest, code blocks — recessed inside a card |
|
|
48
|
+
| onSunken | var(--ggui-color-onSunken) | Muted / secondary text |
|
|
49
|
+
| link | var(--ggui-color-link) | Accent TEXT — eyebrows, taglines, links, labels, inline arrows: the brand's readable ink, derived per mode to clear 4.5:1 on the container and the ground |
|
|
45
50
|
| outline | var(--ggui-color-outline) | Borders, dividers |
|
|
46
51
|
| outlineVariant | var(--ggui-color-outlineVariant) | Subtle borders |
|
|
47
52
|
|
|
48
53
|
**Usage pattern:**
|
|
49
|
-
- Page/section background → \`var(--ggui-color-
|
|
50
|
-
- Body text → \`var(--ggui-color-
|
|
51
|
-
- Card/panel background → \`var(--ggui-color-
|
|
52
|
-
- Secondary/muted text → \`var(--ggui-color-
|
|
54
|
+
- Page/section background → \`var(--ggui-color-ground)\`
|
|
55
|
+
- Body text → \`var(--ggui-color-onContainer)\`
|
|
56
|
+
- Card/panel background → \`var(--ggui-color-container)\`; recessed wells / inputs → \`var(--ggui-color-sunken)\`
|
|
57
|
+
- Secondary/muted text → \`var(--ggui-color-onSunken)\`
|
|
58
|
+
- Accent / branded text (eyebrow, tagline, link, label, inline arrow) → \`var(--ggui-color-link)\` or \`<Text tone="emphasized">\` — never a bare \`primary-<stop>\` as a text color
|
|
53
59
|
- Branded section/header → \`var(--ggui-color-container)\` bg + \`var(--ggui-color-onContainer)\` text
|
|
54
60
|
- Borders/dividers → \`var(--ggui-color-outline)\` or \`var(--ggui-color-outlineVariant)\`
|
|
55
61
|
|
|
@@ -66,11 +72,11 @@ Prefer the primitives' \`tone="success|warning|error|info"\` prop over hand-writ
|
|
|
66
72
|
- **NEVER** use rgba(), hsl(), or other CSS color functions with hardcoded values.
|
|
67
73
|
- **NEVER** use raw neutral-* or gray-* scale tokens (neutral-50, neutral-900, etc.) — these are internal to the theme and break in dark mode.
|
|
68
74
|
- **ALWAYS** use semantic tokens for text and backgrounds:
|
|
69
|
-
- Text: \`var(--ggui-color-
|
|
70
|
-
- Backgrounds: \`var(--ggui-color-
|
|
75
|
+
- Text: \`var(--ggui-color-onContainer)\` or \`var(--ggui-color-onSunken)\`
|
|
76
|
+
- Backgrounds: \`var(--ggui-color-container)\` or \`var(--ggui-color-sunken)\`
|
|
71
77
|
- Borders: \`var(--ggui-color-outline)\` or \`var(--ggui-color-outlineVariant)\`
|
|
72
78
|
- For branded elements use \`var(--ggui-color-primary-*)\` scale tokens — these ARE safe because primary adapts per theme.
|
|
73
|
-
- For card backgrounds use \`var(--ggui-color-
|
|
79
|
+
- For card backgrounds use \`var(--ggui-color-container)\` or \`var(--ggui-color-primary-50)\`
|
|
74
80
|
|
|
75
81
|
## Spacing
|
|
76
82
|
|
|
@@ -165,16 +171,17 @@ Use \`var(--ggui-spacing-N)\` for all padding, gap, and margin values. **Never u
|
|
|
165
171
|
\`\`\`
|
|
166
172
|
|
|
167
173
|
### Color usage guide
|
|
168
|
-
- **
|
|
174
|
+
- **Layering roles** (ground, container, elevated, sunken + their on* inks; outline): one kind of AREA each — use them for every background/text/border decision; they adapt to any theme
|
|
169
175
|
- **primary-50/100**: Section backgrounds, highlight strips, card headers
|
|
170
176
|
- **primary-200/300**: Borders, dividers, focus rings, input outlines
|
|
171
|
-
- **primary-500/600**:
|
|
177
|
+
- **primary-500/600**: Filled buttons, CTAs, badges, icon fills, active indicators — fills and borders, NOT a text color
|
|
178
|
+
- **link**: Accent TEXT — eyebrows, taglines, links, labels, inline arrows; the brand's readable ink per mode (a bare \`primary-300…600\` as a text color fails tier-0: \`universal.accent_text_ink\`)
|
|
172
179
|
- **primary-700/800/900**: Headings and text on light primary backgrounds
|
|
173
180
|
- **neutral-***: Only when you need a specific shade that semantic tokens don't cover
|
|
174
181
|
|
|
175
182
|
The primary palette is the app's brand — use it throughout (headers, accents, borders, interactive elements), not just on the submit button.
|
|
176
183
|
|
|
177
|
-
**Note:** Do NOT add fallback values to var() (e.g., var(--ggui-color-
|
|
184
|
+
**Note:** Do NOT add fallback values to var() (e.g., var(--ggui-color-container, #fafafa)). Just use var(--ggui-color-container) — the theme provides all values.
|
|
178
185
|
|
|
179
186
|
## Motion & Animation
|
|
180
187
|
|
package/src/effort.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// The `effort` reader table (ggui#1059, A2). The wire carries ONLY the level
|
|
2
|
+
// name (`generation.profile.effort`, protocol #1058); this table is the
|
|
3
|
+
// cold-gen READER that maps a name onto the harness's dials, versioned here
|
|
4
|
+
// so a level's meaning (and its price line) can move without a wire change.
|
|
5
|
+
//
|
|
6
|
+
// Boundaries, stated once:
|
|
7
|
+
// - `effort` renders NO prompt text — the prompt is byte-identical under any
|
|
8
|
+
// level (`profile-cross.pin.test.ts`). Dials only.
|
|
9
|
+
// - Absent ⇒ the deployment's own options, untouched. The table is applied
|
|
10
|
+
// only when a level is named.
|
|
11
|
+
// - `selfEvalPassThreshold` is the GENERATOR's in-loop visual
|
|
12
|
+
// self-evaluation threshold (`visualEvaluation.passThreshold`). A serving
|
|
13
|
+
// layer's own acceptance bar is a different number and does not move with
|
|
14
|
+
// `effort`.
|
|
15
|
+
// - `modelTier` / `judgeTier` are REQUESTS to the deployment's router
|
|
16
|
+
// (`fast | balanced | premium`), never a model id: a deployment with one
|
|
17
|
+
// key is never promoted by a profile. The harness does not act on them;
|
|
18
|
+
// the caller that owns model selection reads them from this table.
|
|
19
|
+
import type { AppGenerationProfileEffort, ModelTier } from "@ggui-ai/protocol";
|
|
20
|
+
|
|
21
|
+
/** Bump when a level's meaning changes; the price line beside it follows. */
|
|
22
|
+
export const EFFORT_TABLE_VERSION = 1;
|
|
23
|
+
|
|
24
|
+
export interface EffortDials {
|
|
25
|
+
/** Coding-loop turn cap (`maxAttempts`). */
|
|
26
|
+
readonly maxAttempts: number;
|
|
27
|
+
/** Evaluation rounds the harness may spend (`maxEvalRounds`). */
|
|
28
|
+
readonly maxEvalRounds: number;
|
|
29
|
+
/** The generator's in-loop visual self-evaluation threshold, 0–100 — not a serving layer's acceptance bar. */
|
|
30
|
+
readonly selfEvalPassThreshold: number;
|
|
31
|
+
/** Requested coding-model tier — a request to the deployment's router, never a model id. */
|
|
32
|
+
readonly modelTier: ModelTier;
|
|
33
|
+
/** Requested judge-model tier — same nature. */
|
|
34
|
+
readonly judgeTier: ModelTier;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const EFFORT_DIALS: Readonly<Record<AppGenerationProfileEffort, EffortDials>> = {
|
|
38
|
+
low: { maxAttempts: 5, maxEvalRounds: 1, selfEvalPassThreshold: 65, modelTier: "fast", judgeTier: "fast" },
|
|
39
|
+
medium: { maxAttempts: 8, maxEvalRounds: 2, selfEvalPassThreshold: 70, modelTier: "fast", judgeTier: "balanced" },
|
|
40
|
+
high: { maxAttempts: 8, maxEvalRounds: 3, selfEvalPassThreshold: 75, modelTier: "balanced", judgeTier: "balanced" },
|
|
41
|
+
xhigh: { maxAttempts: 10, maxEvalRounds: 3, selfEvalPassThreshold: 80, modelTier: "premium", judgeTier: "balanced" },
|
|
42
|
+
ultra: { maxAttempts: 12, maxEvalRounds: 4, selfEvalPassThreshold: 85, modelTier: "premium", judgeTier: "premium" },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The dials for a named level; `undefined` when no level is named (the deployment's options stand). */
|
|
46
|
+
export function effortDials(effort: AppGenerationProfileEffort | undefined): EffortDials | undefined {
|
|
47
|
+
return effort === undefined ? undefined : EFFORT_DIALS[effort];
|
|
48
|
+
}
|