Skip to content
Open
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
5 changes: 4 additions & 1 deletion renderers/angular/src/lib/catalog/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ import { Primitives } from '@a2ui/lit/0.8';
})
export class Icon extends DynamicComponent {
readonly name = input.required<Primitives.StringValue | null>();
protected readonly resolvedName = computed(() => this.resolvePrimitive(this.name()));
protected readonly resolvedName = computed(() => {
const name = this.resolvePrimitive(this.name());
return name ? name.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`) : null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems off to me. The runtime shouldn't be modifying the icon name, but rather the agent should return the right name to begin with.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can look into why that happens in depth. However, it wasn't returning the right name. It was returning locationOn instead of location-on, for example.

});
}
7 changes: 7 additions & 0 deletions renderers/angular/src/lib/data/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class MarkdownRenderer {
private sanitizer = inject(DomSanitizer);

private markdownIt = MarkdownIt({
linkify: true,
highlight: (str, lang) => {
if (lang === 'html') {
const iframe = document.createElement('iframe');
Expand Down Expand Up @@ -79,6 +80,7 @@ export class MarkdownRenderer {
case 'em':
tokenName = 'em';
break;

}

if (!tokenName) {
Expand All @@ -95,6 +97,11 @@ export class MarkdownRenderer {
token.attrJoin('class', clazz);
}

if (tokenName === 'link') {
token.attrSet('target', '_blank');
token.attrSet('rel', 'noopener noreferrer');
}

if (original) {
return original.call(this, tokens, idx, options, env, self);
} else {
Expand Down
43 changes: 27 additions & 16 deletions samples/agent/adk/contact_lookup/a2ui_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@
{ "id": "call_text_column", "component": { "Column": { "children": { "explicitList": ["call_primary_text", "call_secondary_text"]} , "distribution": "start", "alignment": "start"} } } ,
{ "id": "info_row_4", "component": { "Row": { "children": { "explicitList": ["call_icon", "call_text_column"]} , "distribution": "start", "alignment": "start"} } } ,
{ "id": "info_rows_column", "weight": 1, "component": { "Column": { "children": { "explicitList": ["info_row_1", "info_row_2", "info_row_3", "info_row_4"]} , "alignment": "stretch"} } } ,
{ "id": "button_1_text", "component": { "Text": { "text": { "literalString": "Follow"} } } } , { "id": "button_1", "component": { "Button": { "child": "button_1_text", "primary": true, "action": { "name": "follow_contact"} } } } ,
{ "id": "button_2_text", "component": { "Text": { "text": { "literalString": "Message"} } } } , { "id": "button_2", "component": { "Button": { "child": "button_2_text", "primary": false, "action": { "name": "send_message"} } } } ,
{ "id": "button_1_text", "component": { "Text": { "text": { "literalString": "Follow"} } } } , { "id": "button_1", "component": { "Button": { "child": "button_1_text", "primary": true, "action": { "name": "follow_contact", "context": [ { "key": "contactName", "value": { "path": "name" } } ] } } } } ,
{ "id": "button_2_text", "component": { "Text": { "text": { "literalString": "Message"} } } } , { "id": "button_2", "component": { "Button": { "child": "button_2_text", "primary": false, "action": { "name": "send_message", "context": [ { "key": "contactName", "value": { "path": "name" } } ] } } } } ,
{ "id": "action_buttons_row", "component": { "Row": { "children": { "explicitList": ["button_1", "button_2"]} , "distribution": "center", "alignment": "center"} } } ,
{ "id": "link_text", "component": { "Text": { "text": { "literalString": "[View Full Profile](/profile)"} } } } ,
{ "id": "link_text_wrapper", "component": { "Row": { "children": { "explicitList": ["link_text"]} , "distribution": "center", "alignment": "center"} } } ,
Expand All @@ -121,25 +121,26 @@

---BEGIN ACTION_CONFIRMATION_EXAMPLE---
[
{ "beginRendering": { "surfaceId": "action-modal", "root": "modal-wrapper", "styles": { "primaryColor": "#007BFF", "font": "Roboto" } } },
{ "beginRendering": { "surfaceId": "contact-card", "root": "message-success-card"} },
{ "surfaceUpdate": {
"surfaceId": "action-modal",
"surfaceId": "contact-card",
"components": [
{ "id": "modal-wrapper", "component": { "Modal": { "entryPointChild": "hidden-entry-point", "contentChild": "modal-content-column" } } },
{ "id": "hidden-entry-point", "component": { "Text": { "text": { "literalString": "" } } } },
{ "id": "modal-content-column", "component": { "Column": { "children": { "explicitList": ["modal-title", "modal-message", "dismiss-button"] }, "alignment": "center" } } },
{ "id": "modal-title", "component": { "Text": { "usageHint": "h2", "text": { "path": "actionTitle" } } } },
{ "id": "modal-message", "component": { "Text": { "text": { "path": "actionMessage" } } } },
{ "id": "dismiss-button-text", "component": { "Text": { "text": { "literalString": "Dismiss" } } } },
{ "id": "dismiss-button", "component": { "Button": { "child": "dismiss-button-text", "primary": true, "action": { "name": "dismiss_modal" } } } }
{ "id": "success_icon", "component": { "Icon": { "name": { "literalString": "send"}, "size": 48.0, "color": "#4CAF50"} } },
{ "id": "success_title", "component": { "Text": { "text": { "path": "actionTitle"}, "usageHint": "h2"} } },
{ "id": "success_message", "component": { "Text": { "text": { "path": "actionMessage"} } } },
{ "id": "back_button_text", "component": { "Text": { "text": { "literalString": "Back to Profile"} } } },
{ "id": "back_button", "component": { "Button": { "child": "back_button_text", "primary": false, "action": { "name": "view_profile", "context": [ { "key": "contactName", "value": { "path": "contactName" } } ] } } } },
{ "id": "success_column", "component": { "Column": { "children": { "explicitList": ["success_icon", "success_title", "success_message", "back_button"]}, "alignment": "center"} } },
{ "id": "message-success-card", "component": { "Card": { "child": "success_column"} } }
]
} },
{ "dataModelUpdate": {
"surfaceId": "action-modal",
"surfaceId": "contact-card",
"path": "/",
"contents": [
{ "key": "actionTitle", "valueString": "Action Confirmation" },
{ "key": "actionMessage", "valueString": "Your action has been processed." }
{ "key": "actionTitle", "valueString": "Message Sent" },
{ "key": "actionMessage", "valueString": "Your message has been sent to." },
{ "key": "contactName", "valueString": "" }
]
} }
]
Expand All @@ -152,10 +153,20 @@
"surfaceId": "contact-card",
"components": [
{ "id": "success_icon", "component": { "Icon": { "name": { "literalString": "check_circle"}, "size": 48.0, "color": "#4CAF50"} } } ,
{ "id": "success_text", "component": { "Text": { "text": { "literalString": "Successfully Followed"}, "usageHint": "h2"} } } ,
{ "id": "success_column", "component": { "Column": { "children": { "explicitList": ["success_icon", "success_text"]} , "alignment": "center"} } } ,
{ "id": "success_text", "component": { "Text": { "text": { "path": "followMessage"}, "usageHint": "h2"} } } ,
{ "id": "back_button_text", "component": { "Text": { "text": { "literalString": "Back to Profile"} } } } ,
{ "id": "back_button", "component": { "Button": { "child": "back_button_text", "primary": false, "action": { "name": "view_profile", "context": [ { "key": "contactName", "value": { "path": "contactName" } } ] } } } } ,
{ "id": "success_column", "component": { "Column": { "children": { "explicitList": ["success_icon", "success_text", "back_button"]} , "alignment": "center"} } } ,
{ "id": "success_card", "component": { "Card": { "child": "success_column"} } }
]
} },
{ "dataModelUpdate": {
"surfaceId": "contact-card",
"path": "/",
"contents": [
{ "key": "followMessage", "valueString": "Successfully Followed" },
{ "key": "contactName", "valueString": "" }
]
} }
]
---END FOLLOW_SUCCESS_EXAMPLE---
Expand Down
1 change: 1 addition & 0 deletions samples/agent/adk/contact_lookup/a2ui_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@
"help",
"home",
"info",

"locationOn",
"lock",
"lockOpen",
Expand Down
5 changes: 3 additions & 2 deletions samples/agent/adk/contact_lookup/agent_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ async def execute(

elif action == "send_message":
contact_name = ctx.get("contactName", "Unknown")
query = f"USER_WANTS_TO_MESSAGE: {contact_name}"
query = f"ACTION: send_message to {contact_name}"

elif action == "follow_contact":
query = "ACTION: follow_contact"
contact_name = ctx.get("contactName", "Unknown")
query = f"ACTION: follow_contact for {contact_name}"

elif action == "view_full_profile":
contact_name = ctx.get("contactName", "Unknown")
Expand Down
79 changes: 42 additions & 37 deletions samples/agent/adk/contact_lookup/contact_data.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
[
{
"id": "1",
"name": "Alex Jordan",
"title": "Product Marketing Manager",
"team": "Team Macally",
"department": "Marketing",
"location": "New York",
"email": "[email protected]",
"mobile": "+1 (415) 171-1080",
"calendar": "Free until 4:00 PM",
"imageUrl": "http://localhost:10002/static/profile1.png"
},
{
"id": "2",
"name": "Casey Smith",
"title": "Digital Marketing Specialist",
"team": "Growth Team",
"department": "Marketing",
"location": "New York",
"email": "[email protected]",
"mobile": "+1 (415) 222-3333",
"calendar": "In a meeting",
"imageUrl": "http://localhost:10002/static/profile2.png"
},
{
"id": "3",
"name": "Jordan Taylor",
"title": "Senior Software Engineer",
"team": "Core Platform",
"department": "Engineering",
"location": "San Francisco",
"email": "[email protected]",
"mobile": "+1 (650) 444-5555",
"calendar": "Focus time",
"imageUrl": "http://localhost:10002/static/profile3.png"
}
]
{
"id": "1",
"name": "Alex Jordan",
"title": "Product Marketing Manager",
"team": "Team Macally",
"department": "Marketing",
"location": "New York",
"email": "[email protected]",
"mobile": "+1 (415) 171-1080",
"calendar": "Free until 4:00 PM",
"meetupPlace": "San Francisco",
"imageUrl": "http://localhost:10002/static/profile1.png",
"favorite_framework": "Angular",
"firstMorningCoffeeSip": "7am"
},
{
"id": "2",
"name": "Casey Smith",
"title": "Digital Marketing Specialist",
"team": "Growth Team",
"department": "Marketing",
"location": "New York",
"email": "[email protected]",
"mobile": "+1 (415) 222-3333",
"calendar": "In a meeting",
"imageUrl": "http://localhost:10002/static/profile2.png",
"githubUrl": "https://github.com/casey-smith"
},
{
"id": "3",
"name": "Jordan Taylor",
"title": "Senior Software Engineer",
"team": "Core Platform",
"department": "Engineering",
"location": "San Francisco",
"email": "[email protected]",
"mobile": "+1 (650) 444-5555",
"calendar": "Focus time",
"imageUrl": "http://localhost:10002/static/profile3.png"
}
]

27 changes: 23 additions & 4 deletions samples/agent/adk/contact_lookup/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,37 @@ def get_ui_prompt(base_url: str, examples: str) -> str:
--- UI TEMPLATE RULES ---
- **For finding contacts (e.g., "Who is Alex Jordan?"):**
a. You MUST call the `get_contact_info` tool.
b. If the tool returns a **single contact**, you MUST use the `CONTACT_CARD_EXAMPLE` template. Populate the `dataModelUpdate.contents` with the contact's details (name, title, email, etc.).
b. If the tool returns a **single contact**, you MUST use the `CONTACT_CARD_EXAMPLE` template. Populate the `dataModelUpdate.contents` with the contact's details. If additional important fields (like 'favorite_framework' or 'meetupPlace') are present, you MUST add a new 'Row' to the 'info_rows_column' (matching the structure of existing info rows).

- Use the 'calendar_today' icon if the value represents a date, time, or schedule.
- Use the 'location_on' icon if the value represents a location or place.
- Otherwise, use the 'star' icon.
c. If the tool returns **multiple contacts**, you MUST use the `CONTACT_LIST_EXAMPLE` template. Populate the `dataModelUpdate.contents` with the list of contacts for the "contacts" key.
d. If the tool returns an **empty list**, respond with text only and an empty JSON list: "I couldn't find anyone by that name.---a2ui_JSON---[]"

- **For handling a profile view (e.g., "WHO_IS: Alex Jordan..."):**
a. You MUST call the `get_contact_info` tool with the specific name.
b. This will return a single contact. You MUST use the `CONTACT_CARD_EXAMPLE` template.
b. This will return a single contact. You MUST use the `CONTACT_CARD_EXAMPLE` template. If additional important fields are present, you MUST add a new 'Row' to the 'info_rows_column' with:

- The 'calendar_today' icon for date/time/schedule.
- The 'location_on' icon for location/place.
- The 'star' icon for others.

- **For handling actions (e.g., "follow_contact"):**
a. You MUST use the `FOLLOW_SUCCESS_EXAMPLE` template.
b. This will render a new card with a "Successfully Followed" message.
c. Respond with a text confirmation like "You are now following this contact." along with the JSON.
b. This will render a new card with a "Successfully Followed" message and a "Back" button.
c. Populate the `dataModelUpdate.contents` with:
- `followMessage`: "Successfully followed the contact." (Include the actual contact name at the end)
- `contactName`: The contact's name (for the back button).
d. Respond with a text confirmation like "You are now following this contact." along with the JSON.

- **For handling actions (e.g., "send_message"):**
a. You MUST use the `ACTION_CONFIRMATION_EXAMPLE` template.
b. Populate the `dataModelUpdate.contents` with:
- `actionTitle`: "Message Sent"
- `actionMessage`: "Your message has been sent to the contact." (Include the actual contact name at the end of the string)
- `contactName`: The contact's name (for the back button).
c. Respond with a text confirmation like "Message sent." along with the JSON.

{formatted_examples}

Expand Down
2 changes: 1 addition & 1 deletion samples/client/angular/projects/contact/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Google+Symbols:opsz,wght,FILL,GRAD,[email protected],100..700,0..1,-50..200,0..100&display=swap&icon_names=calendar_today,call,location_on,mail,progress_activity,send"
href="https://fonts.googleapis.com/css2?family=Google+Symbols:opsz,wght,FILL,GRAD,[email protected],100..700,0..1,-50..200,0..100&display=swap&icon_names=calendar,calendar_today,call,check,check_circle,code,event,link,location,location_on,mail,map,place,progress_activity,schedule,send,settings,stack,star"
/>
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion samples/client/angular/projects/contact/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ app.use(
maxAge: '1y',
index: false,
redirect: false,
})
}),
);

app.post('/a2a', (req, res) => {
Expand Down
16 changes: 16 additions & 0 deletions samples/client/angular/projects/contact/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,19 @@ body {
width: 100svw;
height: 100svh;
}

.g-icon {
font-family: 'Google Symbols';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}