优化: 项目文件树/依赖图组件 + 灵感对抗校验 + 小程序脚本 + 模块管理
This commit is contained in:
@@ -71,6 +71,7 @@ pub(crate) async fn ensure_conversation_title(
|
||||
prompt_cache_hit_tokens: None,
|
||||
prompt_cache_miss_tokens: None,
|
||||
reasoning_tokens: None,
|
||||
is_estimated: None,
|
||||
timestamp: m.timestamp,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -474,7 +474,7 @@ pub async fn get_module_git_status(
|
||||
Ok(serde_json::to_value(status).map_err(err_str)?)
|
||||
}
|
||||
|
||||
/// 列出工程本地分支(只读)。返回 { current, branches: [{ name, is_current }] }。
|
||||
/// 列出工程分支(本地 + 远程跟踪,只读)。返回 { current, branches: [{ name, is_current }] }。
|
||||
#[tauri::command]
|
||||
pub async fn list_branches(
|
||||
state: State<'_, AppState>,
|
||||
@@ -497,10 +497,15 @@ pub async fn list_branches(
|
||||
let dir = module.path.clone();
|
||||
// git 命令在 spawn_blocking 中执行(阻塞 IO 不污染 async runtime),10s 超时
|
||||
let result = tokio::task::spawn_blocking(move || -> (String, Vec<serde_json::Value>) {
|
||||
// git branch --format="%(HEAD)%00%(refname:short)"
|
||||
// git branch -a --format="%(HEAD)%00%(refname:short)%00%(refname)"
|
||||
// -a 同时列出本地分支(refs/heads/*)与远程跟踪分支(refs/remotes/*,短名形如 origin/main);
|
||||
// 原仅 `git branch` 只列本地分支,远程分支被排除(仓库只见一个分支的根因)。
|
||||
let out = run_git_cmd(
|
||||
std::path::Path::new(&dir),
|
||||
&["branch", "--format=%(HEAD)%00%(refname:short)"],
|
||||
&[
|
||||
"branch", "-a",
|
||||
"--format=%(HEAD)%00%(refname:short)%00%(refname)",
|
||||
],
|
||||
std::time::Duration::from_secs(10),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
@@ -509,10 +514,16 @@ pub async fn list_branches(
|
||||
for line in out.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() { continue; }
|
||||
// 格式: "*\0branch_name" 或 "\0branch_name"
|
||||
let (head, name) = line.split_once('\u{0}').unwrap_or(("", line));
|
||||
// 格式: "*\0branch_name\0refs/...",三字段(HEAD 标记 / 短名 / 完整 ref)。
|
||||
let parts: Vec<&str> = line.split('\u{0}').collect();
|
||||
if parts.len() < 2 { continue; }
|
||||
let (head, short) = (parts[0], parts[1]);
|
||||
// 跳过 remote HEAD 伪分支(refs/remotes/<remote>/HEAD → 短名即裸 remote 名如 origin,
|
||||
// 指向远端默认分支,非真实分支,列出会误导)。
|
||||
let full = parts.get(2).copied().unwrap_or("");
|
||||
if full.ends_with("/HEAD") { continue; }
|
||||
let is_current = head.contains('*');
|
||||
let name = name.trim().to_string();
|
||||
let name = short.trim().to_string();
|
||||
if name.is_empty() { continue; }
|
||||
if is_current { current = name.clone(); }
|
||||
branches.push(serde_json::json!({ "name": name, "is_current": is_current }));
|
||||
@@ -655,6 +666,30 @@ const NOISE_DIRS: &[&str] = &[
|
||||
".vite",
|
||||
];
|
||||
|
||||
/// 判断目录是否含可见子项(过滤隐藏文件/噪音目录后,与 `get_module_file_tree` 展开实际展示一致)。
|
||||
///
|
||||
/// 供 `FileTreeEntry.has_children` 用:前端未展开目录即可据此在行内标注空目录,
|
||||
/// 不必等用户展开才发现是空的。`read_dir` 首个子项命中即短路返回,开销为 O(可见项数)。
|
||||
fn dir_has_visible_children(dir: &Path) -> bool {
|
||||
let Ok(read) = std::fs::read_dir(dir) else {
|
||||
// 目录不可读(权限等)按空处理,避免误导为有内容
|
||||
return false;
|
||||
};
|
||||
read.flatten().any(|entry| {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
// 与列目录过滤保持一致:隐藏文件 + 噪音目录不算可见子项
|
||||
if name.starts_with('.') {
|
||||
return false;
|
||||
}
|
||||
if NOISE_DIRS.iter().any(|n| *n == name.as_ref()) {
|
||||
return false;
|
||||
}
|
||||
// 与列目录一致:无法判定类型的条目(断链符号链接等)视为不可见
|
||||
entry.file_type().is_ok()
|
||||
})
|
||||
}
|
||||
|
||||
/// 文件树条目(单层;前端点击文件夹再懒加载下一层)。
|
||||
#[derive(Debug, Serialize)]
|
||||
struct FileTreeEntry {
|
||||
@@ -668,6 +703,9 @@ struct FileTreeEntry {
|
||||
/// 文件夹恒为 None(只标文件)。
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
git_status: Option<String>,
|
||||
/// 目录是否含可见子项(过滤隐藏/噪音目录后,与展开实际展示一致)。
|
||||
/// 文件恒为 false;目录在列目录时即时统计。前端未展开时行内标注空目录用。
|
||||
has_children: bool,
|
||||
}
|
||||
|
||||
/// 列出工程目录(可钻入子目录)的文件树(单层 + git 状态合并)。
|
||||
@@ -771,12 +809,18 @@ pub async fn get_module_file_tree(
|
||||
} else {
|
||||
git_map_clone.get(&posix_rel).cloned()
|
||||
};
|
||||
let has_children = if is_dir {
|
||||
dir_has_visible_children(&entry.path())
|
||||
} else {
|
||||
false
|
||||
};
|
||||
items.push(FileTreeEntry {
|
||||
name,
|
||||
path: posix_rel,
|
||||
is_dir,
|
||||
size,
|
||||
git_status,
|
||||
has_children,
|
||||
});
|
||||
}
|
||||
// 目录优先,各自字母序(稳定可预期,前端无需再排)。
|
||||
|
||||
Reference in New Issue
Block a user