fix save function entrance

This commit is contained in:
2026-06-26 21:27:44 +08:00
parent 5f9f5e19ee
commit 48424382ce
@@ -1831,8 +1831,9 @@ async fn request_generator_game_draft_with_client(
continue;
}
Err(error) => {
// 调用失败(含空返回重试耗尽)时,把本次输入连同错误一并落盘,便于复现定位。
persist_llm_game_draft_error_input(
// 调用失败(含空返回重试耗尽)时,把本次输入连同错误一并落盘,便于复现定位(仅 debug 构建)
#[cfg(all(debug_assertions, not(test)))]
debug_drafts::persist_error_input(
system_prompt,
user_prompt.as_str(),
&error.to_string(),
@@ -1841,8 +1842,9 @@ async fn request_generator_game_draft_with_client(
}
}
};
// 先把原始返回落盘,再解析;解析失败(如输出截断)时仍能从仓库里拿到完整原文。
persist_llm_game_draft_snapshot(response.content.as_str());
// 先把原始返回落盘,再解析;解析失败(如输出截断)时仍能从仓库里拿到完整原文(仅 debug 构建)
#[cfg(all(debug_assertions, not(test)))]
debug_drafts::persist_snapshot(response.content.as_str());
let content = strip_llm_thinking_blocks(response.content.as_str());
parse_llm_game_draft_response(content.as_str())
}
@@ -3747,103 +3749,103 @@ fn parse_llm_game_draft_response(content: &str) -> Result<LlmGameDraft, String>
.map_err(|error| format!("解析 LLM 游戏草案失败:{error}"))
}
// 找到仓库根(包含 apps/ai-game-creator-shell/src-tauri/Cargo.toml 的目录),
// 复用本地 env 那套向上查找逻辑,以便把原始 LLM 草案落到仓库内而非 tmp 项目目录。
fn game_creator_repo_root() -> Option<PathBuf> {
let mut roots = Vec::new();
if let Ok(cwd) = std::env::current_dir() {
roots.push(cwd);
}
if let Ok(exe) = std::env::current_exe() {
if let Some(parent) = exe.parent() {
roots.push(parent.to_path_buf());
// 调试落盘:保存 LLM 原始输出 / 失败输入,便于排查截断、空返回等问题。
// 整个模块用 #[cfg] 编译期门控——仅在开发(debug)且非测试构建中编入二进制;
// 生产 release 与 cargo test 下模块与调用处一并被剔除,不会往仓库写任何文件。
#[cfg(all(debug_assertions, not(test)))]
mod debug_drafts {
use super::*;
// 找到仓库根(包含 apps/ai-game-creator-shell/src-tauri/Cargo.toml 的目录),
// 以便把草案落到仓库内而非 tmp 项目目录。
fn repo_root() -> Option<PathBuf> {
let mut roots = Vec::new();
if let Ok(cwd) = std::env::current_dir() {
roots.push(cwd);
}
}
for root in roots {
for directory in root.ancestors().take(8) {
let marker = directory
.join("apps")
.join("ai-game-creator-shell")
.join("src-tauri")
.join("Cargo.toml");
if marker.is_file() {
return Some(directory.to_path_buf());
if let Ok(exe) = std::env::current_exe() {
if let Some(parent) = exe.parent() {
roots.push(parent.to_path_buf());
}
}
for root in roots {
for directory in root.ancestors().take(8) {
let marker = directory
.join("apps")
.join("ai-game-creator-shell")
.join("src-tauri")
.join("Cargo.toml");
if marker.is_file() {
return Some(directory.to_path_buf());
}
}
}
None
}
None
}
// 把生成器原始返回(含被截断、解析失败的内容)保存到仓库 apps/ai-game-creator-shell/.llm-drafts/
// 便于排查 “EOF while parsing a string” 这类输出截断问题。尽力而为,不阻断生成主流程。
fn persist_llm_game_draft_snapshot(raw_content: &str) {
// 仅在开发/调试构建落盘排查;生产(release)构建与单元测试一律跳过,不往仓库写快照文件。
if cfg!(test) || !cfg!(debug_assertions) {
return;
// 把生成器原始返回(含被截断、解析失败的内容)保存到仓库 .llm-drafts/
// 便于排查 “EOF while parsing a string” 这类输出截断问题。尽力而为,不阻断主流程。
pub(super) fn persist_snapshot(raw_content: &str) {
let Some(repo_root) = repo_root() else {
eprintln!("llm.draft.snapshot.skip: 未能定位仓库根目录");
return;
};
let dir = repo_root
.join("apps")
.join("ai-game-creator-shell")
.join(".llm-drafts");
if let Err(error) = fs::create_dir_all(&dir) {
eprintln!("llm.draft.snapshot.dir.failed: {}: {error}", dir.display());
return;
}
let path = dir.join(format!("draft-{}.txt", unix_millis()));
if let Err(error) = fs::write(&path, raw_content) {
eprintln!(
"llm.draft.snapshot.write.failed: {}: {error}",
path.display()
);
return;
}
// 同步更新 latest.txt,方便直接打开最近一次草案。
let latest = dir.join("latest.txt");
let _ = fs::write(&latest, raw_content);
eprintln!("llm.draft.snapshot.saved: {}", path.display());
}
let Some(repo_root) = game_creator_repo_root() else {
eprintln!("llm.draft.snapshot.skip: 未能定位仓库根目录");
return;
};
let dir = repo_root
.join("apps")
.join("ai-game-creator-shell")
.join(".llm-drafts");
if let Err(error) = fs::create_dir_all(&dir) {
eprintln!("llm.draft.snapshot.dir.failed: {}: {error}", dir.display());
return;
}
let path = dir.join(format!("draft-{}.txt", unix_millis()));
if let Err(error) = fs::write(&path, raw_content) {
eprintln!(
"llm.draft.snapshot.write.failed: {}: {error}",
path.display()
);
return;
}
// 同步更新 latest.txt,方便直接打开最近一次草案。
let latest = dir.join("latest.txt");
let _ = fs::write(&latest, raw_content);
eprintln!("llm.draft.snapshot.saved: {}", path.display());
}
// LLM 调用失败(例如返回内容为空)时,把本次发送给模型的输入(system + 完整 user prompt
// 连同错误信息一起落盘到仓库 .llm-drafts/,便于按同样输入复现与定位。尽力而为,不阻断主流程。
fn persist_llm_game_draft_error_input(system_prompt: &str, user_prompt: &str, error: &str) {
// 仅在开发/调试构建落盘排查;生产(release)构建与单元测试一律跳过,不往仓库写文件。
if cfg!(test) || !cfg!(debug_assertions) {
return;
}
let Some(repo_root) = game_creator_repo_root() else {
eprintln!("llm.draft.error-input.skip: 未能定位仓库根目录");
return;
};
let dir = repo_root
.join("apps")
.join("ai-game-creator-shell")
.join(".llm-drafts");
if let Err(io_error) = fs::create_dir_all(&dir) {
eprintln!(
"llm.draft.error-input.dir.failed: {}: {io_error}",
dir.display()
// LLM 调用失败(例如返回内容为空)时,把本次发送给模型的输入(system + 完整 user prompt
// 连同错误信息一起落盘到 .llm-drafts/,便于按同样输入复现与定位。尽力而为,不阻断主流程。
pub(super) fn persist_error_input(system_prompt: &str, user_prompt: &str, error: &str) {
let Some(repo_root) = repo_root() else {
eprintln!("llm.draft.error-input.skip: 未能定位仓库根目录");
return;
};
let dir = repo_root
.join("apps")
.join("ai-game-creator-shell")
.join(".llm-drafts");
if let Err(io_error) = fs::create_dir_all(&dir) {
eprintln!(
"llm.draft.error-input.dir.failed: {}: {io_error}",
dir.display()
);
return;
}
let body = format!(
"# LLM 生成失败输入快照\n\n## 错误\n{error}\n\n## System Prompt\n{system_prompt}\n\n## User Prompt(含用户需求/短长期记忆/spec/agenda/组 brief/findings\n{user_prompt}\n",
);
return;
let path = dir.join(format!("error-input-{}.txt", unix_millis()));
if let Err(io_error) = fs::write(&path, &body) {
eprintln!(
"llm.draft.error-input.write.failed: {}: {io_error}",
path.display()
);
return;
}
// 同步更新 latest-error-input.txt,方便直接打开最近一次失败输入。
let latest = dir.join("latest-error-input.txt");
let _ = fs::write(&latest, &body);
eprintln!("llm.draft.error-input.saved: {}", path.display());
}
let body = format!(
"# LLM 生成失败输入快照\n\n## 错误\n{error}\n\n## System Prompt\n{system_prompt}\n\n## User Prompt(含用户需求/短长期记忆/spec/agenda/组 brief/findings\n{user_prompt}\n",
);
let path = dir.join(format!("error-input-{}.txt", unix_millis()));
if let Err(io_error) = fs::write(&path, &body) {
eprintln!(
"llm.draft.error-input.write.failed: {}: {io_error}",
path.display()
);
return;
}
// 同步更新 latest-error-input.txt,方便直接打开最近一次失败输入。
let latest = dir.join("latest-error-input.txt");
let _ = fs::write(&latest, &body);
eprintln!("llm.draft.error-input.saved: {}", path.display());
}
fn strip_llm_thinking_blocks(content: &str) -> String {