文章目录
- 前言
- 一、C++ 层的 HTTP POST 接口封装
- 1. 头文件声明 (`SSEClient.h`)
- 2.源码实现 (SSEClient.cpp)
- 二、UMG 蓝图连接
- 1. 最初的纯前端单机蓝图
- 2.硬编码接入接口测试
- 3.动态捕获真实文本
前言
近期的项目中,我主要实现前端ui和后端的连接工作。首先完成的是对话内容界面,发送文字之后能够收到并显示大模型的回复。
一、C++ 层的 HTTP POST 接口封装
为了方便在蓝图层直接调用,我设计并实现了一个静态函数SendChatMessage。该函数将复杂的网络请求、数据序列化与反序列化封装在底层,向蓝图层暴露了干净的入参和出参。
1. 头文件声明 (SSEClient.h)
在类定义中,使用UFUNCTION(BlueprintCallable)将函数暴露给蓝图,并利用FString& Reply作为引用传参,从而在蓝图节点上直接生成输出引脚。
/** * 发送聊天消息并同步等待回复 * @param URL 后端接口地址 * @param SessionID 当前对话会话ID(用于上下文关联) * @param Message 玩家输入的文本内容 * @param SceneKey 当前问诊场景标识 * @param Reply 后端返回的AI回复文本(蓝图输出引脚) */UFUNCTION(BlueprintCallable,Category="HTTP")staticvoidSendChatMessage(constFString&URL,constFString&SessionID,constFString&Message,constFString&SceneKey,FString&Reply);2.源码实现 (SSEClient.cpp)
在具体的代码实现中,包含了 JSON 数据构建、HTTP 请求配置、阻塞等待机制 以及 JSON 响应解析 四个核心步骤:
voidUSSEClient::SendChatMessage(constFString&URL,constFString&SessionID,constFString&Message,constFString&SceneKey,FString&Reply){// 1. 初始化兜底返回值Reply=TEXT("INIT");UE_LOG(LogTemp,Warning,TEXT("=== SendChatMessage Start ==="));// 2. 构造请求体 JSON 对象TSharedPtr<FJsonObject>JsonObject=MakeShared<FJsonObject>();JsonObject->SetStringField(TEXT("session_id"),SessionID);JsonObject->SetStringField(TEXT("message"),Message);JsonObject->SetStringField(TEXT("scene_key"),SceneKey);// 序列化 JSON 为字符串FString RequestBody;TSharedRef<TJsonWriter<>>Writer=TJsonWriterFactory<>::Create(&RequestBody);FJsonSerializer::Serialize(JsonObject.ToSharedRef(),Writer);// 3. 创建并配置 HTTP POST 请求TSharedRef<IHttpRequest,ESPMode::ThreadSafe>Request=FHttpModule::Get().CreateRequest();Request->SetURL(URL);Request->SetVerb(TEXT("POST"));Request->SetHeader(TEXT("Content-Type"),TEXT("application/json"));// 声明传输内容格式Request->SetContentAsString(RequestBody);Request->ProcessRequest();// 4. 阻塞等待请求完成(设置 10 秒超时保护)doubleStartTime=FPlatformTime::Seconds();while(Request->GetStatus()==EHttpRequestStatus::Processing){FPlatformProcess::Sleep(0.01f);// 挂起微小时间,防止死循环跑满 CPUif(FPlatformTime::Seconds()-StartTime>10.0){UE_LOG(LogTemp,Error,TEXT("HTTP Request Timeout!"));break;}}// 5. 解析返回的响应数据FHttpResponsePtr Response=Request->GetResponse();if(Response.IsValid()&&Response->GetResponseCode()==200){TSharedPtr<FJsonObject>ParsedJson;TSharedRef<TJsonReader<>>Reader=TJsonReaderFactory<>::Create(Response->GetContentAsString());// 反序列化 JSON 字符串并提取目标字段if(FJsonSerializer::Deserialize(Reader,ParsedJson)&&ParsedJson.IsValid()){Reply=ParsedJson->GetStringField(TEXT("reply"));UE_LOG(LogTemp,Warning,TEXT("Successfully Reply: %s"),*Reply);}}else{UE_LOG(LogTemp,Error,TEXT("HTTP Failed! Code: %d"),Response.IsValid()?Response->GetResponseCode():0);}}二、UMG 蓝图连接
在完成 C++ 底层封装后,我开始在 UMG 聊天界面的“发送按钮点击事件”(On Clicked (Btn_Send))中配置核心业务数据流。
1. 最初的纯前端单机蓝图
最开始的业务逻辑非常单纯,仅仅为了验证输入框组件和滚动列表的渲染是否正常。
- 连线流向:
玩家点击 On Clicked (Btn_Send) 按钮→ \rightarrow→进入 Branch 节点(调用 Text Is Empty 校验用户是否打了字)。
如果输入不为空(走 False 分支)→ \rightarrow→直接连到 Create WBP Message Widget 节点,把多行输入框读取到的 Text(经过 Trim 与 To Text 处理)喂给它。
最后通过 Add Child 添加到 Messages List 滚动框,并设置对齐方式为右对齐(代表玩家发送)。
2.硬编码接入接口测试
为了测试写好的 C++ 函数 SendChatMessage 是否跑得通,我将白色执行线在右侧继续延伸,并首次拖出了 C++ 节点。
- 连线流向:
从右对齐节点(Set Horizontal Alignment)的右侧执行三角拉出白线,连到 Send Chat Message 的输入端。
为了仅测试代码逻辑以及前后端是否连接成功,我直接在 Send Chat Message 节点的 Message 参数输入框里,手动打字硬编码写死了 “你好” 两个字。
右侧的 Reply 引脚则延伸出去,用于创建左对齐的 AI 回复气泡。
这时候能够收到大模型的回复,说明代码和蓝图的逻辑正确。
但因为网络节点引脚里写死了“你好”,大模型后端接收到的数据永远是“你好”。大模型只能一遍遍针对“你好”做应对,导致医患系统陷入完全死循环的答非所问。
3.动态捕获真实文本
最终的完善实现系统将用户在界面输入的真实问诊话术精准捕获并成功提交,在大模型给出回复后,完美且即时地渲染出极具拟真度的虚拟患者对答文本。
- 在蓝图最左侧的
To Text (String)节点,右键其粉色Return Value引脚选择Promote to Variable(提升为变量),并将新生成的变量重命名为MyInputText。 - 切断原本
Branch (False)直连Create WBP Message Widget的白线。改由Branch (False)先连入SET MyInputText节点进行数据写入,再由SET MyInputText的右侧执行引脚向后连回 UI 渲染节点。 - 从变量栏将
MyInputText拖入图表并选择Get MyInputText,直接将其粉色引脚连入Send Chat Message的Message参数中
下一步工作计划:开始攻克问诊结束后的成绩单总览面板(WBP_SegueUI)开发,实现后端返回的五阶段评分细则数据的分拣与动态 Tab 切换刷新。