Skip to content

Commit dc38d42

Browse files
committed
feat: Add Angular v0.9 renderer and a2ui_explorer sample
1 parent c316611 commit dc38d42

64 files changed

Lines changed: 4023 additions & 53 deletions

Some content is hidden

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

.github/workflows/check_license.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@ jobs:
3838

3939
- name: Check license headers
4040
run: |
41-
addlicense -check \
41+
if ! addlicense -check \
4242
-l apache \
4343
-c "Google LLC" \
44-
.
44+
.; then
45+
echo "License check failed. To fix this, install addlicense and run it:"
46+
echo " go install github.com/google/addlicense@latest"
47+
echo " addlicense -l apache -c \"Google LLC\" ."
48+
exit 1
49+
fi

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ coverage/
2222
a2a_agents/python/a2ui_agent/src/a2ui/assets/**/*.json
2323
## new agent SDK path
2424
agent_sdks/python/src/a2ui/assets/**/*.json
25-
## Generated JS file from the strictly-typed `sandbox.ts`.
25+
## Generated JS file from the strictly-typed `sandbox.ts`.
2626
samples/client/angular/projects/orchestrator/public/sandbox_iframe/sandbox.js

renderers/angular/.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
a2ui_explorer/

renderers/angular/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ All operational data received from an external agent—including its AgentCard,
66

77
Similarly, any UI definition or data stream received must be treated as untrusted. Malicious agents could attempt to spoof legitimate interfaces to deceive users (phishing), inject malicious scripts via property values (XSS), or generate excessive layout complexity to degrade client performance (DoS). If your application supports optional embedded content (such as iframes or web views), additional care must be taken to prevent exposure to malicious external sites.
88

9-
Developer Responsibility: Failure to properly validate data and strictly sandbox rendered content can introduce severe vulnerabilities. Developers are responsible for implementing appropriate security measures—such as input sanitization, Content Security Policies (CSP), strict isolation for optional embedded content, and secure credential handling—to protect their systems and users.
9+
Developer Responsibility: Failure to properly validate data and strictly sandbox rendered content can introduce severe vulnerabilities. Developers are responsible for implementing appropriate security measures—such as input sanitization, Content Security Policies (CSP), strict isolation for optional embedded content, and secure credential handling—to protect their systems and users.
14.7 KB
Binary file not shown.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
* http://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+
import { Injectable } from '@angular/core';
18+
import { Subject } from 'rxjs';
19+
import { SurfaceGroupAction } from '@a2ui/web_core/v0_9';
20+
21+
@Injectable({ providedIn: 'root' })
22+
export class ActionDispatcher {
23+
private action$ = new Subject<SurfaceGroupAction>();
24+
actions = this.action$.asObservable();
25+
26+
dispatch(action: SurfaceGroupAction) {
27+
this.action$.next(action);
28+
}
29+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Copyright 2026 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+
* http://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+
import { Injectable } from '@angular/core';
18+
import { A2uiRendererService } from '@a2ui/angular/v0_9';
19+
import { SurfaceGroupAction, A2uiMessage } from '@a2ui/web_core/v0_9';
20+
import { ActionDispatcher } from './action-dispatcher.service';
21+
22+
/**
23+
* Context for the 'update_property' event.
24+
*/
25+
interface UpdatePropertyContext {
26+
path: string;
27+
value: any;
28+
surfaceId?: string;
29+
}
30+
31+
/**
32+
* Context for the 'submit_form' event.
33+
*/
34+
interface SubmitFormContext {
35+
[key: string]: any;
36+
name?: string;
37+
}
38+
39+
/**
40+
* A stub service that simulates an A2UI agent.
41+
* It listens for actions and responds with data model updates or new surfaces.
42+
*/
43+
@Injectable({
44+
providedIn: 'root',
45+
})
46+
export class AgentStubService {
47+
/** Log of actions received from the surface. */
48+
actionsLog: Array<{ timestamp: Date; action: SurfaceGroupAction }> = [];
49+
50+
constructor(
51+
private rendererService: A2uiRendererService,
52+
private dispatcher: ActionDispatcher,
53+
) {
54+
// Subscribe to actions dispatched by the renderer
55+
this.dispatcher.actions.subscribe((action) => this.handleAction(action));
56+
}
57+
58+
/**
59+
* Pushes actions triggered from the rendered Canvas frame through simulation.
60+
* - Logs actions into inspector event frame aggregates.
61+
* - Emulates generic server-side evaluation triggers delaying deferred updates.
62+
* - Dispatch subsequent node-tree node triggers back over `A2uiRendererService`.
63+
*/
64+
handleAction(action: SurfaceGroupAction) {
65+
console.log('[AgentStub] handleAction action:', action);
66+
this.actionsLog.push({ timestamp: new Date(), action });
67+
68+
// Simulate server processing delay
69+
setTimeout(() => {
70+
if ('event' in action) {
71+
const { name, context } = action.event;
72+
if (name === 'update_property' && context) {
73+
const { path, value, surfaceId } = context as unknown as UpdatePropertyContext;
74+
console.log(
75+
'[AgentStub] update_property path:',
76+
path,
77+
'value:',
78+
value,
79+
'surfaceId:',
80+
surfaceId,
81+
);
82+
this.rendererService.processMessages([
83+
{
84+
version: 'v0.9',
85+
updateDataModel: {
86+
surfaceId: surfaceId || action.surfaceId,
87+
path: path,
88+
value: value,
89+
},
90+
},
91+
]);
92+
} else if (name === 'submit_form' && context) {
93+
const formData = context as unknown as SubmitFormContext;
94+
const nameValue = formData.name || 'Anonymous';
95+
96+
// Respond with an update to the data model in v0.9 layout
97+
this.rendererService.processMessages([
98+
{
99+
version: 'v0.9',
100+
updateDataModel: {
101+
surfaceId: action.surfaceId,
102+
path: '/form/submitted',
103+
value: true,
104+
},
105+
},
106+
{
107+
version: 'v0.9',
108+
updateDataModel: {
109+
surfaceId: action.surfaceId,
110+
path: '/form/responseMessage',
111+
value: `Hello, ${nameValue}! Your form has been processed.`,
112+
},
113+
},
114+
]);
115+
}
116+
}
117+
}, 50); // Shorter delay for property updates
118+
}
119+
120+
/**
121+
* Initializes a demo session with an initial set of messages.
122+
*/
123+
initializeDemo(initialMessages: A2uiMessage[]) {
124+
this.rendererService.processMessages(initialMessages);
125+
}
126+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright 2026 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+
* http://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+
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
18+
19+
export const appConfig: ApplicationConfig = {
20+
providers: [provideBrowserGlobalErrorListeners()],
21+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright 2026 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+
* http://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+
import { TestBed, fakeAsync, tick } from '@angular/core/testing';
18+
import { App } from './app';
19+
20+
describe('App', () => {
21+
beforeEach(async () => {
22+
await TestBed.configureTestingModule({
23+
imports: [App],
24+
}).compileComponents();
25+
});
26+
27+
it('should create the app', () => {
28+
const fixture = TestBed.createComponent(App);
29+
const app = fixture.componentInstance;
30+
expect(app).toBeTruthy();
31+
32+
fixture.detectChanges(); // Trigger ngOnInit
33+
34+
const compiled = fixture.nativeElement as HTMLElement;
35+
expect(compiled.querySelector('.canvas-frame')).toBeTruthy();
36+
});
37+
38+
it('should render title', () => {
39+
const fixture = TestBed.createComponent(App);
40+
fixture.detectChanges();
41+
42+
const compiled = fixture.nativeElement as HTMLElement;
43+
expect(compiled.querySelector('h3')?.textContent).toContain('A2UI Examples');
44+
});
45+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright 2026 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+
* http://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+
import { Component } from '@angular/core';
18+
import { DemoComponent } from './demo.component';
19+
20+
/**
21+
* Root Component of the A2UI Angular Demo app.
22+
*
23+
* This component acts as a direct container that embeds the `<a2ui-v0-9-demo>` dashboard.
24+
* All dynamic canvas layout and agent rendering behavior is handled inside `DemoComponent`.
25+
*/
26+
@Component({
27+
selector: 'app-root',
28+
imports: [DemoComponent],
29+
template: '<a2ui-v0-9-demo></a2ui-v0-9-demo>',
30+
})
31+
export class App {}

0 commit comments

Comments
 (0)