Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@
"type": "string",
"description": "The primary UI color as a hexadecimal code (e.g., '#00BFFF').",
"pattern": "^#[0-9a-fA-F]{6}$"
}
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add this here, please also update the specification/0.8/a2ui_protocol.md and the JSON schema in specification/0.8/json. That should be the source of truth for the schema.

In fact, this code should probably be reading those schemas directly and not defining a copy that is likely to get out of date.

"logoUrl": {
"type": "string",
"description": "The URL of the brand logo."
},
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
RESTAURANT_UI_EXAMPLES = """
---BEGIN SINGLE_COLUMN_LIST_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column" }} }},
{{ "surfaceUpdate": {{
"surfaceId": "default",
"components": [
Expand Down Expand Up @@ -63,7 +63,7 @@

---BEGIN TWO_COLUMN_LIST_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column" }} }},
{{ "surfaceUpdate": {{
"surfaceId": "default",
"components": [
Expand Down Expand Up @@ -122,7 +122,7 @@

---BEGIN BOOKING_FORM_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "booking-form", "root": "booking-form-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "beginRendering": {{ "surfaceId": "booking-form", "root": "booking-form-column" }} }},
{{ "surfaceUpdate": {{
"surfaceId": "booking-form",
"components": [
Expand Down Expand Up @@ -155,7 +155,7 @@

---BEGIN CONFIRMATION_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "confirmation", "root": "confirmation-card", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "beginRendering": {{ "surfaceId": "confirmation", "root": "confirmation-card" }} }},
{{ "surfaceUpdate": {{
"surfaceId": "confirmation",
"components": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

# The A2UI schema remains constant for all A2UI responses.
A2UI_SCHEMA = r'''
A2UI_SCHEMA = r"""
{
"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'.",
Expand Down Expand Up @@ -43,7 +43,12 @@
"type": "string",
"description": "The primary UI color as a hexadecimal code (e.g., '#00BFFF').",
"pattern": "^#[0-9a-fA-F]{6}$"
},
"logoUrl": {
"type": "string",
"description": "The URL of the brand logo or hero image."
}

}
}
},
Expand Down Expand Up @@ -787,10 +792,18 @@
}
}
}
'''
"""

from a2ui_examples import RESTAURANT_UI_EXAMPLES

# Configurable Theme Constants
# Uncomment or modify these values to apply server-driven styling.
THEME_CONFIG = {
# "primaryColor": "#ea1277", # Example: Pink/Red
# "logoUrl": "{base_url}/static/logo.png",
# "font": "Courier New", # Example: Roboto, Outfit, etc.
}


def get_ui_prompt(base_url: str, examples: str) -> str:
"""
Expand All @@ -806,6 +819,15 @@ def get_ui_prompt(base_url: str, examples: str) -> str:
# The f-string substitution for base_url happens here, at runtime.
formatted_examples = examples.format(base_url=base_url)

# Construct dynamic style instructions based on THEME_CONFIG
style_instructions = ""
if THEME_CONFIG:
style_instructions = " - When sending a `beginRendering` message, you MUST include the `styles` object with:\n"
for key, value in THEME_CONFIG.items():
style_instructions += f' - `{key}`: "{value}"\n'
else:
style_instructions = ' - When sending a `beginRendering` message, you MAY include an empty `styles` object: "styles": {}\n'

return f"""
You are a helpful restaurant finding assistant. Your final output MUST be a a2ui UI JSON response.

Expand All @@ -816,6 +838,7 @@ def get_ui_prompt(base_url: str, examples: str) -> str:
4. The JSON part MUST validate against the A2UI JSON SCHEMA provided below.

--- UI TEMPLATE RULES ---
{style_instructions}
- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `dataModelUpdate.contents` array (e.g., as a `valueMap` for the "items" key).
- If the number of restaurants is 5 or fewer, you MUST use the `SINGLE_COLUMN_LIST_EXAMPLE` template.
- If the number of restaurants is more than 5, you MUST use the `TWO_COLUMN_LIST_EXAMPLE` template.
Expand Down
41 changes: 41 additions & 0 deletions samples/client/lit/shell/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,11 +694,52 @@ export class A2UILayoutEditor extends SignalWatcher(LitElement) {
async #sendAndProcessMessage(request) {
const messages = await this.#sendMessage(request);

// Apply server-side styles if present in beginRendering
for (const msg of messages) {
if ('beginRendering' in msg && msg.beginRendering.styles) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I think you can use a nullish coalescing operator here.

Suggested change
if ('beginRendering' in msg && msg.beginRendering.styles) {
if (msg.beginRendering?.styles) {

this.#applyServerStyles(msg.beginRendering.styles);
}
}

this.#lastMessages = messages;
this.#processor.clearSurfaces();
this.#processor.processMessages(messages);
}

#applyServerStyles(styles: any) {
const root = document.documentElement;

// 1. Primary Color
if (styles.primaryColor) {
root.style.setProperty('--primary-color', styles.primaryColor);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we validating these styles anywhere? primaryColor could be like truly any value as far as I can tell.

// Generate a simple dim variant for now (could be more sophisticated)
root.style.setProperty('--p-60', styles.primaryColor);

// Update gradient to use the new primary color
// We'll create a gradient from a lighter version to the primary color
// For now, let's just use the primary color and a shifted hue for a simple dynamic gradient
root.style.setProperty('--primary-gradient', `linear-gradient(135deg, ${styles.primaryColor} 0%, ${styles.primaryColor}dd 100%)`);
}

// 2. Font Family
if (styles.font) {
root.style.setProperty('--bb-font-family', styles.font);
root.style.setProperty('font-family', styles.font);
}



// 3. Logo / Hero Image
if (styles.logoUrl) {
// Update the config dynamically to show the new hero image
this.config = {
...this.config,
heroImage: styles.logoUrl,
heroImageDark: styles.logoUrl, // Use same for dark mode unless specified otherwise
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a way to specify otherwise, right? Should we say we're just going to set this for both until we have a way to specify that?

};
}
}



snackbar(
Expand Down
2 changes: 1 addition & 1 deletion samples/client/lit/shell/theme/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

/* Global overrides */
* {
font-family: 'Outfit', sans-serif !important;
font-family: var(--bb-font-family, 'Outfit', sans-serif) !important;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoa, I don't think I've encountered this file before. Why these overrides as opposed to setting this in html {} or body {}?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to be rewired by Paul..hopefully this PR will not be needed then..

}

/* Card Styling */
Expand Down
Loading