From 7a7a8417339ee50bc9b94b93e2ccf4519bfe3754 Mon Sep 17 00:00:00 2001
From: Suzumiya
Date: Wed, 16 Sep 2026 02:57:27 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=9E=E5=90=88=E9=87=8D?=
=?UTF-8?q?=E8=BF=9B=E7=8A=B6=E6=80=81=E5=B9=B6=E6=94=B6=E8=B5=B7=E5=B7=B2?=
=?UTF-8?q?=E5=AE=8C=E6=88=90=E6=89=A7=E8=A1=8C=E8=BF=87=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
活动回合快照与Direct事件统一管理生命周期,避免历史终态回放恢复忙碌态
用户消息显示发送时间,历史信封保存时间并通过独立映射回读
完成后统一折叠中间输出和工具调用,最终回复与失败提示保持可见
同步回归用例与规范文档,保留原生实机验收待办
---
.../src/agent/direct_project_history.rs | 80 ++++-
.../src/agent/direct_thread_manager.rs | 1 +
.../src-tauri/src/commands.rs | 8 +-
apps/ai-game-creator-shell/src/App.tsx | 338 +++++++++---------
.../ProjectSupervisorView.tsx | 168 +++++----
.../project-workspace/directThreadEvents.ts | 117 +-----
.../directTurnPresentation.ts | 65 +++-
.../toolCallGroupPresentation.ts | 1 +
apps/ai-game-creator-shell/src/styles.css | 28 ++
.../tests/directThreadEvents.test.ts | 39 ++
.../tests/directTurnPresentation.test.ts | 56 ++-
...【实施计划】对话回合唯一投影-2026-09-16.md | 2 +
.../【里程碑】对话回合唯一投影-2026-09-16.md | 7 +-
.../shared-memory/project-overview.md | 1 +
...案】AI游戏创作智能体App实施计划-2026-06-24.md | 9 +-
...方案】GameAgent对话工具调用卡片-2026-09-14.md | 1 +
16 files changed, 558 insertions(+), 363 deletions(-)
create mode 100644 apps/ai-game-creator-shell/tests/directThreadEvents.test.ts
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 24b3aed74..130c01549 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,6 +5,7 @@ use crate::project::{
};
use crate::{LocalConversationMessageRecord, LocalConversationResult};
use serde_json::Value;
+use std::collections::BTreeMap;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
@@ -91,6 +92,10 @@ fn record(item: &Value) -> Result {
serde_json::to_string(&serde_json::json!({
"type": DIRECT_PROJECT_HISTORY_RECORD_TYPE,
"payload": item,
+ "recordedAt": std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map(|duration| duration.as_millis() as u64)
+ .unwrap_or(0),
}))
.map_err(|error| format!("序列化 DirectProject 历史失败:{error}"))
}
@@ -470,6 +475,13 @@ fn direct_project_message_item(role: &str, content: &str, message_id: Option<&st
}
pub(crate) fn read_direct_project_history_items_at(root: &Path) -> Result, String> {
+ Ok(read_direct_project_history_entries_at(root)?
+ .into_iter()
+ .map(|(item, _)| item)
+ .collect())
+}
+
+fn read_direct_project_history_entries_at(root: &Path) -> Result, String> {
let path = history_path(root);
if !prepare_game_creator_private_path_for_read(&path, false, "DirectProject 历史")? {
return Ok(Vec::new());
@@ -507,7 +519,13 @@ pub(crate) fn read_direct_project_history_items_at(root: &Path) -> Result,
limit: usize,
-) -> Result<(Vec, bool), String> {
- let items = read_direct_project_history_items_at(root)?;
+) -> Result<(Vec, bool, BTreeMap), String> {
+ let items = read_direct_project_history_entries_at(root)?;
let end = match before_item_id {
Some(item_id) => items
.iter()
- .position(|item| item.get("id").and_then(Value::as_str) == Some(item_id))
+ .position(|(item, _)| item.get("id").and_then(Value::as_str) == Some(item_id))
.ok_or_else(|| format!("DirectProject 历史中不存在 item:{item_id}"))?,
None => items.len(),
};
let bounded_limit = limit.clamp(1, 200);
let start = end.saturating_sub(bounded_limit);
- Ok((items[start..end].to_vec(), start > 0))
+ let slice = &items[start..end];
+ let timestamps = slice
+ .iter()
+ .filter_map(|(item, at)| {
+ let id = item.get("id").and_then(Value::as_str)?;
+ (*at > 0).then(|| (id.to_string(), *at))
+ })
+ .collect();
+ Ok((
+ slice.iter().map(|(item, _)| item.clone()).collect(),
+ start > 0,
+ timestamps,
+ ))
}
pub(crate) fn read_direct_project_last_item_id_at(root: &Path) -> Result
);
};
- const renderMessage = (message: ChatMessage, index: number) => (
-
-
- {showDesignReasoning && message.reasoningText ? (
-
- 思考过程
- {message.reasoningText}
-
- ) : null}
-
- );
+ const renderMessage = (message: ChatMessage, index: number) => {
+ const sentAt =
+ message.role === 'user' ? directMessageTimestamp(message.updatedAt) : 0;
+ return (
+
+
+ {sentAt > 0 ? (
+
+ ) : null}
+ {showDesignReasoning && message.reasoningText ? (
+
+ 思考过程
+ {message.reasoningText}
+
+ ) : null}
+
+ );
+ };
const submitButton = (