mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-05 15:49:43 +08:00
fix(assistant): 修复测试 Response Body 为 (empty) + 优化结果展示
## 用户反馈
截图显示测试结果详情里 "Response Body: (empty)",但对话实际可用。
用户:"有一个比较严重的bug...具体返回参数看不到...我感觉我们的功能不完整"
## 根因分析
1. **Accept-Encoding 未限制**:reqwest 只启用了 gzip feature(未启用 brotli),
但 provider 经 CDN/反代可能返回 Content-Encoding: br,导致 resp.text() 解码失败。
2. **错误被静默吞**:`resp.text().await.unwrap_or_default()` 在解码失败时返回 "",
前端展示为 (empty) 但没有任何错误提示,用户无法诊断。
3. **展示设计:reply 被截断 + 藏在折叠面板**:成功时模型回复只显示前 80 字符的
预览,完整内容要展开 "查看完整请求/响应参数" 才能看到(还被 JSON 混在一起)。
## 修复
### Rust test_model_verbose
三个分支(OpenAI / Anthropic / Gemini)都显式加 `Accept-Encoding: identity`
头,禁止响应压缩。测试请求的响应体很小(几百字节),不压缩的性能损失可忽略。
`resp.text().await` 失败时不再 unwrap_or_default 静默吞,而是返回带 error 的
JSON:`"读取响应体失败: {e} (可能是压缩编码未支持或非 UTF-8 响应)"`
### dev-api.js test_model_verbose(Web 模式)
三个分支的 headers 都加 `'Accept-Encoding': 'identity'`,和 Rust 行为一致。
### 前端 buildTestResult 重写
- **顶部显眼展示模型回复**(边框高亮 + 完整内容 + max-height:180px 加 scroll,
不再截断为 80 字预览):
```
✓ 连接成功 (300ms, Chat Completions)
╔═════════════════════════════╗
║ MODEL REPLY ║ ← 完整回复全文
║ 你好!我是 QC-A04... ║
╚═════════════════════════════╝
```
- **空 respBody + 非空 reply 时给明确诊断**:"响应主体未能读取(可能是压缩
编码异常),但已从响应流中提取到回复内容"
- **固定 prompt 脚注**:`📌 本次测试使用预设 prompt "你好,请用一句话回复"
+ max_tokens=200` —— 让用户明白这是固定诊断请求,不是真实对话。
- **详情面板的空 body 展示优化**:不再显示 "(empty)" 字面量(可能被误解为
服务器真的返回了空字符串),改为带颜色和 italic 的 "(响应体为空)" 提示。
### i18n 新增 5 个翻译键
- testModelReply:Model Reply
- testFixedPrompt:本次测试使用预设 prompt...
- testRespBodyEmpty:响应主体未能读取...但已从流中提取回复
- testShowDetails:查看完整请求/响应参数(原来硬编码中文)
- testRespBodyEmptyDetail:(响应体为空) [原来是硬编码 "(empty)"]
均覆盖 zh-CN / en / zh-TW / ja / ko / vi。
## 验证
- npm run build 通过(assistant chunk gzip 49.43 KB)
- cargo check 通过
- 下一步:用户实测后确认 Response Body 正常显示再发 v0.13.4
## 相关
- Roadmap v0.14.0:把测试功能升级成迷你 Playground(自定义 prompt / 多轮
对话 / 流式显示 / max_tokens 滑块)
This commit is contained in:
@@ -5268,6 +5268,10 @@ pub async fn test_model_verbose(
|
||||
crate::commands::build_http_client_no_proxy(std::time::Duration::from_secs(30), None)
|
||||
.map_err(|e| format!("创建 HTTP 客户端失败: {e}"))?;
|
||||
|
||||
// 关键:显式 Accept-Encoding: identity 禁止响应压缩,避免:
|
||||
// - reqwest 未启用 brotli feature 时,provider 返回 Content-Encoding: br 导致 text() 失败
|
||||
// - 某些 CDN 会根据默认 UA 自动压缩响应
|
||||
// 测试请求的响应体很小(几百字节),不压缩的性能损失可忽略
|
||||
let (used_api, req_url, req_body_json, req_builder) = match api_type_norm {
|
||||
"anthropic-messages" => {
|
||||
let url = format!("{}/messages", base);
|
||||
@@ -5279,6 +5283,7 @@ pub async fn test_model_verbose(
|
||||
let mut req = client
|
||||
.post(&url)
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header("Accept-Encoding", "identity")
|
||||
.json(&body);
|
||||
if !api_key.is_empty() {
|
||||
req = req.header("x-api-key", api_key.clone());
|
||||
@@ -5294,7 +5299,10 @@ pub async fn test_model_verbose(
|
||||
let body = json!({
|
||||
"contents": [{"role": "user", "parts": [{"text": "你好,请用一句话回复"}]}]
|
||||
});
|
||||
let req = client.post(&url_real).json(&body);
|
||||
let req = client
|
||||
.post(&url_real)
|
||||
.header("Accept-Encoding", "identity")
|
||||
.json(&body);
|
||||
("Gemini", url_display, body, req)
|
||||
}
|
||||
_ => {
|
||||
@@ -5305,7 +5313,10 @@ pub async fn test_model_verbose(
|
||||
"max_tokens": 200,
|
||||
"stream": false
|
||||
});
|
||||
let mut req = client.post(&url).json(&body);
|
||||
let mut req = client
|
||||
.post(&url)
|
||||
.header("Accept-Encoding", "identity")
|
||||
.json(&body);
|
||||
if !api_key.is_empty() {
|
||||
req = req.header("Authorization", format!("Bearer {api_key}"));
|
||||
}
|
||||
@@ -5342,7 +5353,23 @@ pub async fn test_model_verbose(
|
||||
|
||||
let status = resp.status();
|
||||
let status_code = status.as_u16();
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
// 读取响应体:若失败(如 gzip/brotli 解码异常、非法 UTF-8)直接返回错误,不静默吞成空串
|
||||
let text = match resp.text().await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return Ok(json!({
|
||||
"success": false,
|
||||
"status": status_code,
|
||||
"reqUrl": req_url,
|
||||
"reqBody": req_body_json,
|
||||
"respBody": "",
|
||||
"reply": "",
|
||||
"error": format!("读取响应体失败: {e} (可能是压缩编码未支持或非 UTF-8 响应)"),
|
||||
"elapsedMs": elapsed_ms,
|
||||
"usedApi": used_api,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// 提取 reply 文本(兼容 OpenAI / Anthropic / Gemini / DashScope)
|
||||
let reply = serde_json::from_str::<serde_json::Value>(&text)
|
||||
|
||||
Reference in New Issue
Block a user