Skip to content

Commit 6becbce

Browse files
authored
Web refactor (#34)
1 parent 1e052ad commit 6becbce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+2831
-2414
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ node_modules
55
dist
66
.env
77
.vscode
8+
__pycache__
9+
*.pyc

a2a_samples/a2ui_restaurant_finder/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def get_processing_message(self) -> str:
5555

5656
def _build_agent(self, use_ui: bool) -> LlmAgent:
5757
"""Builds the LLM agent for the restaurant agent."""
58-
LITELLM_MODEL = os.getenv("LITELLM_MODEL", "gemini-2.5-flash")
58+
LITELLM_MODEL = os.getenv("LITELLM_MODEL", "gemini/gemini-2.5-flash")
5959

6060
if use_ui:
6161
# Construct the full prompt with UI instructions, examples, and schema

tsconfig.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

web/README.md renamed to web/editor/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This is a UI to generate and visualize A2UI responses.
1313
2. In the `.env` file place your Gemini API Key: `GEMINI_API_KEY=<your key>`.
1414
3. Install the dependencies: `npm i`
1515
4. Run the dev server: `npm run dev`
16-
5. Open http://localhost:5173/editor/ for the Editor or http://localhost:5173/restaurant/ for the A2A UI example (note that this needs an A2A server running)
16+
5. Open http://localhost:5173/ for the Editor
1717

1818
If you do not have a `.env` file with a GEMINI_API_KEY value set the developer
1919
server will not start. It will instead provide you with an error message

web/editor/client.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
limitations under the License.
1515
*/
1616

17-
import DefaultCatalog from "../src/0.8/catalog/default-catalog.json";
18-
import { A2UIClientEventMessage } from "../src/0.8/types/client-event";
17+
import { v0_8 } from "@a2ui/web-lib";
1918

2019
export class A2UIClient {
2120
#ready: Promise<void> = Promise.resolve();
@@ -29,7 +28,7 @@ export class A2UIClient {
2928
(async () => {
3029
await this.#send({
3130
clientUiCapabilities: {
32-
dynamicCatalog: DefaultCatalog,
31+
dynamicCatalog: v0_8.Schemas.DefaultCatalog,
3332
},
3433
});
3534
console.log("A2UI Client Handshake");
@@ -62,7 +61,7 @@ export class A2UIClient {
6261
}
6362

6463
async #send<T extends { role: "model"; parts: Array<{ text: string }> }>(
65-
message: A2UIClientEventMessage
64+
message: v0_8.Types.A2UIClientEventMessage
6665
) {
6766
const response = await fetch("/a2ui", {
6867
body: JSON.stringify(message),

web/editor/editor.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,11 @@ import {
2424
HTMLTemplateResult,
2525
} from "lit";
2626
import { customElement, query, state } from "lit/decorators.js";
27-
import { A2UIModelProcessor } from "../src/0.8/data/model-processor";
28-
import { A2UIProtocolMessage, Theme } from "../src/0.8/types/types";
2927
import { repeat } from "lit/directives/repeat.js";
3028
import { SignalWatcher } from "@lit-labs/signals";
3129
import { provide } from "@lit/context";
32-
import { themeContext } from "../src/0.8/ui/context/theme";
3330
import { theme as uiTheme } from "./theme/theme.js";
3431
import "./ui/ui.js";
35-
import * as UI from "../src/0.8/ui/ui.js";
3632
import { classMap } from "lit/directives/class-map.js";
3733
import { Snackbar } from "./ui/snackbar.js";
3834
import { ref } from "lit/directives/ref.js";
@@ -43,6 +39,7 @@ import {
4339
SnackType,
4440
} from "./types/types.js";
4541
import { DrawableCanvas } from "./ui/ui.js";
42+
import { v0_8 } from "@a2ui/web-lib";
4643

4744
type UserMode = "upload" | "sketch";
4845
type RenderMode = "surfaces" | "messages";
@@ -52,8 +49,8 @@ const RENDER_MODE_KEY = "ui-render-mode";
5249

5350
@customElement("a2ui-layout-editor")
5451
export class A2UILayoutEditor extends SignalWatcher(LitElement) {
55-
@provide({ context: themeContext })
56-
accessor theme: Theme = uiTheme;
52+
@provide({ context: v0_8.UI.Context.themeContext })
53+
accessor theme: v0_8.Types.Theme = uiTheme;
5754

5855
@state()
5956
accessor #ready = false;
@@ -80,10 +77,10 @@ export class A2UILayoutEditor extends SignalWatcher(LitElement) {
8077
accessor #drawableCanvas: DrawableCanvas | null = null;
8178

8279
@state()
83-
accessor #lastMessages: A2UIProtocolMessage[] | null = null;
80+
accessor #lastMessages: v0_8.Types.A2UIProtocolMessage[] | null = null;
8481

8582
static styles = [
86-
UI.Styles.all,
83+
v0_8.UI.Styles.all,
8784
css`
8885
:host {
8986
display: grid;
@@ -451,7 +448,7 @@ export class A2UILayoutEditor extends SignalWatcher(LitElement) {
451448
}
452449
#renderMode: RenderMode = "surfaces";
453450

454-
#processor = new A2UIModelProcessor();
451+
#processor = new v0_8.Data.A2UIModelProcessor();
455452
#a2uiClient = new A2UIClient();
456453

457454
constructor() {
@@ -469,7 +466,7 @@ export class A2UILayoutEditor extends SignalWatcher(LitElement) {
469466
async #processRequest(
470467
image?: HTMLImageElement | null,
471468
instructions?: string
472-
): Promise<A2UIProtocolMessage[]> {
469+
): Promise<v0_8.Types.A2UIProtocolMessage[]> {
473470
try {
474471
this.#requesting = true;
475472
const response = await this.#a2uiClient.sendMultipart(
@@ -478,8 +475,8 @@ export class A2UILayoutEditor extends SignalWatcher(LitElement) {
478475
);
479476

480477
const message = JSON.parse(response.parts[0].text) as
481-
| A2UIProtocolMessage
482-
| A2UIProtocolMessage[];
478+
| v0_8.Types.A2UIProtocolMessage
479+
| v0_8.Types.A2UIProtocolMessage[];
483480

484481
if (Array.isArray(message)) {
485482
return message;
Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,13 @@
1717
import { IncomingMessage, ServerResponse } from "http";
1818
import { Plugin, ViteDevServer } from "vite";
1919
import { GoogleGenAI } from "@google/genai";
20-
import * as JSONSchema from "jsonschema";
21-
import ClientEvent from "../schemas/client-event.json";
22-
import {
23-
A2UIClientEventMessage,
24-
ClientCapabilitiesDynamic,
25-
} from "../types/client-event";
20+
import { v0_8 } from "@a2ui/web-lib";
2621
import { createA2UIPrompt, createImageParsePrompt } from "./prompts";
27-
import { isObject } from "../data/guards";
2822

2923
// TODO: Reenable.
30-
// import A2UIProtocolMessage from "../schemas/a2ui-message.json";
24+
// import { A2UIProtocolMessage } from "../schemas/a2ui-message.js";
3125

32-
const validator = new JSONSchema.Validator();
33-
validator.addSchema(ClientEvent);
34-
35-
let catalog: ClientCapabilitiesDynamic | null = null;
26+
let catalog: v0_8.Types.ClientCapabilitiesDynamic | null = null;
3627
let ai: GoogleGenAI;
3728
export const plugin = (): Plugin => {
3829
if (!("GEMINI_API_KEY" in process.env && process.env.GEMINI_KEY !== "")) {
@@ -58,18 +49,10 @@ export const plugin = (): Plugin => {
5849

5950
req.on("end", async () => {
6051
try {
61-
const payload = JSON.parse(contents) as A2UIClientEventMessage;
52+
const payload = JSON.parse(
53+
contents
54+
) as v0_8.Types.A2UIClientEventMessage;
6255
if (payload.clientUiCapabilities || payload.userAction) {
63-
const payloadValidation = validator.validate(
64-
payload,
65-
ClientEvent
66-
);
67-
68-
if (payloadValidation.errors.length > 0) {
69-
console.warn(payloadValidation);
70-
throw new Error("Invalid payload");
71-
}
72-
7356
if (payload.clientUiCapabilities) {
7457
if ("dynamicCatalog" in payload.clientUiCapabilities) {
7558
catalog = payload.clientUiCapabilities.dynamicCatalog;
@@ -101,7 +84,7 @@ export const plugin = (): Plugin => {
10184
return;
10285
}
10386

104-
if (isObject(payload.request)) {
87+
if (v0_8.Data.Guards.isObject(payload.request)) {
10588
const request = payload.request as {
10689
imageData?: string;
10790
instructions: string;
@@ -156,13 +139,16 @@ export const plugin = (): Plugin => {
156139
model: "gemini-2.5-flash",
157140
contents: prompt,
158141
config: {
159-
// TODO: Enable structured output.
160142
// responseMimeType: "application/json",
161-
// responseJsonSchema: A2UIProtocolMessage,
162-
systemInstruction: `Please return a valid A2UI Protocol
163-
Message object necessary to build the satisfy the user
164-
request. If no data is provided create some. If there are
165-
any URLs you must make them absolute and begin with a /.
143+
// responseJsonSchema: {
144+
// type: "array",
145+
// items: A2UIProtocolMessage,
146+
// },
147+
systemInstruction: `Please return a valid array
148+
necessary to satisfy the user request. If no data is
149+
provided create some. If there are any URLs you must
150+
make them absolute and begin with a /.
151+
166152
Nothing should ever be loaded from a remote source.
167153
168154
You are working as part of an AI system, so no chit-chat and

web/editor/middleware/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
export * as GeminiMiddleware from "./gemini.js";
18+
export * as ImageFallbackMiddleware from "./image-fallback.js";
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@
1414
limitations under the License.
1515
*/
1616

17-
import { ClientCapabilitiesDynamic } from "../types/client-event";
18-
import A2UIProtocolMessage from "../schemas/a2ui-message.json";
17+
import { v0_8 } from "@a2ui/web-lib";
1918

2019
export function createImageParsePrompt(
21-
catalog: ClientCapabilitiesDynamic,
20+
catalog: v0_8.Types.ClientCapabilitiesDynamic,
2221
content: {
2322
inlineData: {
2423
mimeType: string;
@@ -64,7 +63,7 @@ export function createImageParsePrompt(
6463
}
6564

6665
export function createA2UIPrompt(
67-
catalog: ClientCapabilitiesDynamic,
66+
catalog: v0_8.Types.ClientCapabilitiesDynamic,
6867
imageDescription: string,
6968
instructions: string
7069
) {
@@ -99,7 +98,7 @@ export function createA2UIPrompt(
9998
`The user's layout request is: "${combinedInstructions.join('" and "')}"`,
10099
`The Component Catalog you can use is: ${JSON.stringify(catalog)}`,
101100
`The A2UI Protocol Message Schema: "${JSON.stringify(
102-
A2UIProtocolMessage
101+
v0_8.Schemas.A2UIProtocolMessage
103102
)}"`,
104103

105104
`Please return a valid A2UI Protocol Message object necessary to build the

0 commit comments

Comments
 (0)