From b0a1ee0de238758144770c2225d50595439cd021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sat, 5 Sep 2026 10:42:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20DirectProject=20=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E5=B9=82=E7=AD=89=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按文件尾部反向分块查找带 id 的历史 item 避免每次追加都完整读取并扫描历史文件 --- .../src/agent/direct_project_history.rs | 93 ++++++++++++++++++- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs index c438990e0..edad7c9c8 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_project_history.rs @@ -5,7 +5,7 @@ use crate::project::{ use crate::{LocalConversationMessageRecord, LocalConversationResult}; use serde_json::Value; use std::fs::File; -use std::io::{BufRead, BufReader}; +use std::io::{BufRead, BufReader, Read, Seek, SeekFrom}; use std::path::{Path, PathBuf}; const DIRECT_PROJECT_HISTORY_RECORD_TYPE: &str = "response_item"; @@ -22,6 +22,92 @@ fn record(item: &Value) -> Result { .map_err(|error| format!("序列化 DirectProject 历史失败:{error}")) } +const DIRECT_PROJECT_HISTORY_REVERSE_SCAN_CHUNK_BYTES: usize = 16 * 1024; + +fn find_direct_project_history_item_by_id_at( + path: &Path, + item_id: &str, +) -> Result, String> { + let mut file = File::open(path) + .map_err(|error| format!("打开 DirectProject 历史失败:{}: {error}", path.display()))?; + let mut position = file + .metadata() + .map_err(|error| { + format!( + "读取 DirectProject 历史元数据失败:{}: {error}", + path.display() + ) + })? + .len(); + let mut pending = Vec::new(); + let mut chunk = vec![0u8; DIRECT_PROJECT_HISTORY_REVERSE_SCAN_CHUNK_BYTES]; + + loop { + if position == 0 { + break; + } + let read_len = usize::try_from(position) + .unwrap_or(usize::MAX) + .min(chunk.len()); + position -= read_len as u64; + file.seek(SeekFrom::Start(position)) + .map_err(|error| format!("定位 DirectProject 历史失败:{}: {error}", path.display()))?; + file.read_exact(&mut chunk[..read_len]) + .map_err(|error| format!("读取 DirectProject 历史失败:{}: {error}", path.display()))?; + + let mut combined = Vec::with_capacity(read_len + pending.len()); + combined.extend_from_slice(&chunk[..read_len]); + combined.extend_from_slice(&pending); + let mut line_end = combined.len(); + while let Some(newline) = combined[..line_end].iter().rposition(|byte| *byte == b'\n') { + let line = &combined[newline + 1..line_end]; + if !line.is_empty() { + if let Some(item) = direct_project_history_item_from_line(path, line)? { + if item.get("id").and_then(Value::as_str) == Some(item_id) { + return Ok(Some(item)); + } + } + } + line_end = newline; + } + pending = combined[..line_end].to_vec(); + } + + if !pending.is_empty() { + if let Some(item) = direct_project_history_item_from_line(path, &pending)? { + if item.get("id").and_then(Value::as_str) == Some(item_id) { + return Ok(Some(item)); + } + } + } + Ok(None) +} + +fn direct_project_history_item_from_line( + path: &Path, + line: &[u8], +) -> Result, String> { + if line.iter().all(|byte| byte.is_ascii_whitespace()) { + return Ok(None); + } + let parsed: Value = serde_json::from_slice(line) + .map_err(|error| format!("解析 DirectProject 历史失败:{}: {error}", path.display()))?; + if parsed.get("type").and_then(Value::as_str) != Some(DIRECT_PROJECT_HISTORY_RECORD_TYPE) { + return Err(format!( + "DirectProject 历史记录类型无效:{}", + path.display() + )); + } + let item = parsed + .get("payload") + .cloned() + .ok_or_else(|| format!("DirectProject 历史记录缺少 payload:{}", path.display()))?; + if is_direct_project_internal_context_item(&item) { + return Ok(None); + } + Ok(Some(item)) +} + pub(crate) fn append_direct_project_history_item_at( root: &Path, item: &Value, @@ -33,10 +119,7 @@ pub(crate) fn append_direct_project_history_item_at( let lock = project_append_lock_for(&path)?; let _append_guard = lock.lock("DirectProject 历史追加写")?; if let Some(item_id) = item.get("id").and_then(Value::as_str) { - if let Some(existing) = read_direct_project_history_items_at(root)? - .into_iter() - .find(|existing| existing.get("id").and_then(Value::as_str) == Some(item_id)) - { + if let Some(existing) = find_direct_project_history_item_by_id_at(&path, item_id)? { if &existing == item { return Ok(()); }