优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)

- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod

- AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation

- 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n

- 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
This commit is contained in:
lxy
2026-08-05 22:15:34 +08:00
parent 28de5d6143
commit e68d283651
49 changed files with 1257 additions and 138 deletions
+79 -2
View File
@@ -475,6 +475,7 @@ fn body_snippet(v: &Value) -> String {
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
// ── build_images_url:端点拼接规则 ──
@@ -641,8 +642,6 @@ mod tests {
// 边界:估算值 = MAX_IMAGE_BYTES + 1 → 应被拒
// 反推 b64 长度:要使 len*3/4 == MAX+1,len = (MAX+1)*4/3
let target = MAX_IMAGE_BYTES + 1;
let b64_len = (target as u128 * 4 / 3) as usize;
let estimated = estimate_decoded_bytes(b64_len);
// 因整数除法,估算可能略小于 target,但应保证 > MAX_IMAGE_BYTES
// 用更精确的 b64_len:target*4/3 + 4(向上补一个 base64 块),保证估算严格 > MAX
let b64_len_safe = ((target as u128 * 4 + 2) / 3) as usize;
@@ -795,4 +794,82 @@ mod tests {
msg
);
}
// ── 真实 SenseNova U1-Fast 图像生成验证(#[ignore],手动跑) ──
//
// 端到端验证:用真实 db 副本(SenseNova provider 配置)+ OS keyring 解析 api_key +
// 真实调 https://token.sensenova.cn/v1/images/generations 生成一张测试图并下载落地。
// 覆盖 generate_image 全链路:provider 选择(host 匹配)/ build_images_url 端点拼接 /
// 请求构造 / 响应解析 / SSRF 防护下载落地。
//
// 运行方式:
// set DEVFLOW_LIVE_DB=<真实 devflow.db 路径(含 wal)>
// cargo test -p devflow --lib -- --ignored live_sensenova_u1_fast --nocapture
//
// 需真实 API 凭证(付费),默认 #[ignore] 不随 CI 跑。落盘到 <cwd>/tmp_generated/ 便于清理。
/// 构造带持久授权目录的 AllowedDirs,指向项目内 tmp_generated/ 输出目录。
fn mk_allowed_with_generated_dir() -> (AllowedDirs, PathBuf) {
let out_dir = std::env::current_dir()
.expect("读 cwd")
.join("tmp_generated");
let mut persistent = std::collections::HashSet::new();
persistent.insert(out_dir.clone());
(AllowedDirs { persistent, session: Default::default(), once: Default::default() }, out_dir)
}
#[tokio::test]
#[ignore] // 真实 API 调用(付费),需 DEVFLOW_LIVE_DB 环境变量,手动跑
async fn live_sensenova_u1_fast() {
let db_path = std::env::var("DEVFLOW_LIVE_DB")
.map(PathBuf::from)
.expect("请设置 DEVFLOW_LIVE_DB=<真实 devflow.db 路径> 以跑真实 API 验证");
assert!(
db_path.exists(),
"DEVFLOW_LIVE_DB 指向的 db 不存在: {}",
db_path.display()
);
// 打开真实 db(会跑迁移,用真实路径;读 SenseNova provider + keyring 解析 key)
let db = Arc::new(Database::open(&db_path).await.expect("打开真实 db"));
let (allowed, out_dir) = mk_allowed_with_generated_dir();
// 走 execute_generate_image 全链路:model 默认 sensenova-u1-fast → host 匹配 SenseNova provider
let args = json!({
"prompt": "一张极简科技风信息图,主题「DevFlow AI 工作流」,顶部大标题,三个步骤图标(规划→执行→核查),浅蓝灰配色",
"size": "1536x2752",
"n": 1
});
let result = execute_generate_image(args, &db, &Arc::new(RwLock::new(allowed)))
.await
.expect("execute_generate_image 应成功生成并下载图片");
let path = result["path"].as_str().expect("result 应有 path");
let bytes = result["bytes_written"].as_u64().expect("result 应有 bytes_written");
let model = result["model"].as_str().unwrap_or("");
let provider_id = result["provider_id"].as_str().unwrap_or("");
println!("✅ 生成成功:");
println!(" model : {}", model);
println!(" provider : {}", provider_id);
println!(" 本地路径 : {}", path);
println!(" 字节数 : {}", bytes);
// 断言:文件真实落盘 + 非空 + 是图片(PNG 魔数)
assert!(
std::path::Path::new(path).exists(),
"图片应真实落盘: {}",
path
);
assert!(bytes > 1000, "图片应 > 1KB,实际 {} bytes", bytes);
let head = std::fs::read(path).expect("读图片头").into_iter().take(8).collect::<Vec<_>>();
assert!(
head.starts_with(&[0x89, b'P', b'N', b'G']) || head.starts_with(&[0xFF, 0xD8, 0xFF]),
"应为 PNG/JPEG 魔数,实际头字节: {:?}",
head
);
// 清理输出目录(测后不留临时图)
let _ = std::fs::remove_dir_all(&out_dir);
println!(" (输出目录已清理:{})", out_dir.display());
}
}