diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 692eb863..96e4f357 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.pdb b/1.6/1.6/Assemblies/WulaFallenEmpire.pdb index 897cd330..e871ca50 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.pdb and b/1.6/1.6/Assemblies/WulaFallenEmpire.pdb differ diff --git a/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs b/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs index 79e14030..0edce94c 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/SimpleAIClient.cs @@ -90,6 +90,7 @@ namespace WulaFallenEmpire.EventSystem.AI request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); + request.timeout = 120; // 120 seconds timeout if (!string.IsNullOrEmpty(_apiKey)) { request.SetRequestHeader("Authorization", $"Bearer {_apiKey}"); diff --git a/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs b/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs index fe2c4582..112037b5 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs @@ -22,6 +22,20 @@ namespace WulaFallenEmpire.EventSystem.AI.UI private bool _isThinking = false; private Vector2 _scrollPosition = Vector2.zero; private bool _scrollToBottom = false; + private int _lastHistoryCount = -1; + private float _lastUsedWidth = -1f; + private List _cachedMessages = new List(); + private float _cachedTotalHeight = 0f; + + private class CachedMessage + { + public string role; + public string message; + public string displayText; + public float height; + public float yOffset; + public GameFont font; + } private List _tools = new List(); private AIIntelligenceCore _core; private Dictionary _portraits = new Dictionary(); @@ -1400,6 +1414,57 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori _inputText = ""; } } + private void UpdateCacheIfNeeded(float width) + { + if (_core == null) return; + var history = _core.GetHistorySnapshot(); + if (history == null) return; + + if (Math.Abs(_lastUsedWidth - width) < 0.1f && history.Count == _lastHistoryCount) + { + return; + } + + _lastUsedWidth = width; + _lastHistoryCount = history.Count; + _cachedMessages.Clear(); + _cachedTotalHeight = 0f; + float curY = 0f; + float innerPadding = 5f; + float contentWidth = width - innerPadding * 2; + + for (int i = 0; i < history.Count; i++) + { + var entry = history[i]; + string messageText = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message; + + // Always skip tool/toolcall/system messages (original behavior) + if (entry.role == "tool" || entry.role == "system" || entry.role == "toolcall") continue; + if (string.IsNullOrEmpty(messageText) || (entry.role == "user" && messageText.StartsWith("[Tool Results]"))) continue; + + bool isLastMessage = i == history.Count - 1; + GameFont font = (isLastMessage && entry.role == "assistant") ? GameFont.Small : GameFont.Tiny; + float padding = (isLastMessage && entry.role == "assistant") ? 30f : 15f; + + Text.Font = font; + float height = Text.CalcHeight(messageText, contentWidth) + padding; + + _cachedMessages.Add(new CachedMessage + { + role = entry.role, + message = entry.message, + displayText = messageText, + height = height, + yOffset = curY, + font = font + }); + + curY += height + 10f; + } + + _cachedTotalHeight = curY; + } + private void DrawChatHistory(Rect rect) { var originalFont = Text.Font; @@ -1407,98 +1472,72 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori try { - float viewHeight = 0f; - var filteredHistory = _history.Where(e => e.role != "tool" && e.role != "system" && e.role != "toolcall").ToList(); - - // 添加内边距 float innerPadding = 5f; float contentWidth = rect.width - 16f - innerPadding * 2; - // 预计计算高度 - 使用小字体 - for (int i = 0; i < filteredHistory.Count; i++) - { - var entry = filteredHistory[i]; - string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message; - if (string.IsNullOrWhiteSpace(text) || (entry.role == "user" && text.StartsWith("[Tool Results]"))) continue; - bool isLastMessage = i == filteredHistory.Count - 1; + UpdateCacheIfNeeded(rect.width - 16f); - // 设置更小的字体 - if (isLastMessage && entry.role == "assistant") - { - Text.Font = GameFont.Small; // 原来是 Medium,改为 Small - } - else - { - Text.Font = GameFont.Tiny; // 原来是 Small,改为 Tiny - } - // 增加padding - float padding = (isLastMessage && entry.role == "assistant") ? 30f : 15f; - viewHeight += Text.CalcHeight(text, contentWidth) + padding + 10f; - } - - // 为思考指示器预留高度 + float totalHeight = _cachedTotalHeight; if (_isThinking) { - viewHeight += 40f; + totalHeight += 40f; } - Rect viewRect = new Rect(0f, 0f, rect.width - 16f, viewHeight); + Rect viewRect = new Rect(0f, 0f, rect.width - 16f, totalHeight); if (_scrollToBottom) { - _scrollPosition.y = float.MaxValue; + _scrollPosition.y = totalHeight - rect.height; _scrollToBottom = false; } Widgets.BeginScrollView(rect, ref _scrollPosition, viewRect); - float curY = 0f; - for (int i = 0; i < filteredHistory.Count; i++) + float viewTop = _scrollPosition.y; + float viewBottom = _scrollPosition.y + rect.height; + + foreach (var entry in _cachedMessages) { - var entry = filteredHistory[i]; - string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message; + if (entry.yOffset + entry.height < viewTop - 100f) continue; + if (entry.yOffset > viewBottom + 100f) break; - if (string.IsNullOrEmpty(text) || (entry.role == "user" && text.StartsWith("[Tool Results]"))) continue; - bool isLastMessage = i == filteredHistory.Count - 1; - - // 设置更小的字体 - if (isLastMessage && entry.role == "assistant") - { - Text.Font = GameFont.Small; // 原来是 Medium,改为 Small - } - else - { - Text.Font = GameFont.Tiny; // 原来是 Small,改为 Tiny - } - - float padding = (isLastMessage && entry.role == "assistant") ? 30f : 15f; - float height = Text.CalcHeight(text, contentWidth) + padding; - - // 娣诲姞鍐呰竟璺? - Rect labelRect = new Rect(innerPadding, curY, contentWidth, height); + Text.Font = entry.font; + Rect labelRect = new Rect(innerPadding, entry.yOffset, contentWidth, entry.height); if (entry.role == "user") { Text.Anchor = TextAnchor.MiddleRight; - Widgets.Label(labelRect, $"{text}"); + Widgets.Label(labelRect, $"{entry.displayText}"); + } + else if (entry.role == "assistant") + { + Text.Anchor = TextAnchor.MiddleLeft; + Widgets.Label(labelRect, $"P.I.A: {entry.displayText}"); } else { Text.Anchor = TextAnchor.MiddleLeft; - Widgets.Label(labelRect, $"P.I.A: {text}"); + GUI.color = Color.gray; + Widgets.Label(labelRect, $"[{entry.role}] {entry.displayText}"); + GUI.color = Color.white; } - curY += height + 10f; } if (_isThinking) { - DrawThinkingIndicator(new Rect(innerPadding, curY, contentWidth, 35f)); + float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 0f; + if (thinkingY + 40f >= viewTop && thinkingY <= viewBottom) + { + DrawThinkingIndicator(new Rect(innerPadding, thinkingY, contentWidth, 35f)); + } } + Widgets.EndScrollView(); } finally { Text.Font = originalFont; Text.Anchor = originalAnchor; + GUI.color = Color.white; } } diff --git a/Source/WulaFallenEmpire/EventSystem/AI/UI/Overlay_WulaLink.cs b/Source/WulaFallenEmpire/EventSystem/AI/UI/Overlay_WulaLink.cs index e8929fc5..d8c2a54a 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/UI/Overlay_WulaLink.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/UI/Overlay_WulaLink.cs @@ -19,6 +19,19 @@ namespace WulaFallenEmpire.EventSystem.AI.UI private Vector2 _scrollPosition = Vector2.zero; private string _inputText = ""; private bool _scrollToBottom = true; + private int _lastHistoryCount = -1; + private float _lastUsedWidth = -1f; + private List _cachedMessages = new List(); + private float _cachedTotalHeight = 0f; + + private class CachedMessage + { + public string role; + public string message; + public string displayText; + public float height; + public float yOffset; + } // HUD / Minimized State @@ -323,67 +336,87 @@ namespace WulaFallenEmpire.EventSystem.AI.UI return Widgets.ButtonInvisible(rect); } - private void DrawMessageList(Rect rect) + private void UpdateCacheIfNeeded(float width) { var history = _core?.GetHistorySnapshot(); if (history == null) return; - // Filter out tool messages, empty messages, and XML-only messages - var displayHistory = new List<(string role, string message, string displayText)>(); + // 如果宽度没变且条数没变,就不重算 + if (Math.Abs(_lastUsedWidth - width) < 0.1f && history.Count == _lastHistoryCount) + { + return; + } + + _lastUsedWidth = width; + _lastHistoryCount = history.Count; + _cachedMessages.Clear(); + _cachedTotalHeight = 0f; + float reducedSpacing = 8f; + float curY = 10f; + foreach (var msg in history) { - // Skip tool/toolcall messages to avoid empty spacing + // Filter logic if (msg.role == "tool" || msg.role == "toolcall") continue; if (msg.role == "system" && !Prefs.DevMode) continue; - // For assistant messages, strip XML and check if empty string displayText = msg.message; if (msg.role == "assistant") { displayText = StripXmlTags(msg.message)?.Trim() ?? ""; } - // Skip empty or whitespace-only messages if (string.IsNullOrWhiteSpace(displayText)) continue; + + float h = CalcMessageHeight(msg.role == "assistant" ? displayText : msg.message, width); - displayHistory.Add((msg.role, msg.message, displayText)); + _cachedMessages.Add(new CachedMessage + { + role = msg.role, + message = msg.message, + displayText = displayText, + height = h, + yOffset = curY + }); + + curY += h + reducedSpacing; } - // Setup ScrollView - float contentHeight = 0f; + _cachedTotalHeight = curY; + } + + private void DrawMessageList(Rect rect) + { float width = rect.width - 26f; // Scrollbar space - float reducedSpacing = 8f; - - List heights = new List(); - foreach (var msg in displayHistory) + UpdateCacheIfNeeded(width); + + float totalContentHeight = _cachedTotalHeight; + if (_core != null && _core.IsThinking) { - string textToMeasure = msg.role == "assistant" ? msg.displayText : msg.message; - float h = CalcMessageHeight(textToMeasure, width); - heights.Add(h); - contentHeight += h + reducedSpacing; - } - - if (_core.IsThinking) - { - contentHeight += 40f; + totalContentHeight += 40f; } - Rect viewRect = new Rect(0, 0, width, contentHeight); + Rect viewRect = new Rect(0, 0, width, totalContentHeight); if (_scrollToBottom) { - _scrollPosition.y = contentHeight - rect.height; + _scrollPosition.y = totalContentHeight - rect.height; _scrollToBottom = false; } Widgets.BeginScrollView(rect, ref _scrollPosition, viewRect); - float curY = 10f; - for (int i = 0; i < displayHistory.Count; i++) + // 虚拟化渲染:只绘制在窗口内的消息 + float viewTop = _scrollPosition.y; + float viewBottom = _scrollPosition.y + rect.height; + + foreach (var entry in _cachedMessages) { - var entry = displayHistory[i]; - float h = heights[i]; - Rect msgRect = new Rect(0, curY, width, h); + // 检查是否在可见范围内 (略微预留 Buffer) + if (entry.yOffset + entry.height < viewTop - 100f) continue; + if (entry.yOffset > viewBottom + 100f) break; + + Rect msgRect = new Rect(0, entry.yOffset, width, entry.height); if (entry.role == "user") { @@ -401,14 +434,16 @@ namespace WulaFallenEmpire.EventSystem.AI.UI { DrawSystemMessage(msgRect, entry.message); } - - curY += h + reducedSpacing; } - if (_core.IsThinking) + if (_core != null && _core.IsThinking) { - Rect thinkingRect = new Rect(0, curY, width, 30f); - DrawThinkingIndicator(thinkingRect); + float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 10f; + Rect thinkingRect = new Rect(0, thinkingY, width, 30f); + if (thinkingY + 30f >= viewTop && thinkingY <= viewBottom) + { + DrawThinkingIndicator(thinkingRect); + } } Widgets.EndScrollView();