feat(macos): add Canvas A2UI renderer
This commit is contained in:
26
vendor/a2ui/specification/0.8/eval/.gitignore
vendored
Normal file
26
vendor/a2ui/specification/0.8/eval/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
lib/
|
||||
output/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.genkit
|
||||
76
vendor/a2ui/specification/0.8/eval/GEMINI.md
vendored
Normal file
76
vendor/a2ui/specification/0.8/eval/GEMINI.md
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# A2UI Protocol Message Validation Logic
|
||||
|
||||
This document outlines the validation rules implemented in the `validateSchema` function. The purpose of this validator is to check for constraints that are not easily expressed in the JSON schema itself, such as conditional requirements and reference integrity.
|
||||
|
||||
An A2UI message is a JSON object that can have a `surfaceId` and one of the following properties, defining the message type: `beginRendering`, `surfaceUpdate`, `dataModelUpdate`, or `deleteSurface`.
|
||||
|
||||
## Common Properties
|
||||
|
||||
- **`surfaceId`**: An optional string that identifies the UI surface the message applies to.
|
||||
|
||||
## `BeginRendering` Message Rules
|
||||
|
||||
- **Required**: Must have a `root` property, which is the ID of the root component to render.
|
||||
|
||||
## `SurfaceUpdate` Message Rules
|
||||
|
||||
### 1. Component ID Integrity
|
||||
|
||||
- **Uniqueness**: All component `id`s within the `components` array must be unique.
|
||||
- **Reference Validity**: Any property that references a component ID (e.g., `child`, `children`, `entryPointChild`, `contentChild`) must point to an ID that actually exists in the `components` array.
|
||||
|
||||
### 2. Component-Specific Property Rules
|
||||
|
||||
For each component in the `components` array, the following rules apply:
|
||||
|
||||
- **General**:
|
||||
- A component must have an `id` and a `componentProperties` object.
|
||||
- The `componentProperties` object must contain exactly one key, which defines the component's type (e.g., "Heading", "Text").
|
||||
|
||||
- **Heading**:
|
||||
- **Required**: Must have a `text` property.
|
||||
- **Text**:
|
||||
- **Required**: Must have a `text` property.
|
||||
- **Image**:
|
||||
- **Required**: Must have a `url` property.
|
||||
- **Video**:
|
||||
- **Required**: Must have a `url` property.
|
||||
- **AudioPlayer**:
|
||||
- **Required**: Must have a `url` property.
|
||||
- **TextField**:
|
||||
- **Required**: Must have a `label` property.
|
||||
- **DateTimeInput**:
|
||||
- **Required**: Must have a `value` property.
|
||||
- **MultipleChoice**:
|
||||
- **Required**: Must have a `selections` property.
|
||||
- **Slider**:
|
||||
- **Required**: Must have a `value` property.
|
||||
- **Container Components** (`Row`, `Column`, `List`):
|
||||
- **Required**: Must have a `children` property.
|
||||
- The `children` object must contain _either_ `explicitList` _or_ `template`, but not both.
|
||||
- **Card**:
|
||||
- **Required**: Must have a `child` property.
|
||||
- **Tabs**:
|
||||
- **Required**: Must have a `tabItems` property, which must be an array.
|
||||
- Each item in `tabItems` must have a `title` and a `child`.
|
||||
- **Modal**:
|
||||
- **Required**: Must have both `entryPointChild` and `contentChild` properties.
|
||||
- **Button**:
|
||||
- **Required**: Must have `label` and `action` properties.
|
||||
- **CheckBox**:
|
||||
- **Required**: Must have `label` and `value` properties.
|
||||
- **Divider**:
|
||||
- No required properties.
|
||||
|
||||
## `DataModelUpdate` Message Rules
|
||||
|
||||
- **Required**: A `DataModelUpdate` message must have a `contents` property.
|
||||
- The `path` property is optional.
|
||||
- If `path` is not present, the `contents` object will replace the entire data model.
|
||||
- If `path` is present, the `contents` will be set at that location in the data model.
|
||||
- No other properties besides `path` and `contents` are allowed.
|
||||
|
||||
## `DeleteSurface` Message Rules
|
||||
|
||||
- **Required**: Must have a `delete` property set to `true`.
|
||||
- No other properties are allowed.
|
||||
61
vendor/a2ui/specification/0.8/eval/README.md
vendored
Normal file
61
vendor/a2ui/specification/0.8/eval/README.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# Genkit Eval Framework for UI generation
|
||||
|
||||
This is for evaluating A2UI (v0.8) against various LLMs.
|
||||
|
||||
## Setup
|
||||
|
||||
To use the models, you need to set the following environment variables with your API keys:
|
||||
|
||||
- `GEMINI_API_KEY`
|
||||
- `OPENAI_API_KEY`
|
||||
- `ANTHROPIC_API_KEY`
|
||||
|
||||
You can set these in a `.env` file in the root of the project, or in your shell's configuration file (e.g., `.bashrc`, `.zshrc`).
|
||||
|
||||
You also need to install dependencies before running:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Running all evals (warning: can use *lots* of model quota)
|
||||
|
||||
To run the flow, use the following command:
|
||||
|
||||
```bash
|
||||
pnpm run evalAll
|
||||
```
|
||||
|
||||
## Running a Single Test
|
||||
|
||||
You can run the script for a single model and data point by using the `--model` and `--prompt` command-line flags. This is useful for quick tests and debugging.
|
||||
|
||||
### Syntax
|
||||
|
||||
```bash
|
||||
pnpm run eval -- --model='<model_name>' --prompt=<prompt_name>
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
To run the test with the `gpt-5-mini (reasoning: minimal)` model and the `generateDogUIs` prompt, use the following command:
|
||||
|
||||
```bash
|
||||
pnpm run eval -- --model='gpt-5-mini (reasoning: minimal)' --prompt=generateDogUIs
|
||||
```
|
||||
|
||||
## Controlling Output
|
||||
|
||||
By default, the script only prints the summary table and any errors that occur during generation. To see the full JSON output for each successful generation, use the `--verbose` flag.
|
||||
|
||||
To keep the input and output for each run in separate files, specify the `--keep=<output_dir>` flag, which will create a directory hierarchy with the input and output for each LLM call in separate files.
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
pnpm run evalAll -- --verbose
|
||||
```
|
||||
|
||||
```bash
|
||||
pnpm run evalAll -- --keep=output
|
||||
```
|
||||
24
vendor/a2ui/specification/0.8/eval/genkit.conf.js
vendored
Normal file
24
vendor/a2ui/specification/0.8/eval/genkit.conf.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { googleAI } from "@genkit-ai/google-genai";
|
||||
import { configure } from "genkit";
|
||||
|
||||
export default configure({
|
||||
plugins: [googleAI()],
|
||||
logLevel: "debug",
|
||||
enableTracingAndMetrics: true,
|
||||
});
|
||||
36
vendor/a2ui/specification/0.8/eval/package.json
vendored
Normal file
36
vendor/a2ui/specification/0.8/eval/package.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "a2ui_genkit_eval",
|
||||
"version": "1.0.0",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc",
|
||||
"eval": "dotenv -- tsx src/index.ts",
|
||||
"evalAll": "pnpm run eval",
|
||||
"evalGemini": "pnpm run eval -- --model=gemini-2.5-flash --prompt=travelItinerary",
|
||||
"evalGpt": "pnpm run eval -- --model=gpt-5-mini --prompt=travelItinerary",
|
||||
"evalClaude": "pnpm run eval -- --model=claude-4-sonnet --prompt=travelItinerary",
|
||||
"start": "genkit start",
|
||||
"genkit:dev": "genkit start -- tsx --watch src/dev.ts",
|
||||
"format": "prettier --write src/**/*.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@types/yargs": "^17.0.34",
|
||||
"dotenv-cli": "^10.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "^5.9.2",
|
||||
"yargs": "^18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@genkit-ai/compat-oai": "^1.19.2",
|
||||
"@genkit-ai/google-genai": "^1.19.3",
|
||||
"@genkit-ai/vertexai": "^1.19.3",
|
||||
"genkit": "^1.19.2",
|
||||
"genkitx-anthropic": "^0.25.0"
|
||||
}
|
||||
}
|
||||
4885
vendor/a2ui/specification/0.8/eval/pnpm-lock.yaml
generated
vendored
Normal file
4885
vendor/a2ui/specification/0.8/eval/pnpm-lock.yaml
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18
vendor/a2ui/specification/0.8/eval/pnpm-workspace.yaml
vendored
Normal file
18
vendor/a2ui/specification/0.8/eval/pnpm-workspace.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copyright 2025 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- '@firebase/util'
|
||||
- esbuild
|
||||
- protobufjs
|
||||
65
vendor/a2ui/specification/0.8/eval/src/basic_schema_matcher.ts
vendored
Normal file
65
vendor/a2ui/specification/0.8/eval/src/basic_schema_matcher.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { SchemaMatcher, ValidationResult } from "./schema_matcher";
|
||||
|
||||
export class BasicSchemaMatcher extends SchemaMatcher {
|
||||
constructor(
|
||||
public propertyPath: string,
|
||||
public propertyValue?: any,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
validate(schema: any): ValidationResult {
|
||||
if (!schema) {
|
||||
const result: ValidationResult = {
|
||||
success: false,
|
||||
error: "Schema is undefined.",
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
const pathParts = this.propertyPath.split(".");
|
||||
let actualValue = schema;
|
||||
for (const part of pathParts) {
|
||||
if (actualValue && typeof actualValue === "object") {
|
||||
actualValue = actualValue[part];
|
||||
} else {
|
||||
actualValue = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (actualValue === undefined) {
|
||||
const error = `Failed to find property '${this.propertyPath}'.`;
|
||||
return { success: false, error };
|
||||
}
|
||||
|
||||
if (this.propertyValue !== undefined) {
|
||||
if (JSON.stringify(actualValue) !== JSON.stringify(this.propertyValue)) {
|
||||
const error = `Property '${
|
||||
this.propertyPath
|
||||
}' has value '${JSON.stringify(
|
||||
actualValue,
|
||||
)}', but expected '${JSON.stringify(this.propertyValue)}'.`;
|
||||
return { success: false, error };
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
17
vendor/a2ui/specification/0.8/eval/src/dev.ts
vendored
Normal file
17
vendor/a2ui/specification/0.8/eval/src/dev.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import "./flows";
|
||||
71
vendor/a2ui/specification/0.8/eval/src/flows.ts
vendored
Normal file
71
vendor/a2ui/specification/0.8/eval/src/flows.ts
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { googleAI } from "@genkit-ai/google-genai";
|
||||
import { genkit, z } from "genkit";
|
||||
import { openAI } from "@genkit-ai/compat-oai/openai";
|
||||
import { anthropic } from "genkitx-anthropic";
|
||||
|
||||
const plugins = [];
|
||||
|
||||
if (process.env.GEMINI_API_KEY) {
|
||||
console.log("Initializing Google AI plugin...");
|
||||
plugins.push(
|
||||
googleAI({
|
||||
apiKey: process.env.GEMINI_API_KEY!,
|
||||
experimental_debugTraces: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (process.env.OPENAI_API_KEY) {
|
||||
console.log("Initializing OpenAI plugin...");
|
||||
plugins.push(openAI());
|
||||
}
|
||||
if (process.env.ANTHROPIC_API_KEY) {
|
||||
console.log("Initializing Anthropic plugin...");
|
||||
plugins.push(anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }));
|
||||
}
|
||||
|
||||
export const ai = genkit({
|
||||
plugins,
|
||||
});
|
||||
|
||||
// Define a UI component generator flow
|
||||
export const componentGeneratorFlow = ai.defineFlow(
|
||||
{
|
||||
name: "componentGeneratorFlow",
|
||||
inputSchema: z.object({
|
||||
prompt: z.string(),
|
||||
model: z.any(),
|
||||
config: z.any().optional(),
|
||||
schema: z.any(),
|
||||
}),
|
||||
outputSchema: z.any(),
|
||||
},
|
||||
async ({ prompt, model, config, schema }) => {
|
||||
// Generate structured component data using the schema from the file
|
||||
const { output } = await ai.generate({
|
||||
prompt,
|
||||
model,
|
||||
output: { contentType: "application/json", jsonSchema: schema },
|
||||
config,
|
||||
});
|
||||
|
||||
if (!output) throw new Error("Failed to generate component");
|
||||
|
||||
return output;
|
||||
},
|
||||
);
|
||||
363
vendor/a2ui/specification/0.8/eval/src/index.ts
vendored
Normal file
363
vendor/a2ui/specification/0.8/eval/src/index.ts
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { componentGeneratorFlow, ai } from "./flows";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { modelsToTest } from "./models";
|
||||
import { prompts, TestPrompt } from "./prompts";
|
||||
import { validateSchema } from "./validator";
|
||||
|
||||
interface InferenceResult {
|
||||
modelName: string;
|
||||
prompt: TestPrompt;
|
||||
component: any;
|
||||
error: any;
|
||||
latency: number;
|
||||
validationResults: string[];
|
||||
runNumber: number;
|
||||
}
|
||||
|
||||
function generateSummary(
|
||||
resultsByModel: Record<string, InferenceResult[]>,
|
||||
results: InferenceResult[],
|
||||
): string {
|
||||
const promptNameWidth = 40;
|
||||
const latencyWidth = 20;
|
||||
const failedRunsWidth = 15;
|
||||
const toolErrorRunsWidth = 20;
|
||||
|
||||
let summary = "# Evaluation Summary";
|
||||
for (const modelName in resultsByModel) {
|
||||
summary += `\n\n## Model: ${modelName}\n\n`;
|
||||
const header = `| ${"Prompt Name".padEnd(
|
||||
promptNameWidth,
|
||||
)} | ${"Avg Latency (ms)".padEnd(latencyWidth)} | ${"Failed Runs".padEnd(
|
||||
failedRunsWidth,
|
||||
)} | ${"Tool Error Runs".padEnd(toolErrorRunsWidth)} |`;
|
||||
const divider = `|${"-".repeat(promptNameWidth + 2)}|${"-".repeat(
|
||||
latencyWidth + 2,
|
||||
)}|${"-".repeat(failedRunsWidth + 2)}|${"-".repeat(
|
||||
toolErrorRunsWidth + 2,
|
||||
)}|`;
|
||||
summary += header;
|
||||
summary += `\n${divider}`;
|
||||
|
||||
const promptsInModel = resultsByModel[modelName].reduce(
|
||||
(acc, result) => {
|
||||
if (!acc[result.prompt.name]) {
|
||||
acc[result.prompt.name] = [];
|
||||
}
|
||||
acc[result.prompt.name].push(result);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, InferenceResult[]>,
|
||||
);
|
||||
|
||||
let totalModelFailedRuns = 0;
|
||||
|
||||
for (const promptName in promptsInModel) {
|
||||
const runs = promptsInModel[promptName];
|
||||
const totalRuns = runs.length;
|
||||
const errorRuns = runs.filter((r) => r.error).length;
|
||||
const failedRuns = runs.filter(
|
||||
(r) => r.error || r.validationResults.length > 0,
|
||||
).length;
|
||||
const totalLatency = runs.reduce((acc, r) => acc + r.latency, 0);
|
||||
const avgLatency = (totalLatency / totalRuns).toFixed(0);
|
||||
|
||||
totalModelFailedRuns += failedRuns;
|
||||
|
||||
const failedRunsStr =
|
||||
failedRuns > 0 ? `${failedRuns} / ${totalRuns}` : "";
|
||||
const errorRunsStr = errorRuns > 0 ? `${errorRuns} / ${totalRuns}` : "";
|
||||
|
||||
summary += `\n| ${promptName.padEnd(
|
||||
promptNameWidth,
|
||||
)} | ${avgLatency.padEnd(latencyWidth)} | ${failedRunsStr.padEnd(
|
||||
failedRunsWidth,
|
||||
)} | ${errorRunsStr.padEnd(toolErrorRunsWidth)} |`;
|
||||
}
|
||||
|
||||
const totalRunsForModel = resultsByModel[modelName].length;
|
||||
summary += `\n\n**Total failed runs:** ${totalModelFailedRuns} / ${totalRunsForModel}`;
|
||||
}
|
||||
|
||||
summary += "\n\n---\n\n## Overall Summary\n";
|
||||
const totalRuns = results.length;
|
||||
const totalToolErrorRuns = results.filter((r) => r.error).length;
|
||||
const totalRunsWithAnyFailure = results.filter(
|
||||
(r) => r.error || r.validationResults.length > 0,
|
||||
).length;
|
||||
const modelsWithFailures = [
|
||||
...new Set(
|
||||
results
|
||||
.filter((r) => r.error || r.validationResults.length > 0)
|
||||
.map((r) => r.modelName),
|
||||
),
|
||||
].join(", ");
|
||||
|
||||
summary += `\n- **Number of tool error runs:** ${totalToolErrorRuns} / ${totalRuns}`;
|
||||
summary += `\n- **Number of runs with any failure (tool error or validation):** ${totalRunsWithAnyFailure} / ${totalRuns}`;
|
||||
const latencies = results.map((r) => r.latency).sort((a, b) => a - b);
|
||||
const totalLatency = latencies.reduce((acc, l) => acc + l, 0);
|
||||
const meanLatency = (totalLatency / totalRuns).toFixed(0);
|
||||
let medianLatency = 0;
|
||||
if (latencies.length > 0) {
|
||||
const mid = Math.floor(latencies.length / 2);
|
||||
if (latencies.length % 2 === 0) {
|
||||
medianLatency = (latencies[mid - 1] + latencies[mid]) / 2;
|
||||
} else {
|
||||
medianLatency = latencies[mid];
|
||||
}
|
||||
}
|
||||
|
||||
summary += `\n- **Mean Latency:** ${meanLatency} ms`;
|
||||
summary += `\n- **Median Latency:** ${medianLatency} ms`;
|
||||
if (modelsWithFailures) {
|
||||
summary += `\n- **Models with at least one failure:** ${modelsWithFailures}`;
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
|
||||
// Run the flow
|
||||
async function main() {
|
||||
const argv = await yargs(hideBin(process.argv))
|
||||
.option("verbose", {
|
||||
alias: "v",
|
||||
type: "boolean",
|
||||
description: "Run with verbose logging",
|
||||
default: false,
|
||||
})
|
||||
.option("keep", {
|
||||
type: "string",
|
||||
description:
|
||||
"Directory to keep output files. If no path is provided, a temporary directory will be created.",
|
||||
coerce: (arg) => (arg === undefined ? true : arg),
|
||||
})
|
||||
.option("runs-per-prompt", {
|
||||
type: "number",
|
||||
description: "Number of times to run each prompt",
|
||||
default: 1,
|
||||
})
|
||||
.option("model", {
|
||||
type: "string",
|
||||
array: true,
|
||||
description: "Filter models by exact name",
|
||||
default: [],
|
||||
choices: modelsToTest.map((m) => m.name),
|
||||
})
|
||||
.option("prompt", {
|
||||
type: "string",
|
||||
description: "Filter prompts by name prefix",
|
||||
})
|
||||
.help()
|
||||
.alias("h", "help").argv;
|
||||
|
||||
const verbose = argv.verbose;
|
||||
const keep = argv.keep;
|
||||
let outputDir: string | null = null;
|
||||
|
||||
if (keep) {
|
||||
if (typeof keep === "string") {
|
||||
outputDir = keep;
|
||||
} else {
|
||||
outputDir = fs.mkdtempSync(path.join(process.cwd(), "a2ui-eval-"));
|
||||
}
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
console.log(`Keeping output in: ${outputDir}`);
|
||||
}
|
||||
|
||||
const runsPerPrompt = argv["runs-per-prompt"];
|
||||
|
||||
let filteredModels = modelsToTest;
|
||||
if (argv.model && argv.model.length > 0) {
|
||||
const modelNames = argv.model as string[];
|
||||
filteredModels = modelsToTest.filter((m) => modelNames.includes(m.name));
|
||||
if (filteredModels.length === 0) {
|
||||
console.error(`No models found matching: ${modelNames.join(", ")}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let filteredPrompts = prompts;
|
||||
if (argv.prompt) {
|
||||
filteredPrompts = prompts.filter((p) =>
|
||||
p.name.startsWith(argv.prompt as string)
|
||||
);
|
||||
if (filteredPrompts.length === 0) {
|
||||
console.error(`No prompt found with prefix "${argv.prompt}".`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const generationPromises: Promise<InferenceResult>[] = [];
|
||||
|
||||
for (const prompt of filteredPrompts) {
|
||||
const schemaString = fs.readFileSync(
|
||||
path.join(__dirname, prompt.schemaPath),
|
||||
"utf-8"
|
||||
);
|
||||
const schema = JSON.parse(schemaString);
|
||||
for (const modelConfig of filteredModels) {
|
||||
const modelDirName = modelConfig.name.replace(/[\/:]/g, "_");
|
||||
const modelOutputDir = outputDir
|
||||
? path.join(outputDir, modelDirName)
|
||||
: null;
|
||||
if (modelOutputDir && !fs.existsSync(modelOutputDir)) {
|
||||
fs.mkdirSync(modelOutputDir, { recursive: true });
|
||||
}
|
||||
for (let i = 1; i <= runsPerPrompt; i++) {
|
||||
console.log(
|
||||
`Queueing generation for model: ${modelConfig.name}, prompt: ${prompt.name} (run ${i})`
|
||||
);
|
||||
const startTime = Date.now();
|
||||
generationPromises.push(
|
||||
componentGeneratorFlow({
|
||||
prompt: prompt.promptText,
|
||||
model: modelConfig.model,
|
||||
config: modelConfig.config,
|
||||
schema,
|
||||
})
|
||||
.then((component) => {
|
||||
if (modelOutputDir) {
|
||||
const inputPath = path.join(
|
||||
modelOutputDir,
|
||||
`${prompt.name}.input.txt`
|
||||
);
|
||||
fs.writeFileSync(inputPath, prompt.promptText);
|
||||
|
||||
const outputPath = path.join(
|
||||
modelOutputDir,
|
||||
`${prompt.name}.output.json`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
outputPath,
|
||||
JSON.stringify(component, null, 2)
|
||||
);
|
||||
}
|
||||
const validationResults = validateSchema(
|
||||
component,
|
||||
prompt.schemaPath,
|
||||
prompt.matchers
|
||||
);
|
||||
return {
|
||||
modelName: modelConfig.name,
|
||||
prompt,
|
||||
component,
|
||||
error: null,
|
||||
latency: Date.now() - startTime,
|
||||
validationResults,
|
||||
runNumber: i,
|
||||
};
|
||||
})
|
||||
.catch((error) => {
|
||||
if (modelOutputDir) {
|
||||
const inputPath = path.join(
|
||||
modelOutputDir,
|
||||
`${prompt.name}.input.txt`
|
||||
);
|
||||
fs.writeFileSync(inputPath, prompt.promptText);
|
||||
|
||||
const errorPath = path.join(
|
||||
modelOutputDir,
|
||||
`${prompt.name}.error.json`
|
||||
);
|
||||
const errorOutput = {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
...error,
|
||||
};
|
||||
fs.writeFileSync(
|
||||
errorPath,
|
||||
JSON.stringify(errorOutput, null, 2)
|
||||
);
|
||||
}
|
||||
return {
|
||||
modelName: modelConfig.name,
|
||||
prompt,
|
||||
component: null,
|
||||
error,
|
||||
latency: Date.now() - startTime,
|
||||
validationResults: [],
|
||||
runNumber: i,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const results = await Promise.all(generationPromises);
|
||||
|
||||
const resultsByModel: Record<string, InferenceResult[]> = {};
|
||||
|
||||
for (const result of results) {
|
||||
if (!resultsByModel[result.modelName]) {
|
||||
resultsByModel[result.modelName] = [];
|
||||
}
|
||||
resultsByModel[result.modelName].push(result);
|
||||
}
|
||||
|
||||
console.log("\n--- Generation Results ---");
|
||||
for (const modelName in resultsByModel) {
|
||||
for (const result of resultsByModel[modelName]) {
|
||||
const hasError = !!result.error;
|
||||
const hasValidationFailures = result.validationResults.length > 0;
|
||||
const hasComponent = !!result.component;
|
||||
|
||||
if (hasError || hasValidationFailures || (verbose && hasComponent)) {
|
||||
console.log(`\n----------------------------------------`);
|
||||
console.log(`Model: ${modelName}`);
|
||||
console.log(`----------------------------------------`);
|
||||
console.log(`\nQuery: ${result.prompt.name} (run ${result.runNumber})`);
|
||||
|
||||
if (hasError) {
|
||||
console.error("Error generating component:", result.error);
|
||||
} else if (hasComponent) {
|
||||
if (hasValidationFailures) {
|
||||
console.log("Validation Failures:");
|
||||
result.validationResults.forEach((failure) =>
|
||||
console.log(`- ${failure}`)
|
||||
);
|
||||
}
|
||||
if (verbose) {
|
||||
if (hasValidationFailures) {
|
||||
console.log("Generated schema:");
|
||||
console.log(JSON.stringify(result.component, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const summary = generateSummary(resultsByModel, results);
|
||||
console.log(summary);
|
||||
if (outputDir) {
|
||||
const summaryPath = path.join(outputDir, "summary.md");
|
||||
fs.writeFileSync(summaryPath, summary);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
50
vendor/a2ui/specification/0.8/eval/src/message_type_matcher.ts
vendored
Normal file
50
vendor/a2ui/specification/0.8/eval/src/message_type_matcher.ts
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { SchemaMatcher, ValidationResult } from "./schema_matcher";
|
||||
|
||||
/**
|
||||
* A concrete matcher that verifies the top-level message type.
|
||||
*/
|
||||
export class MessageTypeMatcher extends SchemaMatcher {
|
||||
constructor(private messageType: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
validate(response: object): ValidationResult {
|
||||
if (!response || typeof response !== "object") {
|
||||
return {
|
||||
success: false,
|
||||
error: "Response is not a valid object.",
|
||||
};
|
||||
}
|
||||
const keys = Object.keys(response);
|
||||
if (keys.length === 1 && keys[0] === this.messageType) {
|
||||
return { success: true };
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: `Expected top-level message type to be '${
|
||||
this.messageType
|
||||
}', but found '${keys.join(", ")}'`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return `Expected top-level message type to be '${this.messageType}'`;
|
||||
}
|
||||
}
|
||||
68
vendor/a2ui/specification/0.8/eval/src/models.ts
vendored
Normal file
68
vendor/a2ui/specification/0.8/eval/src/models.ts
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { googleAI } from "@genkit-ai/google-genai";
|
||||
import { openAI } from "@genkit-ai/compat-oai/openai";
|
||||
import { claude35Haiku, claude4Sonnet } from "genkitx-anthropic";
|
||||
|
||||
export interface ModelConfiguration {
|
||||
model: any;
|
||||
name: string;
|
||||
config?: any;
|
||||
}
|
||||
|
||||
export const modelsToTest: ModelConfiguration[] = [
|
||||
{
|
||||
model: openAI.model("gpt-5"),
|
||||
name: "gpt-5",
|
||||
config: { reasoning_effort: "minimal" },
|
||||
},
|
||||
{
|
||||
model: openAI.model("gpt-5-mini"),
|
||||
name: "gpt-5-mini",
|
||||
config: { reasoning_effort: "minimal" },
|
||||
},
|
||||
{
|
||||
model: openAI.model("gpt-4.1"),
|
||||
name: "gpt-4.1",
|
||||
config: {},
|
||||
},
|
||||
{
|
||||
model: googleAI.model("gemini-2.5-pro"),
|
||||
name: "gemini-2.5-pro-thinking",
|
||||
config: { thinkingConfig: { thinkingBudget: 1000 } },
|
||||
},
|
||||
{
|
||||
model: googleAI.model("gemini-2.5-flash"),
|
||||
name: "gemini-2.5-flash",
|
||||
config: { thinkingConfig: { thinkingBudget: 0 } },
|
||||
},
|
||||
{
|
||||
model: googleAI.model("gemini-2.5-flash-lite"),
|
||||
name: "gemini-2.5-flash-lite",
|
||||
config: { thinkingConfig: { thinkingBudget: 0 } },
|
||||
},
|
||||
{
|
||||
model: claude4Sonnet,
|
||||
name: "claude-4-sonnet",
|
||||
config: {},
|
||||
},
|
||||
{
|
||||
model: claude35Haiku,
|
||||
name: "claude-35-haiku",
|
||||
config: {},
|
||||
},
|
||||
];
|
||||
493
vendor/a2ui/specification/0.8/eval/src/prompts.ts
vendored
Normal file
493
vendor/a2ui/specification/0.8/eval/src/prompts.ts
vendored
Normal file
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { BasicSchemaMatcher } from "./basic_schema_matcher";
|
||||
import { MessageTypeMatcher } from "./message_type_matcher";
|
||||
import { SchemaMatcher } from "./schema_matcher";
|
||||
import { SurfaceUpdateSchemaMatcher } from "./surface_update_schema_matcher";
|
||||
|
||||
export interface TestPrompt {
|
||||
name: string;
|
||||
description: string;
|
||||
schemaPath: string;
|
||||
promptText: string;
|
||||
matchers: SchemaMatcher[];
|
||||
}
|
||||
|
||||
const schemaPath = "../../json/server_to_client_with_standard_catalog.json";
|
||||
|
||||
export const prompts: TestPrompt[] = [
|
||||
{
|
||||
name: "deleteSurface",
|
||||
description: "A DeleteSurface message to remove a UI surface.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message containing a deleteSurface for the surface 'dashboard-surface-1'.`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("deleteSurface"),
|
||||
new BasicSchemaMatcher("deleteSurface"),
|
||||
new BasicSchemaMatcher("deleteSurface.surfaceId", "dashboard-surface-1"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "dogBreedGenerator",
|
||||
description:
|
||||
"A prompt to generate a UI for a dog breed information and generator tool.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message containing a surfaceUpdate to describe the following UI:
|
||||
|
||||
A root node has already been created with ID "root".
|
||||
|
||||
A vertical list with:
|
||||
Dog breed information
|
||||
Dog generator
|
||||
|
||||
The dog breed information is a card, which contains a title “Famous Dog breeds”, a header image, and a carousel of different dog breeds. The carousel information should be in the data model at /carousel.
|
||||
|
||||
The dog generator is another card which is a form that generates a fictional dog breed with a description
|
||||
- Title
|
||||
- Description text explaining what it is
|
||||
- Dog breed name (text input)
|
||||
- Number of legs (number input)
|
||||
- Skills (checkboxes)
|
||||
- Button called “Generate” which takes the data above and generates a new dog description
|
||||
- A divider
|
||||
- A section which shows the generated content
|
||||
`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"TextField",
|
||||
"label",
|
||||
"Dog breed name",
|
||||
true
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"TextField",
|
||||
"label",
|
||||
"Number of legs",
|
||||
true
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Generate"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "loginForm",
|
||||
description:
|
||||
'A simple login form with username, password, a "remember me" checkbox, and a submit button.',
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message containing a surfaceUpdate for a login form. It should have a "Login" heading, two text fields for username and password (bound to /login/username and /login/password), a checkbox for "Remember Me" (bound to /login/rememberMe), and a "Sign In" button. The button should trigger a 'login' action, passing the username, password, and rememberMe status in the dynamicContext.`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Login"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "username", true),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "password", true),
|
||||
new SurfaceUpdateSchemaMatcher("CheckBox", "label", "Remember Me"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Sign In"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "productGallery",
|
||||
description: "A gallery of products using a list with a template.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message containing a surfaceUpdate for a product gallery. It should display a list of products from the data model at '/products'. Use a template for the list items. Each item should be a Card containing an Image (from '/products/item/imageUrl'), a Text component for the product name (from '/products/item/name'), and a Button labeled "Add to Cart". The button's action should be 'addToCart' and include a staticContext with the product ID, for example, 'productId': 'product123'. You should create a template component and then a list that uses it.`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("Card"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher("Text"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Add to Cart"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "productGalleryData",
|
||||
description:
|
||||
"A DataModelUpdate message to populate the product gallery data.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message containing a dataModelUpdate to populate the data model for the product gallery. The update should target the path '/products' and include at least two products. Each product in the map should have keys 'id', 'name', and 'imageUrl'. For example:
|
||||
{
|
||||
"key": "product1",
|
||||
"valueMap": [
|
||||
{ "key": "id", "valueString": "product1" },
|
||||
{ "key": "name", "valueString": "Awesome Gadget" },
|
||||
{ "key": "imageUrl", "valueString": "https://example.com/gadget.jpg" }
|
||||
]
|
||||
}`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("dataModelUpdate"),
|
||||
new BasicSchemaMatcher("dataModelUpdate.path", "/products"),
|
||||
new BasicSchemaMatcher("dataModelUpdate.contents.0.key"), // Check that the first product key exists
|
||||
new BasicSchemaMatcher("dataModelUpdate.contents.0.valueMap"), // Check that valueMap exists
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "settingsPage",
|
||||
description: "A settings page with tabs and a modal dialog.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message containing a surfaceUpdate for a user settings page. Use a Tabs component with two tabs: "Profile" and "Notifications". The "Profile" tab should contain a simple column with a text field for the user's name. The "Notifications" tab should contain a checkbox for "Enable email notifications". Also, include a Modal component. The modal's entry point should be a button labeled "Delete Account", and its content should be a column with a confirmation text and two buttons: "Confirm Deletion" and "Cancel".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "name", true),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"CheckBox",
|
||||
"label",
|
||||
"Enable email notifications"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Delete Account"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Confirm Deletion"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Cancel"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "dataModelUpdate",
|
||||
description: "A DataModelUpdate message to update user data.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a 'dataModelUpdate' property. This is used to update the client's data model. The scenario is that a user has just logged in, and we need to populate their profile information. Create a single data model update message to set '/user/name' to "John Doe" and '/user/email' to "john.doe@example.com".`,
|
||||
matchers: [new MessageTypeMatcher("dataModelUpdate")],
|
||||
},
|
||||
{
|
||||
name: "uiRoot",
|
||||
description: "A UIRoot message to set the initial UI and data roots.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a 'beginRendering' property. This message tells the client where to start rendering the UI. Set the UI root to a component with ID "mainLayout".`,
|
||||
matchers: [new MessageTypeMatcher("beginRendering")],
|
||||
},
|
||||
{
|
||||
name: "animalKingdomExplorer",
|
||||
description: "A simple, explicit UI to display a hierarchy of animals.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a simplified UI explorer for the Animal Kingdom.
|
||||
|
||||
The UI must have a main 'Heading' with the text "Simple Animal Explorer".
|
||||
|
||||
Below the heading, create a 'Tabs' component with exactly three tabs: "Mammals", "Birds", and "Reptiles".
|
||||
|
||||
Each tab's content should be a 'Column'. The first item in each column must be a 'TextField' with the label "Search...". Below the search field, display the hierarchy for that tab using nested 'Card' components.
|
||||
|
||||
The exact hierarchy to create is as follows:
|
||||
|
||||
**1. "Mammals" Tab:**
|
||||
- A 'Card' for the Class "Mammalia".
|
||||
- Inside the "Mammalia" card, create two 'Card's for the following Orders:
|
||||
- A 'Card' for the Order "Carnivora". Inside this, create 'Card's for these three species: "Lion", "Tiger", "Wolf".
|
||||
- A 'Card' for the Order "Artiodactyla". Inside this, create 'Card's for these two species: "Giraffe", "Hippopotamus".
|
||||
|
||||
**2. "Birds" Tab:**
|
||||
- A 'Card' for the Class "Aves".
|
||||
- Inside the "Aves" card, create three 'Card's for the following Orders:
|
||||
- A 'Card' for the Order "Accipitriformes". Inside this, create a 'Card' for the species: "Bald Eagle".
|
||||
- A 'Card' for the Order "Struthioniformes". Inside this, create a 'Card' for the species: "Ostrich".
|
||||
- A 'Card' for the Order "Sphenisciformes". Inside this, create a 'Card' for the species: "Penguin".
|
||||
|
||||
**3. "Reptiles" Tab:**
|
||||
- A 'Card' for the Class "Reptilia".
|
||||
- Inside the "Reptilia" card, create two 'Card's for the following Orders:
|
||||
- A 'Card' for the Order "Crocodilia". Inside this, create a 'Card' for the species: "Nile Crocodile".
|
||||
- A 'Card' for the Order "Squamata". Inside this, create 'Card's for these two species: "Komodo Dragon", "Ball Python".
|
||||
|
||||
Each species card must contain a 'Row' with an 'Image' and a 'Text' component for the species name. Do not add any other components.
|
||||
|
||||
Each Class and Order card must contain a 'Column' with a 'Text' component with the name, and then the children cards below.
|
||||
|
||||
IMPORTANT: Do not skip any of the classes, orders, or species above. Include every item that is mentioned.
|
||||
`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"Heading",
|
||||
"text",
|
||||
"Simple Animal Explorer"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Search..."),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Class: Mammalia"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Order: Carnivora"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Lion"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Tiger"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Wolf"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Order: Artiodactyla"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Giraffe"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Hippopotamus"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Class: Aves"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Order: Accipitriformes"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Bald Eagle"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Order: Struthioniformes"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Ostrich"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Order: Sphenisciformes"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Penguin"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Class: Reptilia"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Order: Crocodilia"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Nile Crocodile"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Order: Squamata"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Komodo Dragon"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Ball Python"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "recipeCard",
|
||||
description: "A UI to display a recipe with ingredients and instructions.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a recipe card. It should have a 'Heading' for the recipe title, "Classic Lasagna". Below the title, an 'Image' of the lasagna. Then, a 'Row' containing two 'Column's. The first column has a 'Text' heading "Ingredients" and a 'List' of ingredients. The second column has a 'Text' heading "Instructions" and a 'List' of step-by-step instructions. Finally, a 'Button' at the bottom labeled "Watch Video Tutorial".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Classic Lasagna"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Ingredients"),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Instructions"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Watch Video Tutorial"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "musicPlayer",
|
||||
description: "A simple music player UI.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a music player. It should be a 'Card' containing a 'Column'. Inside the column, there's an 'Image' for the album art, a 'Text' for the song title "Bohemian Rhapsody", another 'Text' for the artist "Queen", a 'Slider' for the song progress, and a 'Row' with three 'Button's: "Previous", "Play", and "Next".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Bohemian Rhapsody"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Queen"),
|
||||
new SurfaceUpdateSchemaMatcher("Slider"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Previous"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Play"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Next"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "weatherForecast",
|
||||
description: "A UI to display the weather forecast.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a weather forecast UI. It should have a 'Heading' with the city name, "New York". Below it, a 'Row' with the current temperature as a 'Text' component ("68°F") and an 'Image' for the weather icon (e.g., a sun). Below that, a 'Divider'. Then, a 'List' component to display the 5-day forecast. Each item in the list should be a 'Row' with the day, an icon, and high/low temperatures.`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "New York"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "68°F"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher("List"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "surveyForm",
|
||||
description: "A customer feedback survey form.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a survey form. It should have a 'Heading' "Customer Feedback". Then a 'MultipleChoice' question "How would you rate our service?" with options "Excellent", "Good", "Average", "Poor". Then a 'CheckBox' section for "What did you like?" with options "Product Quality", "Price", "Customer Support". Finally, a 'TextField' with the label "Any other comments?" and a 'Button' labeled "Submit Feedback".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Customer Feedback"),
|
||||
new SurfaceUpdateSchemaMatcher("MultipleChoice", "options", "Excellent"),
|
||||
new SurfaceUpdateSchemaMatcher("CheckBox", "label", "Product Quality"),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"TextField",
|
||||
"label",
|
||||
"Any other comments?"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Submit Feedback"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "flightBooker",
|
||||
description: "A form to search for flights.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a flight booking form. It should have a 'Heading' "Book a Flight". Use a 'Row' for two 'TextField's: "Departure City" and "Arrival City". Below that, another 'Row' for two 'DateTimeInput's: "Departure Date" and "Return Date". Add a 'CheckBox' for "One-way trip". Finally, a 'Button' labeled "Search Flights".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Book a Flight"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Departure City"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Arrival City"),
|
||||
new SurfaceUpdateSchemaMatcher("DateTimeInput"),
|
||||
new SurfaceUpdateSchemaMatcher("CheckBox", "label", "One-way trip"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Search Flights"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "dashboard",
|
||||
description: "A simple dashboard with statistics.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a simple dashboard. It should have a 'Heading' "Sales Dashboard". Below, a 'Row' containing three 'Card's. The first card has a 'Text' "Revenue" and another 'Text' "$50,000". The second card has "New Customers" and "1,200". The third card has "Conversion Rate" and "4.5%".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Sales Dashboard"),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Revenue"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "$50,000"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "New Customers"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "1,200"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Conversion Rate"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "4.5%"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "contactCard",
|
||||
description: "A UI to display contact information.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a contact card. It should be a 'Card' with a 'Row'. The row contains an 'Image' (as an avatar) and a 'Column'. The column contains a 'Text' for the name "Jane Doe", a 'Text' for the email "jane.doe@example.com", and a 'Text' for the phone number "(123) 456-7890". Below the main row, add a 'Button' labeled "View on Map".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Jane Doe"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "jane.doe@example.com"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "(123) 456-7890"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "View on Map"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "calendarEventCreator",
|
||||
description: "A form to create a new calendar event.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a calendar event creation form. It should have a 'Heading' "New Event". Include a 'TextField' for the "Event Title". Use a 'Row' for two 'DateTimeInput's for "Start Time" and "End Time". Add a 'CheckBox' labeled "All-day event". Finally, a 'Row' with two 'Button's: "Save" and "Cancel".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "New Event"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Event Title"),
|
||||
new SurfaceUpdateSchemaMatcher("DateTimeInput"),
|
||||
new SurfaceUpdateSchemaMatcher("CheckBox", "label", "All-day event"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Save"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Cancel"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "checkoutPage",
|
||||
description: "A simplified e-commerce checkout page.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a checkout page. It should have a 'Heading' "Checkout". Create a 'Column' for "Shipping Information" with 'TextField's for "Full Name" and "Address". Create another 'Column' for "Payment Information" with 'TextField's for "Card Number" and "Expiry Date". Add a 'Divider'. Show an order summary with a 'Text' component: "Total: $99.99". Finally, a 'Button' labeled "Place Order".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Checkout"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Full Name"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Address"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Card Number"),
|
||||
new SurfaceUpdateSchemaMatcher("TextField", "label", "Expiry Date"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Total: $99.99"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Place Order"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "socialMediaPost",
|
||||
description: "A component representing a social media post.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a social media post. It should be a 'Card' containing a 'Column'. The first item is a 'Row' with an 'Image' (user avatar) and a 'Text' (username "user123"). Below that, a 'Text' component for the post content: "Enjoying the beautiful weather today!". Then, an 'Image' for the main post picture. Finally, a 'Row' with three 'Button's: "Like", "Comment", and "Share".`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "user123"),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"Text",
|
||||
"text",
|
||||
"Enjoying the beautiful weather today!"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Like"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Comment"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Share"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "eCommerceProductPage",
|
||||
description: "A detailed product page for an e-commerce website.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a product details page.
|
||||
The main layout should be a 'Row'.
|
||||
The left side of the row is a 'Column' containing a large main 'Image' of the product, and below it, a 'Row' of three smaller thumbnail 'Image' components.
|
||||
The right side of the row is another 'Column' for product information:
|
||||
- A 'Heading' for the product name, "Premium Leather Jacket".
|
||||
- A 'Text' component for the price, "$299.99".
|
||||
- A 'Divider'.
|
||||
- A 'Text' heading "Select Size", followed by a 'MultipleChoice' component with options "S", "M", "L", "XL".
|
||||
- A 'Text' heading "Select Color", followed by another 'MultipleChoice' component with options "Black", "Brown", "Red".
|
||||
- A 'Button' with the label "Add to Cart".
|
||||
- A 'Text' component for the product description below the button.`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"Heading",
|
||||
"text",
|
||||
"Premium Leather Jacket"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "$299.99"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
new SurfaceUpdateSchemaMatcher("MultipleChoice", "options", "S"),
|
||||
new SurfaceUpdateSchemaMatcher("MultipleChoice", "options", "Black"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Add to Cart"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "interactiveDashboard",
|
||||
description: "A dashboard with filters and data cards.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for an interactive analytics dashboard.
|
||||
At the top, a 'Heading' "Company Dashboard".
|
||||
Below the heading, a 'Card' containing a 'Row' of filter controls:
|
||||
- A 'DateTimeInput' with a label for "Start Date".
|
||||
- A 'DateTimeInput' with a label for "End Date".
|
||||
- A 'Button' labeled "Apply Filters".
|
||||
Below the filters card, a 'Row' containing two 'Card's for key metrics:
|
||||
- The first 'Card' has a 'Heading' "Total Revenue" and a 'Text' component showing "$1,234,567".
|
||||
- The second 'Card' has a 'Heading' "New Users" and a 'Text' component showing "4,321".
|
||||
Finally, a large 'Card' at the bottom with a 'Heading' "Revenue Over Time" and a placeholder 'Image' to represent a line chart.`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Company Dashboard"),
|
||||
new SurfaceUpdateSchemaMatcher("DateTimeInput"),
|
||||
new SurfaceUpdateSchemaMatcher("Button", "label", "Apply Filters"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Total Revenue"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "$1,234,567"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "New Users"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "4,321"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Revenue Over Time"),
|
||||
new SurfaceUpdateSchemaMatcher("Image"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "travelItinerary",
|
||||
description: "A multi-day travel itinerary display.",
|
||||
schemaPath,
|
||||
promptText: `Generate a JSON message with a surfaceUpdate property for a travel itinerary for a trip to Paris.
|
||||
It should have a main 'Heading' "Paris Adventure".
|
||||
Below, use a 'List' to display three days. Each item in the list should be a 'Card'.
|
||||
- The first 'Card' (Day 1) should contain a 'Heading' "Day 1: Arrival & Eiffel Tower", and a 'List' of activities for that day: "Check into hotel", "Lunch at a cafe", "Visit the Eiffel Tower".
|
||||
- The second 'Card' (Day 2) should contain a 'Heading' "Day 2: Museums & Culture", and a 'List' of activities: "Visit the Louvre Museum", "Walk through Tuileries Garden", "See the Arc de Triomphe".
|
||||
- The third 'Card' (Day 3) should contain a 'Heading' "Day 3: Art & Departure", and a 'List' of activities: "Visit Musée d'Orsay", "Explore Montmartre", "Depart from CDG".
|
||||
Each activity in the inner lists should be a 'Row' containing a 'CheckBox' (to mark as complete) and a 'Text' component with the activity description.`,
|
||||
matchers: [
|
||||
new MessageTypeMatcher("surfaceUpdate"),
|
||||
new SurfaceUpdateSchemaMatcher("Heading", "text", "Paris Adventure"),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"Heading",
|
||||
"text",
|
||||
"Day 1: Arrival & Eiffel Tower"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"Heading",
|
||||
"text",
|
||||
"Day 2: Museums & Culture"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher(
|
||||
"Heading",
|
||||
"text",
|
||||
"Day 3: Art & Departure"
|
||||
),
|
||||
new SurfaceUpdateSchemaMatcher("Column"),
|
||||
new SurfaceUpdateSchemaMatcher("CheckBox"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Visit the Eiffel Tower"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Visit the Louvre Museum"),
|
||||
new SurfaceUpdateSchemaMatcher("Text", "text", "Explore Montmartre"),
|
||||
],
|
||||
},
|
||||
];
|
||||
24
vendor/a2ui/specification/0.8/eval/src/schema_matcher.ts
vendored
Normal file
24
vendor/a2ui/specification/0.8/eval/src/schema_matcher.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export interface ValidationResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export abstract class SchemaMatcher {
|
||||
abstract validate(schema: any): ValidationResult;
|
||||
}
|
||||
207
vendor/a2ui/specification/0.8/eval/src/surface_update_schema_matcher.ts
vendored
Normal file
207
vendor/a2ui/specification/0.8/eval/src/surface_update_schema_matcher.ts
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { SchemaMatcher, ValidationResult } from "./schema_matcher";
|
||||
|
||||
/**
|
||||
* A schema matcher that validates the presence of a component type within a
|
||||
* `surfaceUpdate` message, and optionally validates the presence and value of
|
||||
* a property on that component.
|
||||
*/
|
||||
export class SurfaceUpdateSchemaMatcher extends SchemaMatcher {
|
||||
constructor(
|
||||
public componentType: string,
|
||||
public propertyName?: string,
|
||||
public propertyValue?: any,
|
||||
public caseInsensitive: boolean = false
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private getComponentById(components: any[], id: string): any | undefined {
|
||||
return components.find((c: any) => c.id === id);
|
||||
}
|
||||
|
||||
validate(schema: any): ValidationResult {
|
||||
if (!schema.surfaceUpdate) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Expected a 'surfaceUpdate' message but found none.`,
|
||||
};
|
||||
}
|
||||
if (!Array.isArray(schema.surfaceUpdate.components)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `'surfaceUpdate' message does not contain a 'components' array.`,
|
||||
};
|
||||
}
|
||||
|
||||
const components = schema.surfaceUpdate.components;
|
||||
|
||||
for (const c of components) {
|
||||
if (c.component && Object.keys(c.component).length > 1) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Component ID '${c.id}' has multiple component types defined: ${Object.keys(c.component).join(", ")}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const matchingComponents = components.filter(
|
||||
(c: any) => c.component && c.component[this.componentType]
|
||||
);
|
||||
|
||||
if (matchingComponents.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to find component of type '${this.componentType}'.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.propertyName) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
for (const component of matchingComponents) {
|
||||
const properties = component.component[this.componentType];
|
||||
if (properties) {
|
||||
// Check for property directly on the component
|
||||
if (properties[this.propertyName] !== undefined) {
|
||||
if (this.propertyValue === undefined) {
|
||||
return { success: true };
|
||||
}
|
||||
const actualValue = properties[this.propertyName];
|
||||
if (this.valueMatches(actualValue, this.propertyValue)) {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
// Specifically for Buttons, check for label in a child Text component
|
||||
if (
|
||||
this.componentType === "Button" &&
|
||||
this.propertyName === "label" &&
|
||||
properties.child
|
||||
) {
|
||||
const childComponent = this.getComponentById(
|
||||
components,
|
||||
properties.child
|
||||
);
|
||||
if (
|
||||
childComponent &&
|
||||
childComponent.component &&
|
||||
childComponent.component.Text
|
||||
) {
|
||||
const textValue = childComponent.component.Text.text;
|
||||
if (this.valueMatches(textValue, this.propertyValue)) {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.propertyValue !== undefined) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to find component of type '${this.componentType}' with property '${this.propertyName}' containing value ${JSON.stringify(this.propertyValue)}.`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to find component of type '${this.componentType}' with property '${this.propertyName}'.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private valueMatches(actualValue: any, expectedValue: any): boolean {
|
||||
if (actualValue === null || actualValue === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const compareStrings = (s1: string, s2: string) => {
|
||||
return this.caseInsensitive
|
||||
? s1.toLowerCase() === s2.toLowerCase()
|
||||
: s1 === s2;
|
||||
};
|
||||
|
||||
// Handle new literal/path object structure
|
||||
if (typeof actualValue === "object" && !Array.isArray(actualValue)) {
|
||||
if (actualValue.literalString !== undefined) {
|
||||
return (
|
||||
typeof expectedValue === "string" &&
|
||||
compareStrings(actualValue.literalString, expectedValue)
|
||||
);
|
||||
}
|
||||
if (actualValue.literalNumber !== undefined) {
|
||||
return actualValue.literalNumber === expectedValue;
|
||||
}
|
||||
if (actualValue.literalBoolean !== undefined) {
|
||||
return actualValue.literalBoolean === expectedValue;
|
||||
}
|
||||
// Could also have a 'path' key, but for matching we'd expect a literal value in expectedValue
|
||||
}
|
||||
|
||||
// Handle array cases (e.g., for MultipleChoice options)
|
||||
if (Array.isArray(actualValue)) {
|
||||
for (const item of actualValue) {
|
||||
if (typeof item === "object" && item !== null) {
|
||||
// Check if the item itself is a bound value object
|
||||
if (
|
||||
item.literalString !== undefined &&
|
||||
typeof expectedValue === "string" &&
|
||||
compareStrings(item.literalString, expectedValue)
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
item.literalNumber !== undefined &&
|
||||
item.literalNumber === expectedValue
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
item.literalBoolean !== undefined &&
|
||||
item.literalBoolean === expectedValue
|
||||
)
|
||||
return true;
|
||||
|
||||
// Check for structures like MultipleChoice options {label: {literalString: ...}, value: ...}
|
||||
if (
|
||||
item.label &&
|
||||
typeof item.label === "object" &&
|
||||
item.label.literalString !== undefined &&
|
||||
typeof expectedValue === "string" &&
|
||||
compareStrings(item.label.literalString, expectedValue)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (item.value === expectedValue) {
|
||||
return true;
|
||||
}
|
||||
} else if (
|
||||
typeof item === "string" &&
|
||||
typeof expectedValue === "string" &&
|
||||
compareStrings(item, expectedValue)
|
||||
) {
|
||||
return true;
|
||||
} else if (item === expectedValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to direct comparison
|
||||
return JSON.stringify(actualValue) === JSON.stringify(expectedValue);
|
||||
}
|
||||
}
|
||||
521
vendor/a2ui/specification/0.8/eval/src/validator.ts
vendored
Normal file
521
vendor/a2ui/specification/0.8/eval/src/validator.ts
vendored
Normal file
@@ -0,0 +1,521 @@
|
||||
/*
|
||||
Copyright 2025 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { SurfaceUpdateSchemaMatcher } from "./surface_update_schema_matcher";
|
||||
import { SchemaMatcher } from "./schema_matcher";
|
||||
|
||||
export function validateSchema(
|
||||
data: any,
|
||||
schemaName: string,
|
||||
matchers?: SchemaMatcher[],
|
||||
): string[] {
|
||||
const errors: string[] = [];
|
||||
if (data.surfaceUpdate) {
|
||||
validateSurfaceUpdate(data.surfaceUpdate, errors);
|
||||
} else if (data.dataModelUpdate) {
|
||||
validateDataModelUpdate(data.dataModelUpdate, errors);
|
||||
} else if (data.beginRendering) {
|
||||
validateBeginRendering(data.beginRendering, errors);
|
||||
} else if (data.deleteSurface) {
|
||||
validateDeleteSurface(data.deleteSurface, errors);
|
||||
} else {
|
||||
errors.push(
|
||||
"A2UI Protocol message must have one of: surfaceUpdate, dataModelUpdate, beginRendering, deleteSurface.",
|
||||
);
|
||||
}
|
||||
|
||||
if (matchers) {
|
||||
for (const matcher of matchers) {
|
||||
const result = matcher.validate(data);
|
||||
if (!result.success) {
|
||||
errors.push(result.error!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function validateDeleteSurface(data: any, errors: string[]) {
|
||||
if (data.surfaceId === undefined) {
|
||||
errors.push("DeleteSurface must have a 'surfaceId' property.");
|
||||
}
|
||||
const allowed = ["surfaceId"];
|
||||
for (const key in data) {
|
||||
if (!allowed.includes(key)) {
|
||||
errors.push(`DeleteSurface has unexpected property: ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateSurfaceUpdate(data: any, errors: string[]) {
|
||||
if (data.surfaceId === undefined) {
|
||||
errors.push("SurfaceUpdate must have a 'surfaceId' property.");
|
||||
}
|
||||
if (!data.components || !Array.isArray(data.components)) {
|
||||
errors.push("SurfaceUpdate must have a 'components' array.");
|
||||
return;
|
||||
}
|
||||
|
||||
const componentIds = new Set<string>();
|
||||
for (const c of data.components) {
|
||||
if (c.id) {
|
||||
if (componentIds.has(c.id)) {
|
||||
errors.push(`Duplicate component ID found: ${c.id}`);
|
||||
}
|
||||
componentIds.add(c.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const component of data.components) {
|
||||
validateComponent(component, componentIds, errors);
|
||||
}
|
||||
}
|
||||
|
||||
function validateDataModelUpdate(data: any, errors: string[]) {
|
||||
if (data.surfaceId === undefined) {
|
||||
errors.push("DataModelUpdate must have a 'surfaceId' property.");
|
||||
}
|
||||
|
||||
const allowedTopLevel = ["surfaceId", "path", "contents"];
|
||||
for (const key in data) {
|
||||
if (!allowedTopLevel.includes(key)) {
|
||||
errors.push(`DataModelUpdate has unexpected property: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.contents)) {
|
||||
errors.push("DataModelUpdate must have a 'contents' array.");
|
||||
return;
|
||||
}
|
||||
|
||||
const validateValueProperty = (
|
||||
item: any,
|
||||
itemErrors: string[],
|
||||
prefix: string,
|
||||
) => {
|
||||
const valueProps = [
|
||||
"valueString",
|
||||
"valueNumber",
|
||||
"valueBoolean",
|
||||
"valueMap",
|
||||
];
|
||||
let valueCount = 0;
|
||||
let foundValueProp = "";
|
||||
for (const prop of valueProps) {
|
||||
if (item[prop] !== undefined) {
|
||||
valueCount++;
|
||||
foundValueProp = prop;
|
||||
}
|
||||
}
|
||||
if (valueCount !== 1) {
|
||||
itemErrors.push(
|
||||
`${prefix} must have exactly one value property (${valueProps.join(", ")}), found ${valueCount}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundValueProp === "valueMap") {
|
||||
if (!Array.isArray(item.valueMap)) {
|
||||
itemErrors.push(`${prefix} 'valueMap' must be an array.`);
|
||||
return;
|
||||
}
|
||||
item.valueMap.forEach((mapItem: any, index: number) => {
|
||||
if (!mapItem.key) {
|
||||
itemErrors.push(
|
||||
`${prefix} 'valueMap' item at index ${index} is missing a 'key'.`,
|
||||
);
|
||||
}
|
||||
const mapValueProps = ["valueString", "valueNumber", "valueBoolean"];
|
||||
let mapValueCount = 0;
|
||||
for (const prop of mapValueProps) {
|
||||
if (mapItem[prop] !== undefined) {
|
||||
mapValueCount++;
|
||||
}
|
||||
}
|
||||
if (mapValueCount !== 1) {
|
||||
itemErrors.push(
|
||||
`${prefix} 'valueMap' item at index ${index} must have exactly one value property (${mapValueProps.join(", ")}), found ${mapValueCount}.`,
|
||||
);
|
||||
}
|
||||
const allowedMapKeys = ["key", ...mapValueProps];
|
||||
for (const key in mapItem) {
|
||||
if (!allowedMapKeys.includes(key)) {
|
||||
itemErrors.push(
|
||||
`${prefix} 'valueMap' item at index ${index} has unexpected property: ${key}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
data.contents.forEach((item: any, index: number) => {
|
||||
if (!item.key) {
|
||||
errors.push(
|
||||
`DataModelUpdate 'contents' item at index ${index} is missing a 'key'.`,
|
||||
);
|
||||
}
|
||||
validateValueProperty(
|
||||
item,
|
||||
errors,
|
||||
`DataModelUpdate 'contents' item at index ${index}`,
|
||||
);
|
||||
const allowedKeys = [
|
||||
"key",
|
||||
"valueString",
|
||||
"valueNumber",
|
||||
"valueBoolean",
|
||||
"valueMap",
|
||||
];
|
||||
for (const key in item) {
|
||||
if (!allowedKeys.includes(key)) {
|
||||
errors.push(
|
||||
`DataModelUpdate 'contents' item at index ${index} has unexpected property: ${key}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateBeginRendering(data: any, errors: string[]) {
|
||||
if (data.surfaceId === undefined) {
|
||||
errors.push("BeginRendering message must have a 'surfaceId' property.");
|
||||
}
|
||||
if (!data.root) {
|
||||
errors.push("BeginRendering message must have a 'root' property.");
|
||||
}
|
||||
}
|
||||
|
||||
function validateBoundValue(
|
||||
prop: any,
|
||||
propName: string,
|
||||
componentId: string,
|
||||
componentType: string,
|
||||
errors: string[],
|
||||
) {
|
||||
if (typeof prop !== "object" || prop === null || Array.isArray(prop)) {
|
||||
errors.push(
|
||||
`Component '${componentId}' of type '${componentType}' property '${propName}' must be an object.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const keys = Object.keys(prop);
|
||||
const allowedKeys = [
|
||||
"literalString",
|
||||
"literalNumber",
|
||||
"literalBoolean",
|
||||
"path",
|
||||
];
|
||||
let validKeyCount = 0;
|
||||
for (const key of keys) {
|
||||
if (allowedKeys.includes(key)) {
|
||||
validKeyCount++;
|
||||
}
|
||||
}
|
||||
if (validKeyCount !== 1 || keys.length !== 1) {
|
||||
errors.push(
|
||||
`Component '${componentId}' of type '${componentType}' property '${propName}' must have exactly one key from [${allowedKeys.join(", ")}]. Found: ${keys.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateComponent(
|
||||
component: any,
|
||||
allIds: Set<string>,
|
||||
errors: string[],
|
||||
) {
|
||||
if (!component.id) {
|
||||
errors.push(`Component is missing an 'id'.`);
|
||||
return;
|
||||
}
|
||||
if (!component.component) {
|
||||
errors.push(`Component '${component.id}' is missing 'component'.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const componentTypes = Object.keys(component.component);
|
||||
if (componentTypes.length !== 1) {
|
||||
errors.push(
|
||||
`Component '${component.id}' must have exactly one property in 'component', but found ${componentTypes.length}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const componentType = componentTypes[0];
|
||||
const properties = component.component[componentType];
|
||||
|
||||
const checkRequired = (props: string[]) => {
|
||||
for (const prop of props) {
|
||||
if (properties[prop] === undefined) {
|
||||
errors.push(
|
||||
`Component '${component.id}' of type '${componentType}' is missing required property '${prop}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkRefs = (ids: (string | undefined)[]) => {
|
||||
for (const id of ids) {
|
||||
if (id && !allIds.has(id)) {
|
||||
errors.push(
|
||||
`Component '${component.id}' references non-existent component ID '${id}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch (componentType) {
|
||||
case "Heading":
|
||||
checkRequired(["text"]);
|
||||
if (properties.text)
|
||||
validateBoundValue(
|
||||
properties.text,
|
||||
"text",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "Text":
|
||||
checkRequired(["text"]);
|
||||
if (properties.text)
|
||||
validateBoundValue(
|
||||
properties.text,
|
||||
"text",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "Image":
|
||||
checkRequired(["url"]);
|
||||
if (properties.url)
|
||||
validateBoundValue(
|
||||
properties.url,
|
||||
"url",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "Video":
|
||||
checkRequired(["url"]);
|
||||
if (properties.url)
|
||||
validateBoundValue(
|
||||
properties.url,
|
||||
"url",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "AudioPlayer":
|
||||
checkRequired(["url"]);
|
||||
if (properties.url)
|
||||
validateBoundValue(
|
||||
properties.url,
|
||||
"url",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
if (properties.description)
|
||||
validateBoundValue(
|
||||
properties.description,
|
||||
"description",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "TextField":
|
||||
checkRequired(["label"]);
|
||||
if (properties.label)
|
||||
validateBoundValue(
|
||||
properties.label,
|
||||
"label",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
if (properties.text)
|
||||
validateBoundValue(
|
||||
properties.text,
|
||||
"text",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "DateTimeInput":
|
||||
checkRequired(["value"]);
|
||||
if (properties.value)
|
||||
validateBoundValue(
|
||||
properties.value,
|
||||
"value",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "MultipleChoice":
|
||||
checkRequired(["selections", "options"]);
|
||||
if (properties.selections) {
|
||||
if (
|
||||
typeof properties.selections !== "object" ||
|
||||
properties.selections === null ||
|
||||
(!properties.selections.literalArray && !properties.selections.path)
|
||||
) {
|
||||
errors.push(
|
||||
`Component '${component.id}' of type '${componentType}' property 'selections' must have either 'literalArray' or 'path'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(properties.options)) {
|
||||
properties.options.forEach((option: any, index: number) => {
|
||||
if (!option.label)
|
||||
errors.push(
|
||||
`Component '${component.id}' option at index ${index} missing 'label'.`,
|
||||
);
|
||||
if (option.label)
|
||||
validateBoundValue(
|
||||
option.label,
|
||||
"label",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
if (!option.value)
|
||||
errors.push(
|
||||
`Component '${component.id}' option at index ${index} missing 'value'.`,
|
||||
);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Slider":
|
||||
checkRequired(["value"]);
|
||||
if (properties.value)
|
||||
validateBoundValue(
|
||||
properties.value,
|
||||
"value",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "CheckBox":
|
||||
checkRequired(["value", "label"]);
|
||||
if (properties.value)
|
||||
validateBoundValue(
|
||||
properties.value,
|
||||
"value",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
if (properties.label)
|
||||
validateBoundValue(
|
||||
properties.label,
|
||||
"label",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
case "Row":
|
||||
case "Column":
|
||||
case "List":
|
||||
checkRequired(["children"]);
|
||||
if (properties.children && Array.isArray(properties.children)) {
|
||||
const hasExplicit = !!properties.children.explicitList;
|
||||
const hasTemplate = !!properties.children.template;
|
||||
if ((hasExplicit && hasTemplate) || (!hasExplicit && !hasTemplate)) {
|
||||
errors.push(
|
||||
`Component '${component.id}' must have either 'explicitList' or 'template' in children, but not both or neither.`,
|
||||
);
|
||||
}
|
||||
if (hasExplicit) {
|
||||
checkRefs(properties.children.explicitList);
|
||||
}
|
||||
if (hasTemplate) {
|
||||
checkRefs([properties.children.template?.componentId]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Card":
|
||||
checkRequired(["child"]);
|
||||
checkRefs([properties.child]);
|
||||
break;
|
||||
case "Tabs":
|
||||
checkRequired(["tabItems"]);
|
||||
if (properties.tabItems && Array.isArray(properties.tabItems)) {
|
||||
properties.tabItems.forEach((tab: any) => {
|
||||
if (!tab.title) {
|
||||
errors.push(
|
||||
`Tab item in component '${component.id}' is missing a 'title'.`,
|
||||
);
|
||||
}
|
||||
if (!tab.child) {
|
||||
errors.push(
|
||||
`Tab item in component '${component.id}' is missing a 'child'.`,
|
||||
);
|
||||
}
|
||||
checkRefs([tab.child]);
|
||||
if (tab.title)
|
||||
validateBoundValue(
|
||||
tab.title,
|
||||
"title",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Modal":
|
||||
checkRequired(["entryPointChild", "contentChild"]);
|
||||
checkRefs([properties.entryPointChild, properties.contentChild]);
|
||||
break;
|
||||
case "Button":
|
||||
checkRequired(["child", "action"]);
|
||||
checkRefs([properties.child]);
|
||||
if (!properties.action || !properties.action.name) {
|
||||
errors.push(
|
||||
`Component '${component.id}' Button action is missing a 'name'.`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "Divider":
|
||||
// No required properties
|
||||
break;
|
||||
case "Icon":
|
||||
checkRequired(["name"]);
|
||||
if (properties.name)
|
||||
validateBoundValue(
|
||||
properties.name,
|
||||
"name",
|
||||
component.id,
|
||||
componentType,
|
||||
errors,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
errors.push(
|
||||
`Unknown component type '${componentType}' in component '${component.id}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
12
vendor/a2ui/specification/0.8/eval/tsconfig.json
vendored
Normal file
12
vendor/a2ui/specification/0.8/eval/tsconfig.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
15
vendor/a2ui/specification/0.8/json/README.md
vendored
Normal file
15
vendor/a2ui/specification/0.8/json/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# A2UI JSON Schema Files
|
||||
|
||||
This directory contains the formal JSON Schema definitions for the A2UI protocol.
|
||||
|
||||
## Schema Descriptions
|
||||
|
||||
- `server_to_client.json`: This is the core, catalog-agnostic schema for messages sent from the server to the client. It defines the four main message types (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`, `deleteSurface`) and their structure. In this schema, the `component` object within a `surfaceUpdate` message is generic (`"additionalProperties": true`), allowing any component definitions to be passed.
|
||||
|
||||
- `client_to_server.json`: This schema defines the structure for event messages sent from the client to the server. This includes user-initiated actions (`userAction`), error reporting (`error`), and the crucial `clientUiCapabilities` message, which allows a client to inform the server about the component catalog it supports.
|
||||
|
||||
- `catalog_description_schema.json`: This is a meta-schema that defines the structure of an A2UI component catalog. A catalog consists of a `components` object and a `styles` object, where each key is a component/style name and the value is a JSON schema defining its properties. This allows for the creation of custom component sets.
|
||||
|
||||
- `standard_catalog_definition.json`: This file is a concrete implementation of a catalog, conforming to the `catalog_description_schema.json`. It defines the standard set of components (e.g., `Text`, `Image`, `Row`, `Card`) and styles that are part of the baseline A2UI specification.
|
||||
|
||||
- `server_to_client_with_standard_catalog.json`: This is a resolved, LLM-friendly version of the server-to-client schema. It is generated by combining `server_to_client.json` with the `standard_catalog_definition.json`. In this version, the generic `component` object is replaced with a strict `oneOf` definition that includes every component from the standard catalog. This provides a complete, strictly-typed schema that is ideal for LLMs to use for generating valid A2UI messages without ambiguity.
|
||||
23
vendor/a2ui/specification/0.8/json/a2ui_client_capabilities_schema.json
vendored
Normal file
23
vendor/a2ui/specification/0.8/json/a2ui_client_capabilities_schema.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "A2UI Client Capabilities Schema",
|
||||
"description": "A schema for the a2uiClientCapabilities object, which is sent from the client to the server to describe the client's UI rendering capabilities.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"supportedCatalogIds": {
|
||||
"type": "array",
|
||||
"description": "The URI of each of the catalogs that is supported by the client. The standard catalog for v0.8 is 'a2ui.org:standard_catalog_0_8_0'.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"inlineCatalogs": {
|
||||
"type": "array",
|
||||
"description": "An array of inline catalog definitions. This should only be provided if the agent declares 'acceptsInlineCatalogs: true' in its capabilities.",
|
||||
"items": {
|
||||
"$ref": "catalog_description_schema.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["supportedCatalogIds"]
|
||||
}
|
||||
34
vendor/a2ui/specification/0.8/json/catalog_description_schema.json
vendored
Normal file
34
vendor/a2ui/specification/0.8/json/catalog_description_schema.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "A2UI Catalog Description Schema",
|
||||
"description": "A schema for a custom Catalog Description including A2UI components and styles.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"catalogId": {
|
||||
"title": "Catalog ID",
|
||||
"description": "A string that uniquely identifies this catalog. It is recommended to prefix this with an internet domain that you own, to avoid conflicts e.g. mycompany.com:somecatalog'.",
|
||||
"type": "string"
|
||||
},
|
||||
"components": {
|
||||
"title": "A2UI Components",
|
||||
"description": "A schema that defines a catalog of A2UI components. Each key is a component name, and each value is the JSON schema for that component's properties.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "https://json-schema.org/draft/2020-12/schema"
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"title": "A2UI Styles",
|
||||
"description": "A schema that defines a catalog of A2UI styles. Each key is a style name, and each value is the JSON schema for that style's properties.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "https://json-schema.org/draft/2020-12/schema"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"catalogId",
|
||||
"components",
|
||||
"styles"
|
||||
]
|
||||
}
|
||||
53
vendor/a2ui/specification/0.8/json/client_to_server.json
vendored
Normal file
53
vendor/a2ui/specification/0.8/json/client_to_server.json
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"title": "A2UI (Agent to UI) Client-to-Server Event Schema",
|
||||
"description": "Describes a JSON payload for a client-to-server event message.",
|
||||
"type": "object",
|
||||
"minProperties": 1,
|
||||
"maxProperties": 1,
|
||||
"properties": {
|
||||
"userAction": {
|
||||
"type": "object",
|
||||
"description": "Reports a user-initiated action from a component.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the action, taken from the component's action.name property."
|
||||
},
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The id of the surface where the event originated."
|
||||
},
|
||||
"sourceComponentId": {
|
||||
"type": "string",
|
||||
"description": "The id of the component that triggered the event."
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "An ISO 8601 timestamp of when the event occurred."
|
||||
},
|
||||
"context": {
|
||||
"type": "object",
|
||||
"description": "A JSON object containing the key-value pairs from the component's action.context, after resolving all data bindings.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"surfaceId",
|
||||
"sourceComponentId",
|
||||
"timestamp",
|
||||
"context"
|
||||
]
|
||||
},
|
||||
"error": {
|
||||
"type": "object",
|
||||
"description": "Reports a client-side error. The content is flexible.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{ "required": ["userAction"] },
|
||||
{ "required": ["error"] }
|
||||
]
|
||||
}
|
||||
148
vendor/a2ui/specification/0.8/json/server_to_client.json
vendored
Normal file
148
vendor/a2ui/specification/0.8/json/server_to_client.json
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"title": "A2UI Message Schema",
|
||||
"description": "Describes a JSON payload for an A2UI (Agent to UI) message, which is used to dynamically construct and update user interfaces. A message MUST contain exactly ONE of the action properties: 'beginRendering', 'surfaceUpdate', 'dataModelUpdate', or 'deleteSurface'.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"beginRendering": {
|
||||
"type": "object",
|
||||
"description": "Signals the client to begin rendering a surface with a root component and specific styles.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface to be rendered."
|
||||
},
|
||||
"catalogId": {
|
||||
"type": "string",
|
||||
"description": "The identifier of the component catalog to use for this surface. If omitted, the client MUST default to the standard catalog for this A2UI version (a2ui.org:standard_catalog_0_8_0)."
|
||||
},
|
||||
"root": {
|
||||
"type": "string",
|
||||
"description": "The ID of the root component to render."
|
||||
},
|
||||
"styles": {
|
||||
"type": "object",
|
||||
"description": "Styling information for the UI.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"required": ["root", "surfaceId"]
|
||||
},
|
||||
"surfaceUpdate": {
|
||||
"type": "object",
|
||||
"description": "Updates a surface with a new set of components.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface to be updated. If you are adding a new surface this *must* be a new, unique identified that has never been used for any existing surfaces shown."
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"description": "A list containing all UI components for the surface.",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "Represents a *single* component in a UI widget tree. This component could be one of many supported types.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for this component."
|
||||
},
|
||||
"weight": {
|
||||
"type": "number",
|
||||
"description": "The relative weight of this component within a Row or Column. This corresponds to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
|
||||
},
|
||||
"component": {
|
||||
"type": "object",
|
||||
"description": "A wrapper object that MUST contain exactly one key, which is the name of the component type. The value is an object containing the properties for that specific component.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"required": ["id", "component"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["surfaceId", "components"]
|
||||
},
|
||||
"dataModelUpdate": {
|
||||
"type": "object",
|
||||
"description": "Updates the data model for a surface.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface this data model update applies to."
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "An optional path to a location within the data model (e.g., '/user/name'). If omitted, or set to '/', the entire data model will be replaced."
|
||||
},
|
||||
"contents": {
|
||||
"type": "array",
|
||||
"description": "An array of data entries. Each entry must contain a 'key' and exactly one corresponding typed 'value*' property.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A single data entry. Exactly one 'value*' property should be provided alongside the key.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "The key for this data entry."
|
||||
},
|
||||
"valueString": {
|
||||
"type": "string"
|
||||
},
|
||||
"valueNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"valueBoolean": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"valueMap": {
|
||||
"description": "Represents a map as an adjacency list.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "One entry in the map. Exactly one 'value*' property should be provided alongside the key.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"valueString": {
|
||||
"type": "string"
|
||||
},
|
||||
"valueNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"valueBoolean": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["key"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["key"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["contents", "surfaceId"]
|
||||
},
|
||||
"deleteSurface": {
|
||||
"type": "object",
|
||||
"description": "Signals the client to delete the surface identified by 'surfaceId'.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface to be deleted."
|
||||
}
|
||||
},
|
||||
"required": ["surfaceId"]
|
||||
}
|
||||
}
|
||||
}
|
||||
827
vendor/a2ui/specification/0.8/json/server_to_client_with_standard_catalog.json
vendored
Normal file
827
vendor/a2ui/specification/0.8/json/server_to_client_with_standard_catalog.json
vendored
Normal file
@@ -0,0 +1,827 @@
|
||||
{
|
||||
"title": "A2UI Message Schema",
|
||||
"description": "Describes a JSON payload for an A2UI (Agent to UI) message, which is used to dynamically construct and update user interfaces. A message MUST contain exactly ONE of the action properties: 'beginRendering', 'surfaceUpdate', 'dataModelUpdate', or 'deleteSurface'.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"beginRendering": {
|
||||
"type": "object",
|
||||
"description": "Signals the client to begin rendering a surface with a root component and specific styles.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface to be rendered."
|
||||
},
|
||||
"root": {
|
||||
"type": "string",
|
||||
"description": "The ID of the root component to render."
|
||||
},
|
||||
"styles": {
|
||||
"type": "object",
|
||||
"description": "Styling information for the UI.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"font": {
|
||||
"type": "string",
|
||||
"description": "The primary font for the UI."
|
||||
},
|
||||
"primaryColor": {
|
||||
"type": "string",
|
||||
"description": "The primary UI color as a hexadecimal code (e.g., '#00BFFF').",
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["root", "surfaceId"]
|
||||
},
|
||||
"surfaceUpdate": {
|
||||
"type": "object",
|
||||
"description": "Updates a surface with a new set of components.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface to be updated. If you are adding a new surface this *must* be a new, unique identified that has never been used for any existing surfaces shown."
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"description": "A list containing all UI components for the surface.",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "Represents a *single* component in a UI widget tree. This component could be one of many supported types.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for this component."
|
||||
},
|
||||
"weight": {
|
||||
"type": "number",
|
||||
"description": "The relative weight of this component within a Row or Column. This corresponds to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column."
|
||||
},
|
||||
"component": {
|
||||
"type": "object",
|
||||
"description": "A wrapper object that MUST contain exactly one key, which is the name of the component type (e.g., 'Heading'). The value is an object containing the properties for that specific component.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"Text": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "object",
|
||||
"description": "The text content to display. This can be a literal string or a reference to a value in the data model ('path', e.g., '/doc/title'). While simple Markdown formatting is supported (i.e. without HTML, images, or links), utilizing dedicated UI components is generally preferred for a richer and more structured presentation.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"usageHint": {
|
||||
"type": "string",
|
||||
"description": "A hint for the base text style. One of:\n- `h1`: Largest heading.\n- `h2`: Second largest heading.\n- `h3`: Third largest heading.\n- `h4`: Fourth largest heading.\n- `h5`: Fifth largest heading.\n- `caption`: Small text for captions.\n- `body`: Standard body text.",
|
||||
"enum": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"caption",
|
||||
"body"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["text"]
|
||||
},
|
||||
"Image": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "object",
|
||||
"description": "The URL of the image to display. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/thumbnail/url').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fit": {
|
||||
"type": "string",
|
||||
"description": "Specifies how the image should be resized to fit its container. This corresponds to the CSS 'object-fit' property.",
|
||||
"enum": [
|
||||
"contain",
|
||||
"cover",
|
||||
"fill",
|
||||
"none",
|
||||
"scale-down"
|
||||
]
|
||||
},
|
||||
"usageHint": {
|
||||
"type": "string",
|
||||
"description": "A hint for the image size and style. One of:\n- `icon`: Small square icon.\n- `avatar`: Circular avatar image.\n- `smallFeature`: Small feature image.\n- `mediumFeature`: Medium feature image.\n- `largeFeature`: Large feature image.\n- `header`: Full-width, full bleed, header image.",
|
||||
"enum": [
|
||||
"icon",
|
||||
"avatar",
|
||||
"smallFeature",
|
||||
"mediumFeature",
|
||||
"largeFeature",
|
||||
"header"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["url"]
|
||||
},
|
||||
"Icon": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "object",
|
||||
"description": "The name of the icon to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/form/submit').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"accountCircle",
|
||||
"add",
|
||||
"arrowBack",
|
||||
"arrowForward",
|
||||
"attachFile",
|
||||
"calendarToday",
|
||||
"call",
|
||||
"camera",
|
||||
"check",
|
||||
"close",
|
||||
"delete",
|
||||
"download",
|
||||
"edit",
|
||||
"event",
|
||||
"error",
|
||||
"favorite",
|
||||
"favoriteOff",
|
||||
"folder",
|
||||
"help",
|
||||
"home",
|
||||
"info",
|
||||
"locationOn",
|
||||
"lock",
|
||||
"lockOpen",
|
||||
"mail",
|
||||
"menu",
|
||||
"moreVert",
|
||||
"moreHoriz",
|
||||
"notificationsOff",
|
||||
"notifications",
|
||||
"payment",
|
||||
"person",
|
||||
"phone",
|
||||
"photo",
|
||||
"print",
|
||||
"refresh",
|
||||
"search",
|
||||
"send",
|
||||
"settings",
|
||||
"share",
|
||||
"shoppingCart",
|
||||
"star",
|
||||
"starHalf",
|
||||
"starOff",
|
||||
"upload",
|
||||
"visibility",
|
||||
"visibilityOff",
|
||||
"warning"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
},
|
||||
"Video": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "object",
|
||||
"description": "The URL of the video to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/video/url').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["url"]
|
||||
},
|
||||
"AudioPlayer": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "object",
|
||||
"description": "The URL of the audio to be played. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/song/url').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the audio, such as a title or summary. This can be a literal string or a reference to a value in the data model ('path', e.g. '/song/title').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["url"]
|
||||
},
|
||||
"Row": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "object",
|
||||
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"explicitList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"componentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataBinding": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["componentId", "dataBinding"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"distribution": {
|
||||
"type": "string",
|
||||
"description": "Defines the arrangement of children along the main axis (horizontally). This corresponds to the CSS 'justify-content' property.",
|
||||
"enum": [
|
||||
"center",
|
||||
"end",
|
||||
"spaceAround",
|
||||
"spaceBetween",
|
||||
"spaceEvenly",
|
||||
"start"
|
||||
]
|
||||
},
|
||||
"alignment": {
|
||||
"type": "string",
|
||||
"description": "Defines the alignment of children along the cross axis (vertically). This corresponds to the CSS 'align-items' property.",
|
||||
"enum": ["start", "center", "end", "stretch"]
|
||||
}
|
||||
},
|
||||
"required": ["children"]
|
||||
},
|
||||
"Column": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "object",
|
||||
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"explicitList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"componentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataBinding": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["componentId", "dataBinding"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"distribution": {
|
||||
"type": "string",
|
||||
"description": "Defines the arrangement of children along the main axis (vertically). This corresponds to the CSS 'justify-content' property.",
|
||||
"enum": [
|
||||
"start",
|
||||
"center",
|
||||
"end",
|
||||
"spaceBetween",
|
||||
"spaceAround",
|
||||
"spaceEvenly"
|
||||
]
|
||||
},
|
||||
"alignment": {
|
||||
"type": "string",
|
||||
"description": "Defines the alignment of children along the cross axis (horizontally). This corresponds to the CSS 'align-items' property.",
|
||||
"enum": ["center", "end", "start", "stretch"]
|
||||
}
|
||||
},
|
||||
"required": ["children"]
|
||||
},
|
||||
"List": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "object",
|
||||
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"explicitList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"componentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataBinding": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["componentId", "dataBinding"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"description": "The direction in which the list items are laid out.",
|
||||
"enum": ["vertical", "horizontal"]
|
||||
},
|
||||
"alignment": {
|
||||
"type": "string",
|
||||
"description": "Defines the alignment of children along the cross axis.",
|
||||
"enum": ["start", "center", "end", "stretch"]
|
||||
}
|
||||
},
|
||||
"required": ["children"]
|
||||
},
|
||||
"Card": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"child": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component to be rendered inside the card."
|
||||
}
|
||||
},
|
||||
"required": ["child"]
|
||||
},
|
||||
"Tabs": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"tabItems": {
|
||||
"type": "array",
|
||||
"description": "An array of objects, where each object defines a tab with a title and a child component.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "object",
|
||||
"description": "The tab title. Defines the value as either a literal value or a path to data model value (e.g. '/options/title').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"child": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["title", "child"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["tabItems"]
|
||||
},
|
||||
"Divider": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"axis": {
|
||||
"type": "string",
|
||||
"description": "The orientation of the divider.",
|
||||
"enum": ["horizontal", "vertical"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Modal": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"entryPointChild": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component that opens the modal when interacted with (e.g., a button)."
|
||||
},
|
||||
"contentChild": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component to be displayed inside the modal."
|
||||
}
|
||||
},
|
||||
"required": ["entryPointChild", "contentChild"]
|
||||
},
|
||||
"Button": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"child": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component to display in the button, typically a Text component."
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates if this button should be styled as the primary action."
|
||||
},
|
||||
"action": {
|
||||
"type": "object",
|
||||
"description": "The client-side action to be dispatched when the button is clicked. It includes the action's name and an optional context payload.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"context": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "Defines the value to be included in the context as either a literal value or a path to a data model value (e.g. '/user/name').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"literalNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"literalBoolean": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["key", "value"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"required": ["child", "action"]
|
||||
},
|
||||
"CheckBox": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "The text to display next to the checkbox. Defines the value as either a literal value or a path to data model ('path', e.g. '/option/label').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "The current state of the checkbox (true for checked, false for unchecked). This can be a literal boolean ('literalBoolean') or a reference to a value in the data model ('path', e.g. '/filter/open').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalBoolean": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["label", "value"]
|
||||
},
|
||||
"TextField": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "The text label for the input field. This can be a literal string or a reference to a value in the data model ('path, e.g. '/user/name').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"type": "object",
|
||||
"description": "The value of the text field. This can be a literal string or a reference to a value in the data model ('path', e.g. '/user/name').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"textFieldType": {
|
||||
"type": "string",
|
||||
"description": "The type of input field to display.",
|
||||
"enum": [
|
||||
"date",
|
||||
"longText",
|
||||
"number",
|
||||
"shortText",
|
||||
"obscured"
|
||||
]
|
||||
},
|
||||
"validationRegexp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used for client-side validation of the input."
|
||||
}
|
||||
},
|
||||
"required": ["label"]
|
||||
},
|
||||
"DateTimeInput": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "The selected date and/or time value. This can be a literal string ('literalString') or a reference to a value in the data model ('path', e.g. '/user/dob').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableDate": {
|
||||
"type": "boolean",
|
||||
"description": "If true, allows the user to select a date."
|
||||
},
|
||||
"enableTime": {
|
||||
"type": "boolean",
|
||||
"description": "If true, allows the user to select a time."
|
||||
},
|
||||
"outputFormat": {
|
||||
"type": "string",
|
||||
"description": "The desired format for the output string after a date or time is selected."
|
||||
}
|
||||
},
|
||||
"required": ["value"]
|
||||
},
|
||||
"MultipleChoice": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"selections": {
|
||||
"type": "object",
|
||||
"description": "The currently selected values for the component. This can be a literal array of strings or a path to an array in the data model('path', e.g. '/hotel/options').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalArray": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"description": "An array of available options for the user to choose from.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "The text to display for this option. This can be a literal string or a reference to a value in the data model (e.g. '/option/label').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "The value to be associated with this option when selected."
|
||||
}
|
||||
},
|
||||
"required": ["label", "value"]
|
||||
}
|
||||
},
|
||||
"maxAllowedSelections": {
|
||||
"type": "integer",
|
||||
"description": "The maximum number of options that the user is allowed to select."
|
||||
}
|
||||
},
|
||||
"required": ["selections", "options"]
|
||||
},
|
||||
"Slider": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "The current value of the slider. This can be a literal number ('literalNumber') or a reference to a value in the data model ('path', e.g. '/restaurant/cost').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minValue": {
|
||||
"type": "number",
|
||||
"description": "The minimum value of the slider."
|
||||
},
|
||||
"maxValue": {
|
||||
"type": "number",
|
||||
"description": "The maximum value of the slider."
|
||||
}
|
||||
},
|
||||
"required": ["value"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["id", "component"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["surfaceId", "components"]
|
||||
},
|
||||
"dataModelUpdate": {
|
||||
"type": "object",
|
||||
"description": "Updates the data model for a surface.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface this data model update applies to."
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "An optional path to a location within the data model (e.g., '/user/name'). If omitted, or set to '/', the entire data model will be replaced."
|
||||
},
|
||||
"contents": {
|
||||
"type": "array",
|
||||
"description": "An array of data entries. Each entry must contain a 'key' and exactly one corresponding typed 'value*' property.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A single data entry. Exactly one 'value*' property should be provided alongside the key.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "The key for this data entry."
|
||||
},
|
||||
"valueString": {
|
||||
"type": "string"
|
||||
},
|
||||
"valueNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"valueBoolean": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"valueMap": {
|
||||
"description": "Represents a map as an adjacency list.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "One entry in the map. Exactly one 'value*' property should be provided alongside the key.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"valueString": {
|
||||
"type": "string"
|
||||
},
|
||||
"valueNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"valueBoolean": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["key"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["key"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["contents", "surfaceId"]
|
||||
},
|
||||
"deleteSurface": {
|
||||
"type": "object",
|
||||
"description": "Signals the client to delete the surface identified by 'surfaceId'.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"description": "The unique identifier for the UI surface to be deleted."
|
||||
}
|
||||
},
|
||||
"required": ["surfaceId"]
|
||||
}
|
||||
}
|
||||
}
|
||||
685
vendor/a2ui/specification/0.8/json/standard_catalog_definition.json
vendored
Normal file
685
vendor/a2ui/specification/0.8/json/standard_catalog_definition.json
vendored
Normal file
@@ -0,0 +1,685 @@
|
||||
{
|
||||
"components": {
|
||||
"Text": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "object",
|
||||
"description": "The text content to display. This can be a literal string or a reference to a value in the data model ('path', e.g., '/doc/title'). While simple Markdown formatting is supported (i.e. without HTML, images, or links), utilizing dedicated UI components is generally preferred for a richer and more structured presentation.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"usageHint": {
|
||||
"type": "string",
|
||||
"description": "A hint for the base text style. One of:\n- `h1`: Largest heading.\n- `h2`: Second largest heading.\n- `h3`: Third largest heading.\n- `h4`: Fourth largest heading.\n- `h5`: Fifth largest heading.\n- `caption`: Small text for captions.\n- `body`: Standard body text.",
|
||||
"enum": [
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"caption",
|
||||
"body"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["text"]
|
||||
},
|
||||
"Image": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "object",
|
||||
"description": "The URL of the image to display. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/thumbnail/url').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fit": {
|
||||
"type": "string",
|
||||
"description": "Specifies how the image should be resized to fit its container. This corresponds to the CSS 'object-fit' property.",
|
||||
"enum": [
|
||||
"contain",
|
||||
"cover",
|
||||
"fill",
|
||||
"none",
|
||||
"scale-down"
|
||||
]
|
||||
},
|
||||
"usageHint": {
|
||||
"type": "string",
|
||||
"description": "A hint for the image size and style. One of:\n- `icon`: Small square icon.\n- `avatar`: Circular avatar image.\n- `smallFeature`: Small feature image.\n- `mediumFeature`: Medium feature image.\n- `largeFeature`: Large feature image.\n- `header`: Full-width, full bleed, header image.",
|
||||
"enum": [
|
||||
"icon",
|
||||
"avatar",
|
||||
"smallFeature",
|
||||
"mediumFeature",
|
||||
"largeFeature",
|
||||
"header"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["url"]
|
||||
},
|
||||
"Icon": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "object",
|
||||
"description": "The name of the icon to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/form/submit').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"accountCircle",
|
||||
"add",
|
||||
"arrowBack",
|
||||
"arrowForward",
|
||||
"attachFile",
|
||||
"calendarToday",
|
||||
"call",
|
||||
"camera",
|
||||
"check",
|
||||
"close",
|
||||
"delete",
|
||||
"download",
|
||||
"edit",
|
||||
"event",
|
||||
"error",
|
||||
"favorite",
|
||||
"favoriteOff",
|
||||
"folder",
|
||||
"help",
|
||||
"home",
|
||||
"info",
|
||||
"locationOn",
|
||||
"lock",
|
||||
"lockOpen",
|
||||
"mail",
|
||||
"menu",
|
||||
"moreVert",
|
||||
"moreHoriz",
|
||||
"notificationsOff",
|
||||
"notifications",
|
||||
"payment",
|
||||
"person",
|
||||
"phone",
|
||||
"photo",
|
||||
"print",
|
||||
"refresh",
|
||||
"search",
|
||||
"send",
|
||||
"settings",
|
||||
"share",
|
||||
"shoppingCart",
|
||||
"star",
|
||||
"starHalf",
|
||||
"starOff",
|
||||
"upload",
|
||||
"visibility",
|
||||
"visibilityOff",
|
||||
"warning"
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
},
|
||||
"Video": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "object",
|
||||
"description": "The URL of the video to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/video/url').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["url"]
|
||||
},
|
||||
"AudioPlayer": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "object",
|
||||
"description": "The URL of the audio to be played. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/song/url').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the audio, such as a title or summary. This can be a literal string or a reference to a value in the data model ('path', e.g. '/song/title').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["url"]
|
||||
},
|
||||
"Row": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "object",
|
||||
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"explicitList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"componentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataBinding": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["componentId", "dataBinding"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"distribution": {
|
||||
"type": "string",
|
||||
"description": "Defines the arrangement of children along the main axis (horizontally). This corresponds to the CSS 'justify-content' property.",
|
||||
"enum": [
|
||||
"center",
|
||||
"end",
|
||||
"spaceAround",
|
||||
"spaceBetween",
|
||||
"spaceEvenly",
|
||||
"start"
|
||||
]
|
||||
},
|
||||
"alignment": {
|
||||
"type": "string",
|
||||
"description": "Defines the alignment of children along the cross axis (vertically). This corresponds to the CSS 'align-items' property.",
|
||||
"enum": ["start", "center", "end", "stretch"]
|
||||
}
|
||||
},
|
||||
"required": ["children"]
|
||||
},
|
||||
"Column": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "object",
|
||||
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"explicitList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"componentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataBinding": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["componentId", "dataBinding"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"distribution": {
|
||||
"type": "string",
|
||||
"description": "Defines the arrangement of children along the main axis (vertically). This corresponds to the CSS 'justify-content' property.",
|
||||
"enum": [
|
||||
"start",
|
||||
"center",
|
||||
"end",
|
||||
"spaceBetween",
|
||||
"spaceAround",
|
||||
"spaceEvenly"
|
||||
]
|
||||
},
|
||||
"alignment": {
|
||||
"type": "string",
|
||||
"description": "Defines the alignment of children along the cross axis (horizontally). This corresponds to the CSS 'align-items' property.",
|
||||
"enum": ["center", "end", "start", "stretch"]
|
||||
}
|
||||
},
|
||||
"required": ["children"]
|
||||
},
|
||||
"List": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"children": {
|
||||
"type": "object",
|
||||
"description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"explicitList": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"componentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"dataBinding": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["componentId", "dataBinding"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"description": "The direction in which the list items are laid out.",
|
||||
"enum": ["vertical", "horizontal"]
|
||||
},
|
||||
"alignment": {
|
||||
"type": "string",
|
||||
"description": "Defines the alignment of children along the cross axis.",
|
||||
"enum": ["start", "center", "end", "stretch"]
|
||||
}
|
||||
},
|
||||
"required": ["children"]
|
||||
},
|
||||
"Card": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"child": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component to be rendered inside the card."
|
||||
}
|
||||
},
|
||||
"required": ["child"]
|
||||
},
|
||||
"Tabs": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"tabItems": {
|
||||
"type": "array",
|
||||
"description": "An array of objects, where each object defines a tab with a title and a child component.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "object",
|
||||
"description": "The tab title. Defines the value as either a literal value or a path to data model value (e.g. '/options/title').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"child": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["title", "child"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["tabItems"]
|
||||
},
|
||||
"Divider": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"axis": {
|
||||
"type": "string",
|
||||
"description": "The orientation of the divider.",
|
||||
"enum": ["horizontal", "vertical"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Modal": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"entryPointChild": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component that opens the modal when interacted with (e.g., a button)."
|
||||
},
|
||||
"contentChild": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component to be displayed inside the modal."
|
||||
}
|
||||
},
|
||||
"required": ["entryPointChild", "contentChild"]
|
||||
},
|
||||
"Button": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"child": {
|
||||
"type": "string",
|
||||
"description": "The ID of the component to display in the button, typically a Text component."
|
||||
},
|
||||
"primary": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates if this button should be styled as the primary action."
|
||||
},
|
||||
"action": {
|
||||
"type": "object",
|
||||
"description": "The client-side action to be dispatched when the button is clicked. It includes the action's name and an optional context payload.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"context": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "Defines the value to be included in the context as either a literal value or a path to a data model value (e.g. '/user/name').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"literalNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"literalBoolean": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["key", "value"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"required": ["child", "action"]
|
||||
},
|
||||
"CheckBox": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "The text to display next to the checkbox. Defines the value as either a literal value or a path to data model ('path', e.g. '/option/label').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "The current state of the checkbox (true for checked, false for unchecked). This can be a literal boolean ('literalBoolean') or a reference to a value in the data model ('path', e.g. '/filter/open').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalBoolean": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["label", "value"]
|
||||
},
|
||||
"TextField": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "The text label for the input field. This can be a literal string or a reference to a value in the data model ('path, e.g. '/user/name').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"type": "object",
|
||||
"description": "The value of the text field. This can be a literal string or a reference to a value in the data model ('path', e.g. '/user/name').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"textFieldType": {
|
||||
"type": "string",
|
||||
"description": "The type of input field to display.",
|
||||
"enum": [
|
||||
"date",
|
||||
"longText",
|
||||
"number",
|
||||
"shortText",
|
||||
"obscured"
|
||||
]
|
||||
},
|
||||
"validationRegexp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used for client-side validation of the input."
|
||||
}
|
||||
},
|
||||
"required": ["label"]
|
||||
},
|
||||
"DateTimeInput": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "The selected date and/or time value. This can be a literal string ('literalString') or a reference to a value in the data model ('path', e.g. '/user/dob').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableDate": {
|
||||
"type": "boolean",
|
||||
"description": "If true, allows the user to select a date."
|
||||
},
|
||||
"enableTime": {
|
||||
"type": "boolean",
|
||||
"description": "If true, allows the user to select a time."
|
||||
},
|
||||
"outputFormat": {
|
||||
"type": "string",
|
||||
"description": "The desired format for the output string after a date or time is selected."
|
||||
}
|
||||
},
|
||||
"required": ["value"]
|
||||
},
|
||||
"MultipleChoice": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"selections": {
|
||||
"type": "object",
|
||||
"description": "The currently selected values for the component. This can be a literal array of strings or a path to an array in the data model('path', e.g. '/hotel/options').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalArray": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"description": "An array of available options for the user to choose from.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "The text to display for this option. This can be a literal string or a reference to a value in the data model (e.g. '/option/label').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalString": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "The value to be associated with this option when selected."
|
||||
}
|
||||
},
|
||||
"required": ["label", "value"]
|
||||
}
|
||||
},
|
||||
"maxAllowedSelections": {
|
||||
"type": "integer",
|
||||
"description": "The maximum number of options that the user is allowed to select."
|
||||
}
|
||||
},
|
||||
"required": ["selections", "options"]
|
||||
},
|
||||
"Slider": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "The current value of the slider. This can be a literal number ('literalNumber') or a reference to a value in the data model ('path', e.g. '/restaurant/cost').",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"literalNumber": {
|
||||
"type": "number"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minValue": {
|
||||
"type": "number",
|
||||
"description": "The minimum value of the slider."
|
||||
},
|
||||
"maxValue": {
|
||||
"type": "number",
|
||||
"description": "The maximum value of the slider."
|
||||
}
|
||||
},
|
||||
"required": ["value"]
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"font": {
|
||||
"type": "string",
|
||||
"description": "The primary font for the UI."
|
||||
},
|
||||
"primaryColor": {
|
||||
"type": "string",
|
||||
"description": "The primary UI color as a hexadecimal code (e.g., '#00BFFF').",
|
||||
"pattern": "^#[0-9a-fA-F]{6}$"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user