A2A Chat Canvas 组件集成指南:在 Angular 中构建 A2A + A2UI 的聊天与画布界面

发布时间:2026/9/15 1:37:02
A2A Chat Canvas 组件集成指南:在 Angular 中构建 A2A + A2UI 的聊天与画布界面 A2A Chat Canvas 组件集成指南在 Angular 中构建 A2A A2UI 的聊天与画布界面【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2uia2a-chat-canvas是一个 Angular 组件库用于渲染 Agent通过 A2A 协议通信与用户之间的聊天界面并支持将 A2UI 渲染的表面Surface嵌入为画布Canvas。本指南以该组件在仓库中的官方 README 为骨架结合其源码与 orchestrator 示例应用的完整配置说明如何在你的 Angular 应用中配置并扩展它。读完本文你将能够实现A2aService、通过configureChatCanvasFeatures装配 A2A/A2UI/Markdown 等特性、为自定义变体注册渲染器并理解内部渲染管线的工作机制。前置准备依赖与 UI 表面组件假定宿主应用已具备以下 UI 基础设施GM3 主题Theme已安装遵循 Material 3 设计令牌规范Google Material Symbols 字体已加载供输入区、按钮等图标使用Material Symbols 的 FILL 轴0 到 1已加载允许图标以实心/描边样式切换。这些是 Chat 与 Canvas 组件正常渲染的前提需在应用级如styles.scss统一引入而非在组件内部。此外还需在package.json中声明对核心库的 peer 依赖参考 a2a-chat-canvas/package.json{ peerDependencies: { a2ui/angular: ^0.10.0, a2ui/web_core: ^0.10.0, angular/common: ^21.2.5, angular/core: ^21.2.5, angular/platform-browser: ^21.2.5 } }实现A2aService与 Agent 通信的契约组件与 Agent 服务之间的所有通信都通过A2aService接口进行该接口必须由 UI 表面提供实现。前提条件一个托管 A2AService 的端点endpoint组件通过它收发 A2A 协议消息。实现方式创建一个实现A2aService接口的新类Injectable({providedIn: root}) export class MyA2aService implements A2aService { sendMessage(parts: Part[], signal?: AbortSignal): PromiseSendMessageSuccessResponse { // ... } getAgentCard(): PromiseAgentCard { // ... } }接口契约定义如下源码见 a2a-service.tssendMessage(parts, signal?)向 Agent 发送消息parts为 A2A 的Part数组signal可选用于取消请求组件通过AbortController传入详见下文返回PromiseSendMessageSuccessResponse。getAgentCard()获取 Agent 卡片信息名称、图标等用于渲染聊天头与消息角色ChatService中通过agentCard()?.name ?? Agent回退。该接口对应的注入令牌为A2A_SERVICEInjectionTokenA2aService组件内由ChatService通过inject(A2A_SERVICE)获取。参考实现见 orchestrator 示例的 a2a-service-impl.ts这个类将在后续配置 Chat 与 Canvas 特性时被引用。配置特性configureChatCanvasFeaturesChat 与 Canvas 需要通过调用configureChatCanvasFeatures函数进行配置。该函数要求提供一个A2aService实现并允许设置额外的可选特性。它在应用引导时仅调用一次源码注释明确要求见 config.ts返回EnvironmentProviders供ApplicationConfig.providers使用。最小化配置// app.config.ts import {configureChatCanvasFeatures, usingA2aService} from src/lib/config; import {A2aServiceImpl} from path/to/code; export const appConfig: ApplicationConfig { providers: [ // ... configureChatCanvasFeatures(usingA2aService(A2aServiceImpl)), // ... ], };从源码看configureChatCanvasFeatures内部会始终注入默认的DEFAULT_PART_RESOLVERSA2UI DataPart 与默认文本 Part 的解析器和DEFAULT_RENDERERS对应的渲染器条目再叠加你传入的特性见 config.tsexport function configureChatCanvasFeatures( a2aFeature: A2aFeature, a2uiFeature: A2uiFeature, ...additionalFeatures: ReadonlyArrayExcludeChatCanvasFeatures, A2aFeature | A2uiFeature ): EnvironmentProviders { const defaultPartResolversFeature usingPartResolvers(...DEFAULT_PART_RESOLVERS); const defaultRenderersFeature usingRenderers(...DEFAULT_RENDERERS); return makeEnvironmentProviders([ [a2aFeature, a2uiFeature, defaultPartResolversFeature, defaultRenderersFeature, ...additionalFeatures] .map(feature feature.providers), ]); }注意签名要求前两个参数必须是A2aFeature与A2uiFeature——即使不需要自定义 A2UI 配置也需要显式传入usingA2uiRenderers()占位。A2aFeature必需该特性配置A2aService的引用方式。没有它Chat 与 Canvas 什么都做不了。推荐通过以下辅助函数之一配置。usingA2aService使用usingA2aService绑定自定义服务实现// app.config.ts import {configureChatCanvasFeatures, usingA2aService} from src/lib/config; import {A2aServiceImpl} from path/to/code; export const appConfig: ApplicationConfig { providers: [ // ... configureChatCanvasFeatures(usingA2aService(A2aServiceImpl)), // ... ], };源码中它生成{provide: A2A_SERVICE, useClass: a2aServiceClass}的 provider见 config.ts。ChatService会通过该令牌注入服务其sendMessage在发送时会创建AbortController并将signal传给a2aService.sendMessage([{kind: text, text}], signal)从而支持流式取消见 chat-service.ts。A2uiFeature推荐该特性配置 Chat Canvas 的 A2UI 设置核心是为 A2UI Renderer 提供自定义 Catalog并控制视觉主题。usingA2uiRenderersusingA2uiRenderers函数配置 A2UI 库同时支持 v0.8 与 v0.9 两条渲染管线。它接受可选的 v0.8 Catalog将与默认 A2UI Catalog 合并、可选的 v0.9 Catalog、可选的自定义Theme。// app.config.ts import {configureChatCanvasFeatures, usingA2uiRenderers} from src/lib/config; import {MY_CUSTOM_CATALOG_V08, MY_CUSTOM_CATALOG_V09, MY_CUSTOM_THEME} from path/to/code; export const appConfig: ApplicationConfig { providers: [ configureChatCanvasFeatures( // ... usingA2uiRenderers(MY_CUSTOM_CATALOG_V08, MY_CUSTOM_CATALOG_V09, MY_CUSTOM_THEME), // ... ), ], };从源码config.ts可以看到它的实际行为v0.8将默认画布目录DEFAULT_A2UI_CATALOG与你提供的 v0.8 Catalog 做浅合并{...DEFAULT_A2UI_CATALOG, ...(customCatalogV08 ?? {})}再通过Catalog令牌注入v0.9将 v0.9 Catalog 收集到catalogsV09数组作为A2UI_RENDERER_CONFIG的catalogs同时配置actionHandler——当用户在 v0.9 表面上触发 A2UI action 时会把 action 包装成{version: v0.9, action}信封通过ChatService.sendMessage(...)静默发送给 Agent主题Theme令牌在所有渲染版本间共享未提供时回退到组件内置主题a2uiThemetheme.ts。这意味着如果客户端或 Agent 发送 v0.8 或 v0.9 任一版本的载荷组件都能自动选中对应的渲染器与 Catalog。这也解释了为什么A2uiDataPart组件内部会同时导入Surfacev0.8与SurfaceComponentv0.9并根据载荷中存在beginRenderingv0.8 生命周期起点还是createSurfacev0.9 起点来选择渲染见 a2ui-data-part.ts。MarkdownFeature推荐该特性配置 Markdown 的渲染方式。默认情况下Markdown 使用仅做 HTML 消毒sanitize的实现即MarkdownRendererService接口的默认实现。usingMarkdownRenderer高级可以提供自定义的MarkdownRendererService实现完全控制 Markdown 到 HTML 的转换Injectable({providedIn: root}) export class MyMarkdownRendererService implements MarkdownRendererService { // ... }// app.config.ts import {configureChatCanvasFeatures, usingMarkdownRenderer} from src/lib/config; import {MyMarkdownRendererService} from path/to/code; export const appConfig: ApplicationConfig { providers: [ configureChatCanvasFeatures( // ... usingMarkdownRenderer(MyMarkdownRendererService), // ... ), ], };接口只有一个方法见 markdown-renderer-service.tsrender(markdown: string): PromiseSafeHtml返回值使用 Angular 的SafeHtml类型确保输出被标记为可信。示例应用orchestrator就采用了高级做法通过a2ui/angular的provideMarkdownRenderer(renderMarkdown)配合a2ui/markdown-it提供完整的 markdown-it 渲染见 orchestrator 的 app.config.ts。usingDefaultSanitizerMarkdownRenderer如果希望使用默认渲染器仅消毒 HTML 内容、不做 Markdown 语法转换调用usingDefaultSanitizerMarkdownRenderer()。这是未提供任何 Markdown 渲染器时的默认行为MARKDOWN_RENDERER_SERVICE令牌的根级工厂直接new SanitizerMarkdownRendererService()见 markdown-renderer-service.ts。// app.config.ts import {configureChatCanvasFeatures, usingDefaultSanitizerMarkdownRenderer} from src/lib/config; export const appConfig: ApplicationConfig { providers: [ configureChatCanvasFeatures( // ... usingDefaultSanitizerMarkdownRenderer(), // ... ), ], };ArtifactResolverFeature该特性配置要使用的ArtifactResolver。默认情况下不使用任何ArtifactResolver。注意如果没有同时提供渲染 artifact 的 UI该特性毫无用处。usingArtifactResolversusingArtifactResolvers函数接受ArtifactResolver的变长参数varargs配置 Agent 响应中 artifact 到渲染 ID 的映射。// app.config.ts import {configureChatCanvasFeatures, usingArtifactResolvers} from src/lib/config; import {ARTIFACT_RESOLVER_1, ARTIFACT_RESOLVER_2} from path/to/code; export const appConfig: ApplicationConfig { providers: [ configureChatCanvasFeatures( // ... usingArtifactResolvers(ARTIFACT_RESOLVER_1, ARTIFACT_RESOLVER_2), // ... ), ], };ArtifactResolver的类型是(part: Artifact) string | null即把 A2A 的Artifact映射为变体名字符串无法解析时返回null未解析的 artifact 对应内置变体名unresolved_artifact见 types.ts。注册时每个 resolver 都通过multi: true追加到ARTIFACT_RESOLVERS令牌见 config.ts。PartResolverFeature该特性配置要使用的PartResolver。默认情况下不使用任何PartResolver。注意如果没有同时提供渲染 artifact 的 UI该特性毫无用处。usingPartResolversusingPartResolvers函数接受PartResolver的变长参数配置 Agent 响应中 parts 到渲染 ID 的映射。// app.config.ts import {configureChatCanvasFeatures, usingPartResolvers} from src/lib/config; import {PART_RESOLVER_1, PART_RESOLVER_2} from path/to/code; export const appConfig: ApplicationConfig { providers: [ configureChatCanvasFeatures( // ... usingPartResolvers(PART_RESOLVER_1, PART_RESOLVER_2), // ... ), ], };PartResolver的类型是(part: Part) string | null未解析的 part 对应内置变体名unresolved_part见 types.ts。组件内置的默认 Part Resolver 很能说明该机制A2UI_DATA_PART_RESOLVER检查 part 是否为data类型且数据中包含beginRendering或createSurface键A2UI 消息的标志命中则返回变体名a2ui_data_part见 resolver.ts。RenderersFeature该特性控制 Chat 与 Canvas 中渲染哪些变体variant。默认行为默认情况下注册了文本与A2UI DataPart的渲染器。它们都是懒加载的通过动态import()因此不会影响初始包体积直到真正需要时才加载见 config.ts 与 renderer-config.ts。usingRenderers额外的RendererEntry实例必须通过usingRenderers函数添加以便组件把PartRenderer或ArtifactRenderer返回的变体类型映射到对应 Component。该函数可以多次调用但一次调用即可。渲染器按顺序添加如果多个RendererEntry声明了相同的键最后添加的会生效这与RENDERERS_MAP的构建逻辑一致见 tokens.ts重复键时控制台会打印警告并采用最后一个。// app.config.ts import {configureChatCanvasFeatures, usingRenderers} from src/lib/config; import {RENDERER_ENTRY_1, RENDERER_ENTRY_2} from path/to/code; export const appConfig: ApplicationConfig { providers: [ configureChatCanvasFeatures( // ... usingRenderers(RENDERER_ENTRY_1, RENDERER_ENTRY_2), // ... ), ], };RendererEntry是二元组类型[variantName: string, componentClassLoader: RendererComponentClassLoader]其中 loader 是() PromiseTypeRendererComponent见 types.ts。渲染 Agent 响应从变体名到 UI 组件ArtifactResolver与PartResolver都会返回一个标识变体名的字符串。本节讨论如何把该变体名映射到渲染该变体的 UI 代码。在 Chat 中变体名不经修改直接用于查找渲染代码。在 Angular 中渲染器通过以下三步提供创建一个实现RendererComponent接口的 Component。接口唯一的要求是声明uiMessageContent输入InputSignalUiMessageContentimport {Component, input, output, Type} from angular/core; import {UiMessageContent} from a2a_chat_canvas/types/ui-message; import {Part} from a2a-js/sdk; Component({ /* ... */ }) export class MyRendererComponent implements RendererComponent { // Required by the interface. readonly uiMessageContent input.requiredUiMessageContent(); }在独立文件中创建一个RendererEntry常量import {RendererEntry} from a2a_chat_canvas/a2a-renderer/types; export const MY_RENDERER_ENTRY: RendererEntry [ my_variant_name, // 该动态导入在运行时按需懒加载组件代码。 // 如果希望立即加载或因为 UI 表面是不使用 MSS 的遗留表面而必须立即加载 // 则直接导入上面的组件类并在这个 async 函数中返回它。 async () { const {MyRendererComponent} await import(./path/to/code); return MyRendererComponent; }, ];使用上面的Renderers特性注册你的RendererEntry见usingRenderers一节。当某个PartResolver或ArtifactResolver返回匹配的变体名时该 Component 就会被渲染到 Agent 响应中对应的内容上。渲染的运行时机制可以在A2aRenderer中看到它注入RENDERERS_MAP由RENDERERS令牌构造的MapvariantName, loader用resource()API 根据当前uiMessageContent().variant查找并执行 loader动态取得组件类再交给NgComponentOutlet渲染。如果找不到对应变体控制台会警告No renderer found for variant: ...。使用组件完成上述配置后即可在应用中嵌入组件。在模板中包含a2a-chat-canvas并按需传入输入a2a-chat-canvas/a2a-chat-canvas主组件a2a-chat-canvas.ts内部编排Chat与Canvas两个子组件当CanvasService中存在surfaceId即有 A2UI 表面打开时显示 Canvas否则显示 Chat。类型MessageDecoratorComponent接口MessageDecorator类型别名函数签名输入InputsemptyHistoryTemplateTemplateRefunknown用于指定替换空聊天历史时显示内容的模板。ng-template #myEmptyHistoryTemplate Empty chat history? You must always get the shemp! ng-template a2a-chat-canvas [emptyHistoryTemplate]myEmptyHistoryTemplate /a2a-chat-canvas/ng-template /ng-templatemessageDecoratorMessageDecorator很少需要用于提供一个MessageDecorator——一个返回MessageDecoratorComponent支持懒加载的函数。当 UI 表面需要在消息上追加额外信息时使用例如添加延迟信息、消息操作按钮等。被渲染的消息与消息完整渲染后的模板都会作为输入提供。MessageDecoratorComponent应使用NgTemplateOutlet渲染传入的TemplateRef。import {Component, input, TemplateRef} from angular/core; import {UiMessage} from a2a_chat_canvas/types/ui_message; import {MessageDecoratorComponent} from a2a_chat_canvas/components/chat/chat_history/message_decorator/types; Component({ /* ... */ }) export class MyMessageDecoratorComponent implements MessageDecorator { readonly message input.requiredReadonlyUiMessage(); readonly coreContentTemplateRef input.requiredTemplateRefunknown(); }divSome content before the message/div ng-container *ngTemplateOutletcoreContentTemplateRef()/ng-contaioner divSome content after the message/div注意MessageDecorator的类型是() PromiseTypeMessageDecoratorComponent即懒加载函数MessageDecoratorComponent接口要求message与coreContentTemplateRef两个必填输入见 types.ts。orchestrator 示例应用中有完整演示demo-message-decorator.ts。完整示例orchestrator 应用的装配方式仓库中的 orchestrator 应用是a2a-chat-canvas的最佳实战参考其 app.config.ts 展示了上述全部特性的组合用法export const appConfig: ApplicationConfig { providers: [ provideBrowserGlobalErrorListeners(), provideZonelessChangeDetection(), provideRouter(routes), provideClientHydration(withEventReplay()), provideCharts(withDefaultRegisterables()), provideMarkdownRenderer(renderMarkdown), configureChatCanvasFeatures( usingA2aService(A2aServiceImpl), usingA2uiRenderers(DEMO_CATALOG), usingDefaultSanitizerMarkdownRenderer(), ), ], };从中可以看到几个值得注意的实践usingA2aService(A2aServiceImpl)传入其自定义的 A2A 服务实现usingA2uiRenderers(DEMO_CATALOG)只传入自定义 Catalog即 v0.8 参数v0.9 Catalog 与主题走默认值显式调用usingDefaultSanitizerMarkdownRenderer()尽管它是默认行为显式写出更清晰应用同时使用a2ui/angular的provideMarkdownRenderer(renderMarkdown)来自a2ui/markdown-it来提供全局 Markdown 渲染能力与组件内部的 Markdown 特性共同工作。内部机制速览ChatService如何串联各特性理解ChatServicechat-service.ts有助于把上述所有特性串成一条完整链路发送sendMessage(text)先做乐观更新把用户消息和 pending 的 Agent 消息写入history置isA2aStreamOpen true创建AbortController调用a2aService.sendMessage([{kind: text, text}], signal)响应处理handleSuccess用extractA2aPartsFromResponse提取 Agent 响应 parts经convertPartToUiMessageContent(part, partResolvers)使用注入的PART_RESOLVERS转换为UiMessageContent列表更新 pending 消息为 completedA2UI 分发extractA2uiDataParts提取出 A2UI 消息按协议版本分流——v0.8含beginRendering/surfaceUpdate/dataModelUpdate/deleteSurface交给MessageProcessor处理并刷新a2uiSurfacesv0.9含createSurface/updateComponents/updateDataModel/带version的deleteSurface交给A2uiRendererService.processMessages处理见 chat-service.ts用户交互回传MessageProcessor派发的 A2UI 事件会被订阅把 action 消息序列化后经sendMessage(JSON.stringify(event.message), isSilent)回传给 Agent静默标志来自userAction.context[silent]取消cancelOngoingStream()通过AbortController.abort()取消进行中的流式请求错误处理中AbortError会显示 You cancelled the response.。这就是configureChatCanvasFeatures所装配的一切A2A 服务、A2UI 渲染器、Markdown、Resolvers、Renderers在运行时被组织起来的方式。总结在 Angular 应用中接入a2a-chat-canvas的核心步骤可归纳为准备 UI 表面安装 GM3 主题与 Material Symbols 字体含 FILL 轴实现A2aService提供sendMessage与getAgentCard两个方法配置特性在app.config.ts中调用configureChatCanvasFeatures至少传入usingA2aService(...)与usingA2uiRenderers(...)按需叠加 Markdown、Resolver、Renderer 特性注册自定义渲染器实现RendererComponent创建RendererEntry用usingRenderers注册使用组件在模板中写入a2a-chat-canvas按需传入emptyHistoryTemplate与messageDecorator。该组件库的核心价值在于它把 A2A 协议的消息收发与 A2UI 的界面渲染无缝地融合进一个 Angular 组件同时兼容 v0.8 与 v0.9 两代 A2UI 管线并通过 Resolver → 变体名 → Renderer 的映射机制保持高度可扩展性——这正是 orchestrator 等示例应用展示的架构模式。如需更深入的信息可继续阅读 a2a-chat-canvas 源码与 orchestrator 完整示例。【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考