Blazor 前端¶
本导读介绍位于零售分析 API 前端的 Blazor WebAssembly 客户端。你将了解它如何连接 API、将聊天响应流式写入消息列表、持久化本地设置,并使模型选择器与实时模型列表保持一致。
应用结构与端口¶
前端是位于
AgentHQDemo.Web 下的 Blazor WebAssembly 应用。
在整个仓库中,这两个服务都使用显式的
--urls启动,从而覆盖启动配置文件:
因此,文档、图表和实验都将 5051 用于 UI,将
5050 用于 API。⚠️ 仓库中提交的启动配置文件默认使用 不同的
端口——Web 项目使用 5240,API 使用 5167——因此,如果运行时未指定
--urls (或在 IDE 中按 F5),服务将改用这些端口,UI 的默认 API 基址 http://localhost:5050 将不再匹配。请按文档所示传入 --urls ,或者设置 ApiBaseUrl 使其匹配。
AgentHQDemo.Web/Program.cs
配置 API 基址:
var apiBase = builder.Configuration["ApiBaseUrl"] ?? "http://localhost:5050";
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBase) });
API 在
AgentHQDemo.Api/Program.cs
中启用 CORS,并采用允许任意源、方法和请求头的默认策略。这样,在演示期间,从本地开发 URL 提供的 WebAssembly 应用就可以调用已配置的 API 基址。
Home.razor¶
Home.razor 是主聊天页面,负责管理以下页面状态:
Messages:按顺序排列的聊天记录。SelectedModel:当前选择的 Copilot 模型。Models:模型 ID 到显示名称的字典。IsDarkTheme:当前主题标志。IsStreaming:助手响应是否正在进行。
没有消息时,页面会显示欢迎面板和
SuggestionChips。出现消息后,页面会使用 Message
组件渲染每条消息。
流式状态与滚动¶
Home.razor.SendMessage 添加并存储用户消息,然后追加一个空的助手占位消息。当 IsStreaming 为 true 时,输入框会被禁用;随着数据块从
ChatService.StreamChatAsync 到达,助手消息会不断更新。
页面使用 Timer 以大约每秒 20 帧的频率批量更新 UI,而不是针对每个词元都执行渲染。渲染后,页面会调用 JavaScript 辅助函数,将消息容器滚动到底部并高亮代码块。
本地存储持久化¶
StorageService
封装了 Blazored.LocalStorage,并存储三个本地值:
| 键 | 用途 |
|---|---|
chat_messages |
持久化的 ChatMessage 聊天记录。 |
selected_model |
下次加载页面时恢复的模型。 |
theme |
已保存的 dark 或 light 主题。 |
Home.razor.OnInitializedAsync 会在获取模型列表之前加载这三个值。
模型选择器¶
Header.razor
渲染模型选择器。它接收实时 Models 字典,并将所选选项绑定到 SelectedModel:
当用户更改选择时, Header.OnModelChanged 会调用
SelectedModelChanged 回调。Home.razor.OnModelChanged 更新本地状态,并通过 StorageService.SetSelectedModelAsync 保存新模型。
获取实时可用模型¶
ChatService.GetModelsAsync
从 API 加载模型元数据:
如果 API 至少返回一个模型,服务会将其转换为
Header.razor 使用的 Dictionary<string, string>。如果 API 无法访问或返回不可用数据,则回退到 ChatService.AvailableModels,即用于离线容错的静态目录。
防止使用过时的已保存模型¶
本地存储中保存的模型可能已不再对当前登录账户可用。 Home.razor.OnInitializedAsync 会在获取实时模型后处理这种情况:
if (!Models.ContainsKey(SelectedModel))
{
SelectedModel = Models.ContainsKey("claude-haiku-4.5")
? "claude-haiku-4.5"
: Models.Keys.First();
}
随后,页面会持久化替代模型。这样可避免旧的 localStorage 值导致聊天请求使用 API 已不再提供的模型。
辅助组件¶
ChatInput
提供文本区域和发送按钮。单击按钮或按下不带 Shift 的 Enter 键时会发送消息;加载期间禁用输入;移除空白消息;并在首次渲染后聚焦输入框。
Message
渲染用户和助手消息。助手内容为空时显示输入指示器;非空内容使用 Markdig 从 Markdown 渲染,并标记代码块以供 JavaScript 执行高亮。
SuggestionChips
显示预定义的零售分析提示。选择一个提示标签后,会通过与手动输入相同的 Home.razor.SendMessage 路径发送该提示。