news 2026/9/13 23:37:10

Parlant 自定义前端开发指南:从 React 聊天组件到基于会话事件的自建聊天界面

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Parlant 自定义前端开发指南:从 React 聊天组件到基于会话事件的自建聊天界面

Parlant 自定义前端开发指南:从 React 聊天组件到基于会话事件的自建聊天界面

【免费下载链接】parlantBuild reliable customer-facing AI agents with Parlant: an interaction control harness optimized for controlled, consistent, and predictable LLM interactions.项目地址: https://gitcode.com/GitHub_Trending/pa/parlant

在 Parlant 中,面向客户的 AI Agent 能力全部通过会话(Session)与事件(Event)体系对外暴露,而文档 docs/production/custom-frontend.md 专门讲的就是如何把这套能力接进你自己的产品前端:既可以用官方parlant-chat-react组件快速嵌入一个完整的聊天界面,也可以直接基于 Parlant 客户端 SDK 的会话 API 自建任意框架(Vue、Angular、原生 JavaScript)的聊天 UI。读完本篇,你可以掌握两条路径的完整接入方式:widget 的属性配置与组件替换,以及"创建会话 → 发送事件 → 长轮询监听事件 → 渲染消息"这一自建前端的核心调用链,并能对照仓库源码理解每个 API 参数在服务端的真实行为。

两条集成路径总览

Parlant 服务端(FastAPI 应用)负责会话状态、事件追加与 Agent 编排,前端只需要通过 HTTP 接口与之交互。仓库中自带了一个完整的参考实现前端 src/parlant/api/chat/(React + Vite 应用),其 API 封装见 api.ts,会话视图中的事件轮询逻辑见 session-view.tsx,可作为自建前端时的活参考。

  • 路径一:官方 React widget。适合 React 技术栈、希望快速上线标准聊天体验的场景。
  • 路径二:自建前端。适合非 React 框架、深度定制 UI/交互、或需要接管完整会话生命周期的场景。

路径一:parlant-chat-react 官方组件

parlant-chat-react是一个开源的 React 聊天组件,提供完整的聊天界面并直接连接你的 Parlant 服务端 Agent。

安装与基本接入

通过 npm 或 yarn 安装:

npm install parlant-chat-react # 或 yarn add parlant-chat-react

在 React 应用中集成:

import React from 'react'; import ParlantChatbox from 'parlant-chat-react'; function App() { return ( <div> <h1>My Application</h1> <ParlantChatbox server="http://localhost:8800" // 你的 Parlant 服务端 URL agentId="your-agent-id" // 你的 Agent ID /> </div> ); } export default App;

其中server指向你的 Parlant 服务端(本地示例用http://localhost:8800),agentId为要对话的 Agent 标识。

配置属性(Props)

组件支持如下配置:

<ParlantChatbox // 必需属性 server="http://localhost:8800" agentId="your-agent-id" // 可选属性 sessionId="existing-session-id" // 续接已有会话 customerId="customer-123" // 关联到指定客户 float={true} // 以浮动弹窗形式展示 titleFn={(session) => `Chat ${session.id}`} // 动态生成标题 />
  • sessionId:传入已有会话 ID 可恢复历史对话,而不是每次新开一个会话;
  • customerId:将本次对话归属到具体客户,对应服务端会话创建参数中的customer_id(见下文);
  • float:开启浮动聊天窗口模式,适合网站右下角悬浮入口;
  • titleFn:接收会话对象回调生成动态标题,常用于显示会话主题或时间。

常见定制方式

用 CSS 类名覆盖样式

通过classNames属性为各区域注入自定义类名:

<ParlantChatbox server="http://localhost:8800" agentId="your-agent-id" classNames={{ chatboxWrapper: "my-chat-wrapper", chatbox: "my-chatbox", messagesArea: "my-messages", agentMessage: "my-agent-bubble", customerMessage: "my-customer-bubble", textarea: "my-input-field", popupButton: "my-popup-btn" }} />

这些类名分别覆盖组件外壳、聊天主体、消息区、Agent/客户消息气泡、输入框和浮动按钮,配合你自己的样式表即可实现品牌化外观。

替换具体子组件

components属性允许用你自己的组件替换内部实现,例如自定义浮动按钮和 Agent 消息渲染:

<ParlantChatbox server="http://localhost:8800" agentId="your-agent-id" components={{ popupButton: ({ toggleChatOpen }) => ( <button onClick={toggleChatOpen} className="custom-chat-button" > 💬 Chat with us </button> ), agentMessage: ({ message }) => ( <div className="custom-agent-message"> <img src="./assets/agent-avatar.png" alt="Agent" /> <p>{message.data.message}</p> </div> ) }} />

注意agentMessage收到的message就是 Parlant 的事件对象,文案在message.data.message字段——这与自建前端路径中EventDTO.data.message的结构完全一致。

浮动聊天模式
<ParlantChatbox server="http://localhost:8800" agentId="your-agent-id" float={true} popupButton={<ChatIcon size={24} color="white" />} />

float={true}加上自定义popupButton,即可把整个聊天压缩成一个可展开的悬浮图标。

该 widget 本身是开源项目,你可以查阅其源码作为会话管理、事件处理与 UI 状态同步的最佳实践参考,再套用到 Vue、Angular 或原生 JavaScript 等其它框架上。

路径二:基于 Parlant Client SDK 自建前端

当你需要比 React 组件更强的控制力,或使用的不是 React 时,可以直接调用 Parlant 客户端 API 自建前端。整体流程分五步:初始化客户端 → 创建会话 → 发送客户消息 → 监听会话事件 → 渲染到 UI。

Step 1: 初始化 Parlant 客户端

import { ParlantClient } from 'parlant-client'; class ParlantChat { private client: ParlantClient; private sessionId: string | null = null; private lastOffset: number = 0; constructor(serverUrl: string) { this.client = new ParlantClient({ environment: serverUrl }); } }
import { ParlantClient } from 'parlant-client'; class ParlantChat { constructor(serverUrl) { this.client = new ParlantClient({ environment: serverUrl }); this.sessionId = null; this.lastOffset = 0; } }

environment传入服务端 URL。lastOffset用于记录客户端已消费到的事件偏移量,是长轮询去重的关键状态。从仓库结构看,parlant-clientSDK 是由服务端的 OpenAPI 定义生成的(仓库中有 generate_client_sdk.py 与 fern 配置),因此 SDK 方法与下面讲到的服务端端点一一对应。

Step 2: 创建会话

async createSession(agentId: string, customerId?: string): Promise<string> { try { const session = await this.client.sessions.create({ agentId: agentId, customerId: customerId, title: `Chat Session ${new Date().toLocaleString()}` }); this.sessionId = session.id; console.log('Session created:', this.sessionId); // 启动事件监听 this.startEventMonitoring(); return this.sessionId; } catch (error) { console.error('Failed to create session:', error); throw error; } }

对照服务端实现 SessionCreationParamsDTO,创建会话的参数为:

参数说明
agent_id必填,要对话的 Agent
customer_id可选;不提供时服务端会自动创建一个 guest 客户
title可选,会话标题,最长 200 字符
metadata/labels可选的元数据与标签,可用于会话分类检索

返回的会话对象(SessionDTO)包含idagent_idcustomer_idcreation_utcmodeconsumption_offsetsmetadatalabels等字段,其中consumption_offsets.client用于服务端追踪客户端已消费到的事件偏移。

JavaScript 版本实现相同:

async createSession(agentId, customerId) { try { const session = await this.client.sessions.create({ agentId: agentId, customerId: customerId, title: `Chat Session ${new Date().toLocaleString()}` }); this.sessionId = session.id; console.log('Session created:', this.sessionId); this.startEventMonitoring(); return this.sessionId; } catch (error) { console.error('Failed to create session:', error); throw error; } }

Step 3: 发送客户消息

async sendMessage(message: string): Promise<void> { if (!this.sessionId) { throw new Error('No active session'); } try { await this.client.sessions.createEvent(this.sessionId, { kind: "message", source: "customer", message: message }); // 消息将在事件监听回传时出现在 UI 上 console.log('Message sent:', message); } catch (error) { console.error('Failed to send message:', error); throw error; } }

JavaScript 版本:

async sendMessage(message) { if (!this.sessionId) { throw new Error('No active session'); } try { await this.client.sessions.createEvent(this.sessionId, { kind: "message", source: "customer", message: message }); console.log('Message sent:', message); } catch (error) { console.error('Failed to send message:', error); throw error; } }

服务端对应参数模型见 EventCreationParamsDTO,其中:

  • kind取值来自 EventKindDTO:message(消息)、tool(工具调用)、status(状态)、custom(自定义);
  • source取值来自 EventSourceDTO:customercustomer_uihuman_agenthuman_agent_on_behalf_of_ai_agentai_agentsystem。自建前端以客户身份发消息时用customer
  • message外还支持datametadataparticipantstatusguidelines等字段,可用于发送自定义事件或附带参与者信息。

Step 4: 监听会话事件(长轮询)

这是自建前端的核心——通过listEvents长轮询持续获取新事件:

private async startEventMonitoring(): Promise<void> { if (!this.sessionId) return; while (true) { try { // 长轮询获取新事件 const events = await this.client.sessions.listEvents(this.sessionId, { minOffset: this.lastOffset, waitForData: 30, // 最多等待 30 秒新事件 kinds: ["message", "status"] // 只取消息与状态事件 }); for (const event of events) { await this.handleEvent(event); this.lastOffset = Math.max(this.lastOffset, event.offset + 1); } } catch (error) { console.error('Event monitoring error:', error); // 出错后等待再重试 await new Promise(resolve => setTimeout(resolve, 5000)); } } } private async handleEvent(event: any): Promise<void> { if (event.kind === "message") { this.displayMessage(event); } else if (event.kind === "status") { this.updateStatus(event.data.status); } }

JavaScript 版本:

async startEventMonitoring() { if (!this.sessionId) return; while (true) { try { const events = await this.client.sessions.listEvents(this.sessionId, { minOffset: this.lastOffset, waitForData: 30, kinds: ["message", "status"] }); for (const event of events) { await this.handleEvent(event); this.lastOffset = Math.max(this.lastOffset, event.offset + 1); } } catch (error) { console.error('Event monitoring error:', error); await new Promise(resolve => setTimeout(resolve, 5000)); } } } async handleEvent(event) { if (event.kind === "message") { this.displayMessage(event); } else if (event.kind === "status") { this.updateStatus(event.data.status); } }

服务端行为(源码级说明)。对应的 API 端点为GET /{session_id}/events,实现在 list_events,要点:

  1. wait_for_data长轮询语义wait_for_data = 0时立即返回当前匹配事件;wait_for_data > 0时若超时内没有新事件,服务端抛出504 Gateway Timeout;已有匹配事件则立即返回。服务端默认值为 60 秒,文档示例用 30 秒。因此上面的catch分支 + 5 秒延迟重试不是可选项,而是必须处理的正常路径——504 超时会被 SDK 表现为错误,重试循环即恢复监听;
  2. min_offset去重:事件在会话内按offset顺序编号,客户端始终从lastOffset开始拉取并推进到event.offset + 1,保证不重复、不遗漏;
  3. kinds过滤:逗号分隔的 kind 列表(message,tool,status,custom),只订阅 UI 需要的类型可减少数据量;
  4. SSE 模式:端点还支持sse=true参数,以text/event-stream持续推送事件,wait_for_data此时作为两次事件间的空闲超时(见 SSE 分支实现),对不想维护轮询循环的前端是另一种选择;
  5. 事件结构:返回的 EventDTO 包含idsourcekindoffsetcreation_utctrace_iddatametadatadeleted等字段,官方示例负载形如{"message": "Hello, I need help...", "participant": {"id": "cust_123xy", "display_name": "John Doe"}}(见 event_example)。

Step 5: 在 UI 中渲染消息与状态

private displayMessage(event: any): void { const messageElement = document.createElement('div'); messageElement.className = `message ${event.source}`; // 按消息来源区分样式 switch (event.source) { case 'customer': messageElement.classList.add('customer-message'); break; case 'ai_agent': messageElement.classList.add('agent-message'); break; case 'human_agent': messageElement.classList.add('human-agent-message'); const agentName = event.data.participant?.display_name || 'Agent'; messageElement.innerHTML = ` <div class="agent-info">${agentName}</div> <div class="message-content">${event.data.message}</div> `; break; } const chatContainer = document.getElementById('chat-messages'); if (chatContainer) { chatContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; } } private updateStatus(status: string): void { const statusElement = document.getElementById('chat-status'); if (statusElement) { switch (status) { case 'processing': statusElement.textContent = 'Agent is thinking...'; break; case 'typing': statusElement.textContent = 'Agent is typing...'; break; case 'ready': statusElement.textContent = ''; break; } } }

JavaScript 版本:

displayMessage(event) { const messageElement = document.createElement('div'); messageElement.className = `message ${event.source}`; switch (event.source) { case 'customer': messageElement.classList.add('customer-message'); messageElement.innerHTML = `<div class="message-content">${event.data.message}</div>`; break; case 'ai_agent': messageElement.classList.add('agent-message'); messageElement.innerHTML = `<div class="message-content">${event.data.message}</div>`; break; case 'human_agent': messageElement.classList.add('human-agent-message'); const agentName = event.data.participant?.display_name || 'Agent'; messageElement.innerHTML = ` <div class="agent-info">${agentName}</div> <div class="message-content">${event.data.message}</div> `; break; } const chatContainer = document.getElementById('chat-messages'); if (chatContainer) { chatContainer.appendChild(messageElement); chatContainer.scrollTop = chatContainer.scrollHeight; } } updateStatus(status) { const statusElement = document.getElementById('chat-status'); if (statusElement) { switch (status) { case 'processing': statusElement.textContent = 'Agent is thinking...'; break; case 'typing': statusElement.textContent = 'Agent is typing...'; break; case 'ready': statusElement.textContent = ''; break; } } }

对照源码有两点补充:

  • 来源枚举比示例更宽sourcecustomer/ai_agent/human_agent外还有customer_uisystemhuman_agent_on_behalf_of_ai_agent(EventSourceDTO),涉及人工接管(human handoff)场景时建议一并处理;
  • 状态枚举更完整:SessionStatusDTO 定义了acknowledgedcancelledprocessingreadytypingerror六种状态,上面的updateStatus只处理了三种,健壮的实现应对error做提示、对cancelled做收尾处理。

Step 6: 完整 HTML 示例

下面是一个不依赖构建工具之外的完整页面,演示自建实现(parlant-client通过 npm 安装后由 Vite/webpack 等打包器引入):

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Custom Parlant Chat</title> <style> .chat-container { max-width: 500px; margin: 50px auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; } .chat-header { background: #007bff; color: white; padding: 15px; text-align: center; } .chat-messages { height: 400px; padding: 15px; overflow-y: auto; background: #f8f9fa; } .message { margin: 10px 0; padding: 10px; border-radius: 8px; max-width: 80%; } .customer-message { background: #007bff; color: white; margin-left: auto; text-align: right; } .agent-message { background: white; border: 1px solid #ddd; } .human-agent-message { background: #28a745; color: white; } .chat-input { display: flex; padding: 15px; background: white; } .chat-input input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-right: 10px; } .chat-input button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } #chat-status { font-style: italic; color: #666; padding: 5px 15px; } </style> </head> <body> <div class="chat-container"> <div class="chat-header"> <h3>Customer Support Chat</h3> </div> <div id="chat-status"></div> <div id="chat-messages" class="chat-messages"></div> <div class="chat-input"> <input type="text" id="message-input" placeholder="Type your message..." onkeypress="handleKeyPress(event)" /> <button onclick="sendMessage()">Send</button> </div> </div> <script type="module"> import { ParlantClient } from 'parlant-client'; // 由打包器解析 npm 包 // 初始化你的自定义聊天 const chat = new ParlantChat('http://localhost:8800'); // 启动聊天会话 chat.createSession('your-agent-id') .then(sessionId => { console.log('Chat ready!', sessionId); }) .catch(error => { console.error('Failed to start chat:', error); }); // 将函数暴露到全局 window.sendMessage = () => chat.sendUserMessage(); window.handleKeyPress = (event) => { if (event.key === 'Enter') { chat.sendUserMessage(); } }; </script> </body> </html>

其中ParlantChat类即 Step 1–5 中 TypeScript/JavaScript 各方法的组合,sendUserMessage从输入框取值后调用sendMessage

关键实现原则与源码依据

官方文档总结了五条自建前端应遵循的原则,结合仓库源码可以进一步理解其必要性:

  1. 事件驱动架构:聊天界面完全由 Parlant 会话事件驱动,UI 状态与服务端状态保持一致。服务端所有交互(客户消息、Agent 回复、状态变化)都以事件形式写入会话,前端不维护独立的对话状态。
  2. 长轮询(Long Polling)listEventswaitForData参数让服务端在有新事件时才返回,避免高频轮询;其语义(超时返回 504、已有匹配事件立即返回)由 list_events 实现 的文档字符串明确定义。
  3. 状态同步:始终以 Parlant 事件为准渲染,而非乐观更新 UI——即使客户消息由本地发出,也要等customer来源的事件从监听链路回来后显示,这样消息的顺序与offset天然对齐。
  4. 错误处理:网络错误与 504 超时是长轮询的常态,需要重试逻辑(示例中为 5 秒延迟后重新进入轮询循环)。
  5. 响应式设计:确保聊天界面在桌面和移动端都可用。

除上述文档要点外,还有两个值得了解的机制:服务端会记录consumption_offsets.client(客户端已消费的最大偏移,见 ConsumptionOffsetsDTO),可用于断线重连后的状态校准;会话创建时若不带customer_id,服务端自动创建 guest 客户,这对应 widget 中customerId为可选的原因。

参考路径与延伸阅读

  • 原始文档:docs/production/custom-frontend.md
  • 会话 API 与服务端事件模型:src/parlant/api/sessions.py
  • 仓库内置参考前端(React + Vite):src/parlant/api/chat/,API 封装 api.ts
  • 会话 API 测试用例:tests/api/test_sessions.py
  • SDK 生成脚本:scripts/generate_client_sdk.py、scripts/fern/fern.config.json

适用前提:以上所有示例假设你已部署一个本地或远程 Parlant 服务端(示例 URL 为http://localhost:8800),并且已创建好至少一个 Agent(可用agentId标识)。widget 路径仅适用于 React 技术栈;自建前端路径适用于任意能发起 HTTP 请求的前端环境,事件订阅可选择长轮询或sse=true的 SSE 流两种方式。

【免费下载链接】parlantBuild reliable customer-facing AI agents with Parlant: an interaction control harness optimized for controlled, consistent, and predictable LLM interactions.项目地址: https://gitcode.com/GitHub_Trending/pa/parlant

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 23:33:37

深入解析CGridCtrl:打造可编辑高性能的MFC表格控件

简介&#xff1a;CGridCtrl_demo演示程序是一份面向Visual C开发者的完整示例&#xff0c;重点展示MFC表格控件CGridCtrl与CMyODBC数据库访问类的结合用法。开发者在MFC应用中往往需要以网格形式展示、编辑数据库记录&#xff0c;这份代码将两者封装并串起从连接数据源、执行SQ…

作者头像 李华
网站建设 2026/9/13 23:28:33

SAP HANA Cloud 迁移真正要搬什么,从 BTP 账户到数据库对象与业务数据的完整资产地图

很多 SAP HANA 迁移项目刚启动时,团队脑海里出现的第一幅画面往往是数据库。 源端有一套本地部署的 SAP HANA,目标端准备了一套 SAP HANA Cloud,于是很自然地开始盘点 schema、table、view、procedure,再讨论数据量、停机窗口和数据传输速度。数据库当然是核心,但如果整个…

作者头像 李华