This commit is contained in:
2025-12-31 13:46:49 +08:00
parent 2af305de68
commit 244ba3d354
5 changed files with 362 additions and 35 deletions

View File

@@ -964,6 +964,13 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
return trimmed.Length >= 4; return trimmed.Length >= 4;
} }
private void AddTraceNote(string note)
{
if (string.IsNullOrWhiteSpace(note)) return;
_history.Add(("trace", note.Trim()));
PersistHistory();
}
private bool IsToolAvailable(string toolName) private bool IsToolAvailable(string toolName)
{ {
if (string.IsNullOrWhiteSpace(toolName)) return false; if (string.IsNullOrWhiteSpace(toolName)) return false;
@@ -1165,6 +1172,10 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
{ {
continue; continue;
} }
if (string.Equals(entry.role, "trace", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (string.Equals(entry.role, "tool", StringComparison.OrdinalIgnoreCase)) if (string.Equals(entry.role, "tool", StringComparison.OrdinalIgnoreCase))
{ {
if (lastUserIndex != -1 && i > lastUserIndex) if (lastUserIndex != -1 && i > lastUserIndex)
@@ -1886,6 +1897,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
if (LooksLikeNaturalReply(normalizedResponse)) if (LooksLikeNaturalReply(normalizedResponse))
{ {
toolPhaseReplyCandidate = normalizedResponse; toolPhaseReplyCandidate = normalizedResponse;
AddTraceNote(normalizedResponse);
break; break;
} }
@@ -1902,6 +1914,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
if (LooksLikeNaturalReply(normalizedFixed)) if (LooksLikeNaturalReply(normalizedFixed))
{ {
toolPhaseReplyCandidate = normalizedFixed; toolPhaseReplyCandidate = normalizedFixed;
AddTraceNote(normalizedFixed);
} }
if (Prefs.DevMode) if (Prefs.DevMode)
{ {
@@ -1932,6 +1945,24 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
} }
} }
if (!string.IsNullOrWhiteSpace(parsedSource))
{
string traceText = parsedSource;
if (!string.IsNullOrWhiteSpace(jsonFragment))
{
int idx = traceText.IndexOf(jsonFragment, StringComparison.Ordinal);
if (idx >= 0)
{
traceText = traceText.Remove(idx, jsonFragment.Length);
}
}
traceText = traceText.Trim();
if (LooksLikeNaturalReply(traceText))
{
AddTraceNote(traceText);
}
}
var invalidTools = toolCalls var invalidTools = toolCalls
.Where(c => !IsToolAvailable(c.Name)) .Where(c => !IsToolAvailable(c.Name))
.Select(c => c.Name) .Select(c => c.Name)

View File

@@ -31,6 +31,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
private int _currentPortraitId = 0; private int _currentPortraitId = 0;
private static readonly Regex ExpressionTagRegex = new Regex(@"\[EXPR\s*:\s*([1-6])\s*\]", RegexOptions.IgnoreCase); private static readonly Regex ExpressionTagRegex = new Regex(@"\[EXPR\s*:\s*([1-6])\s*\]", RegexOptions.IgnoreCase);
private readonly Dictionary<int, bool> _traceExpandedByAssistantIndex = new Dictionary<int, bool>(); private readonly Dictionary<int, bool> _traceExpandedByAssistantIndex = new Dictionary<int, bool>();
private readonly Dictionary<int, string> _traceHeaderByAssistantIndex = new Dictionary<int, string>();
private class CachedMessage private class CachedMessage
{ {
@@ -325,6 +326,11 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (Math.Abs(_lastUsedWidth - width) < 0.1f && history.Count == _lastHistoryCount) return; if (Math.Abs(_lastUsedWidth - width) < 0.1f && history.Count == _lastHistoryCount) return;
_lastUsedWidth = width; _lastUsedWidth = width;
if (_lastHistoryCount >= 0 && history.Count < _lastHistoryCount)
{
_traceExpandedByAssistantIndex.Clear();
_traceHeaderByAssistantIndex.Clear();
}
_lastHistoryCount = history.Count; _lastHistoryCount = history.Count;
_cachedMessages.Clear(); _cachedMessages.Clear();
_cachedTotalHeight = 0f; _cachedTotalHeight = 0f;
@@ -333,6 +339,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float contentWidth = width - innerPadding * 2; float contentWidth = width - innerPadding * 2;
var toolcallBuffer = new List<string>(); var toolcallBuffer = new List<string>();
var toolResultBuffer = new List<string>(); var toolResultBuffer = new List<string>();
var traceNoteBuffer = new List<string>();
bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true; bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true;
for (int i = 0; i < history.Count; i++) for (int i = 0; i < history.Count; i++)
@@ -342,6 +349,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
{ {
toolcallBuffer.Clear(); toolcallBuffer.Clear();
toolResultBuffer.Clear(); toolResultBuffer.Clear();
traceNoteBuffer.Clear();
} }
if (entry.role == "toolcall") if (entry.role == "toolcall")
@@ -362,6 +370,15 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
continue; continue;
} }
if (entry.role == "trace")
{
if (traceEnabled)
{
traceNoteBuffer.Add(entry.message ?? "");
}
continue;
}
string messageText = entry.role == "assistant" string messageText = entry.role == "assistant"
? ParseResponseForDisplay(entry.message) ? ParseResponseForDisplay(entry.message)
: AIIntelligenceCore.StripContextInfo(entry.message); : AIIntelligenceCore.StripContextInfo(entry.message);
@@ -371,12 +388,12 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (entry.role == "user" && entry.message.Contains("[AUTO_COMMENTARY]")) continue; if (entry.role == "user" && entry.message.Contains("[AUTO_COMMENTARY]")) continue;
if (entry.role == "assistant" && traceEnabled && toolcallBuffer.Count > 0) if (entry.role == "assistant" && traceEnabled && toolcallBuffer.Count > 0)
{ {
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer); var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
if (traceLines.Count > 0) if (traceLines.Count > 0)
{ {
int traceKey = i; int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved; bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader(); string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny; Text.Font = GameFont.Tiny;
float tracePadding = 8f; float tracePadding = 8f;
@@ -415,13 +432,18 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
toolcallBuffer.Clear(); toolcallBuffer.Clear();
toolResultBuffer.Clear(); toolResultBuffer.Clear();
traceNoteBuffer.Clear();
} }
else if (entry.role == "assistant" && traceEnabled && toolcallBuffer.Count == 0) else if (entry.role == "assistant" && traceEnabled && toolcallBuffer.Count == 0)
{ {
var traceLines = new List<string> { "无工具调用,直接回复" }; var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
if (traceLines.Count == 0)
{
traceLines.Add("无工具调用,直接回复");
}
int traceKey = i; int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved; bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader(); string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny; Text.Font = GameFont.Tiny;
float tracePadding = 8f; float tracePadding = 8f;
@@ -456,6 +478,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
traceHeaderHeight = headerHeight traceHeaderHeight = headerHeight
}); });
curY += traceHeight + 10f; curY += traceHeight + 10f;
traceNoteBuffer.Clear();
} }
if (string.IsNullOrEmpty(messageText) || (entry.role == "user" && messageText.StartsWith("[Tool Results]"))) continue; if (string.IsNullOrEmpty(messageText) || (entry.role == "user" && messageText.StartsWith("[Tool Results]"))) continue;
@@ -491,9 +514,59 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float innerPadding = 5f; float innerPadding = 5f;
float contentWidth = rect.width - 16f - innerPadding * 2; float contentWidth = rect.width - 16f - innerPadding * 2;
UpdateCacheIfNeeded(rect.width - 16f); UpdateCacheIfNeeded(rect.width - 16f);
bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true;
CachedMessage liveTraceEntry = null;
float liveTraceHeight = 0f;
if (_isThinking && traceEnabled)
{
var liveLines = BuildLiveTraceLines();
if (liveLines.Count == 0)
{
liveLines.Add("思考中…");
}
int traceKey = -1;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) ? saved : true;
string header = GetTraceHeader(traceKey, true);
Text.Font = GameFont.Tiny;
float tracePadding = 8f;
float headerWidth = Mathf.Max(10f, contentWidth - tracePadding * 2f);
string headerLine = $"{(expanded ? "v" : ">")} {header}";
float headerHeight = Text.CalcHeight(headerLine, headerWidth) + 10f;
float linesHeight = 0f;
if (expanded)
{
float lineWidth = Mathf.Max(10f, contentWidth - tracePadding * 2f);
foreach (string line in liveLines)
{
linesHeight += Text.CalcHeight(line, lineWidth) + 2f;
}
linesHeight += 8f;
}
float traceHeight = headerHeight + linesHeight;
liveTraceHeight = traceHeight + 10f;
liveTraceEntry = new CachedMessage
{
role = "trace",
message = "",
displayText = "",
height = traceHeight,
yOffset = 0f,
font = GameFont.Tiny,
isTrace = true,
traceKey = traceKey,
traceHeader = header,
traceLines = liveLines,
traceExpanded = expanded,
traceHeaderHeight = headerHeight
};
}
float totalHeight = _cachedTotalHeight; float totalHeight = _cachedTotalHeight;
if (_isThinking) totalHeight += 40f; if (_isThinking)
{
totalHeight += liveTraceEntry != null ? liveTraceHeight : 40f;
}
Rect viewRect = new Rect(0f, 0f, rect.width - 16f, totalHeight); Rect viewRect = new Rect(0f, 0f, rect.width - 16f, totalHeight);
if (_scrollToBottom) if (_scrollToBottom)
@@ -541,7 +614,15 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (_isThinking) if (_isThinking)
{ {
float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 0f; float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 0f;
if (thinkingY + 40f >= viewTop && thinkingY <= viewBottom) if (liveTraceEntry != null)
{
if (thinkingY + liveTraceEntry.height >= viewTop && thinkingY <= viewBottom)
{
Rect traceRect = new Rect(innerPadding, thinkingY, contentWidth, liveTraceEntry.height);
DrawReactTracePanel(traceRect, liveTraceEntry);
}
}
else if (thinkingY + 40f >= viewTop && thinkingY <= viewBottom)
{ {
DrawThinkingIndicator(new Rect(innerPadding, thinkingY, contentWidth, 35f)); DrawThinkingIndicator(new Rect(innerPadding, thinkingY, contentWidth, 35f));
} }
@@ -574,19 +655,19 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
return text.Split(new[] { "OPTIONS:" }, StringSplitOptions.None)[0].Trim(); return text.Split(new[] { "OPTIONS:" }, StringSplitOptions.None)[0].Trim();
} }
private List<string> BuildTraceLines(List<string> toolcallBuffer, List<string> toolResultBuffer) private List<string> BuildTraceLines(List<string> toolcallBuffer, List<string> toolResultBuffer, List<string> traceNotes)
{ {
var lines = new List<string>(); var lines = new List<string>();
if (toolcallBuffer == null || toolcallBuffer.Count == 0) return lines; bool hasToolCalls = toolcallBuffer != null && toolcallBuffer.Count > 0;
int stepIndex = 0; int stepIndex = 0;
int maxSteps = Math.Max(toolcallBuffer.Count, toolResultBuffer.Count); int maxSteps = Math.Max(toolcallBuffer?.Count ?? 0, toolResultBuffer?.Count ?? 0);
for (int i = 0; i < maxSteps; i++) for (int i = 0; i < maxSteps; i++)
{ {
bool anyStepContent = false; bool anyStepContent = false;
stepIndex++; stepIndex++;
if (i < toolcallBuffer.Count && if (hasToolCalls && i < toolcallBuffer.Count &&
JsonToolCallParser.TryParseToolCallsFromText(toolcallBuffer[i], out var calls, out _)) JsonToolCallParser.TryParseToolCallsFromText(toolcallBuffer[i], out var calls, out _))
{ {
foreach (var call in calls) foreach (var call in calls)
@@ -601,7 +682,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
} }
} }
if (i < toolResultBuffer.Count) if (toolResultBuffer != null && i < toolResultBuffer.Count)
{ {
foreach (string resultLine in ExtractToolResultLines(toolResultBuffer[i], 4)) foreach (string resultLine in ExtractToolResultLines(toolResultBuffer[i], 4))
{ {
@@ -616,6 +697,16 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
} }
} }
if (traceNotes != null && traceNotes.Count > 0)
{
foreach (string note in traceNotes)
{
string trimmed = (note ?? "").Trim();
if (string.IsNullOrWhiteSpace(trimmed)) continue;
lines.Add($"模型 · {TrimForDisplay(trimmed, 220)}");
}
}
return lines; return lines;
} }
@@ -650,10 +741,67 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
return text.Substring(0, maxChars) + "..."; return text.Substring(0, maxChars) + "...";
} }
private string BuildReactTraceHeader() private List<string> BuildLiveTraceLines()
{ {
string state = _isThinking ? "思考中" : "已思考"; if (_core == null) return new List<string>();
float elapsed = _isThinking var history = _core.GetHistorySnapshot();
if (history == null || history.Count == 0) return new List<string>();
int lastUserIndex = -1;
for (int i = history.Count - 1; i >= 0; i--)
{
if (history[i].role == "user")
{
lastUserIndex = i;
break;
}
}
if (lastUserIndex == -1) return new List<string>();
var toolcallBuffer = new List<string>();
var toolResultBuffer = new List<string>();
var traceNoteBuffer = new List<string>();
for (int i = lastUserIndex + 1; i < history.Count; i++)
{
var entry = history[i];
if (entry.role == "toolcall")
{
toolcallBuffer.Add(entry.message ?? "");
}
else if (entry.role == "tool")
{
toolResultBuffer.Add(entry.message ?? "");
}
else if (entry.role == "trace")
{
traceNoteBuffer.Add(entry.message ?? "");
}
}
return BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
}
private string GetTraceHeader(int traceKey, bool isLive)
{
if (isLive)
{
return BuildReactTraceHeader(true);
}
if (_traceHeaderByAssistantIndex.TryGetValue(traceKey, out string header))
{
return header;
}
header = BuildReactTraceHeader(false);
_traceHeaderByAssistantIndex[traceKey] = header;
return header;
}
private string BuildReactTraceHeader(bool isLive)
{
string state = isLive ? "思考中" : "已思考";
float elapsed = isLive
? Mathf.Max(0f, Time.realtimeSinceStartup - (_core?.ThinkingStartTime ?? 0f)) ? Mathf.Max(0f, Time.realtimeSinceStartup - (_core?.ThinkingStartTime ?? 0f))
: _core?.LastThinkingDuration ?? 0f; : _core?.LastThinkingDuration ?? 0f;
string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", CultureInfo.InvariantCulture) : "0.0"; string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", CultureInfo.InvariantCulture) : "0.0";

View File

@@ -26,6 +26,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
private List<CachedMessage> _cachedMessages = new List<CachedMessage>(); private List<CachedMessage> _cachedMessages = new List<CachedMessage>();
private float _cachedTotalHeight = 0f; private float _cachedTotalHeight = 0f;
private readonly Dictionary<int, bool> _traceExpandedByAssistantIndex = new Dictionary<int, bool>(); private readonly Dictionary<int, bool> _traceExpandedByAssistantIndex = new Dictionary<int, bool>();
private readonly Dictionary<int, string> _traceHeaderByAssistantIndex = new Dictionary<int, string>();
private class CachedMessage private class CachedMessage
{ {
@@ -377,6 +378,11 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
} }
_lastUsedWidth = width; _lastUsedWidth = width;
if (_lastHistoryCount >= 0 && history.Count < _lastHistoryCount)
{
_traceExpandedByAssistantIndex.Clear();
_traceHeaderByAssistantIndex.Clear();
}
_lastHistoryCount = history.Count; _lastHistoryCount = history.Count;
_cachedMessages.Clear(); _cachedMessages.Clear();
_cachedTotalHeight = 0f; _cachedTotalHeight = 0f;
@@ -384,6 +390,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float curY = 10f; float curY = 10f;
var toolcallBuffer = new List<string>(); var toolcallBuffer = new List<string>();
var toolResultBuffer = new List<string>(); var toolResultBuffer = new List<string>();
var traceNoteBuffer = new List<string>();
bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true; bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true;
for (int i = 0; i < history.Count; i++) for (int i = 0; i < history.Count; i++)
@@ -394,6 +401,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
{ {
toolcallBuffer.Clear(); toolcallBuffer.Clear();
toolResultBuffer.Clear(); toolResultBuffer.Clear();
traceNoteBuffer.Clear();
} }
if (msg.role == "toolcall") if (msg.role == "toolcall")
@@ -414,6 +422,15 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
continue; continue;
} }
if (msg.role == "trace")
{
if (traceEnabled)
{
traceNoteBuffer.Add(msg.message ?? "");
}
continue;
}
if (msg.role == "system" && !Prefs.DevMode) continue; if (msg.role == "system" && !Prefs.DevMode) continue;
// Hide auto-commentary system messages (user-side) from display // Hide auto-commentary system messages (user-side) from display
@@ -431,12 +448,12 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (msg.role == "assistant" && traceEnabled && toolcallBuffer.Count > 0) if (msg.role == "assistant" && traceEnabled && toolcallBuffer.Count > 0)
{ {
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer); var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
if (traceLines.Count > 0) if (traceLines.Count > 0)
{ {
int traceKey = i; int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved; bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader(); string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny; Text.Font = GameFont.Tiny;
float padding = 8f; float padding = 8f;
@@ -474,13 +491,18 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
toolcallBuffer.Clear(); toolcallBuffer.Clear();
toolResultBuffer.Clear(); toolResultBuffer.Clear();
traceNoteBuffer.Clear();
} }
else if (msg.role == "assistant" && traceEnabled && toolcallBuffer.Count == 0) else if (msg.role == "assistant" && traceEnabled && toolcallBuffer.Count == 0)
{ {
var traceLines = new List<string> { "无工具调用,直接回复" }; var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
if (traceLines.Count == 0)
{
traceLines.Add("无工具调用,直接回复");
}
int traceKey = i; int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved; bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader(); string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny; Text.Font = GameFont.Tiny;
float tracePadding = 8f; float tracePadding = 8f;
@@ -514,6 +536,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
traceHeaderHeight = headerHeight traceHeaderHeight = headerHeight
}); });
curY += traceHeight + reducedSpacing; curY += traceHeight + reducedSpacing;
traceNoteBuffer.Clear();
} }
if (string.IsNullOrWhiteSpace(displayText)) continue; if (string.IsNullOrWhiteSpace(displayText)) continue;
@@ -540,10 +563,57 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float width = rect.width - 26f; // Scrollbar space float width = rect.width - 26f; // Scrollbar space
UpdateCacheIfNeeded(width); UpdateCacheIfNeeded(width);
bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true;
CachedMessage liveTraceEntry = null;
float liveTraceHeight = 0f;
if (_core != null && _core.IsThinking && traceEnabled)
{
var liveLines = BuildLiveTraceLines();
if (liveLines.Count == 0)
{
liveLines.Add("思考中…");
}
int traceKey = -1;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) ? saved : true;
string header = GetTraceHeader(traceKey, true);
Text.Font = GameFont.Tiny;
float tracePadding = 8f;
float headerWidth = Mathf.Max(10f, width - tracePadding * 2f);
string headerLine = $"{(expanded ? "v" : ">")} {header}";
float headerHeight = Text.CalcHeight(headerLine, headerWidth) + 10f;
float linesHeight = 0f;
if (expanded)
{
float lineWidth = Mathf.Max(10f, width - tracePadding * 2f);
foreach (string line in liveLines)
{
linesHeight += Text.CalcHeight(line, lineWidth) + 2f;
}
linesHeight += 8f;
}
float traceHeight = headerHeight + linesHeight;
liveTraceHeight = traceHeight + 8f;
liveTraceEntry = new CachedMessage
{
role = "trace",
message = "",
displayText = "",
height = traceHeight,
yOffset = 0f,
isTrace = true,
traceKey = traceKey,
traceHeader = header,
traceLines = liveLines,
traceExpanded = expanded,
traceHeaderHeight = headerHeight
};
}
float totalContentHeight = _cachedTotalHeight; float totalContentHeight = _cachedTotalHeight;
if (_core != null && _core.IsThinking) if (_core != null && _core.IsThinking)
{ {
totalContentHeight += 40f; totalContentHeight += liveTraceEntry != null ? liveTraceHeight : 40f;
} }
Rect viewRect = new Rect(0, 0, width, totalContentHeight); Rect viewRect = new Rect(0, 0, width, totalContentHeight);
@@ -593,12 +663,23 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (_core != null && _core.IsThinking) if (_core != null && _core.IsThinking)
{ {
float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 10f; float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 10f;
if (liveTraceEntry != null)
{
if (thinkingY + liveTraceEntry.height >= viewTop && thinkingY <= viewBottom)
{
Rect traceRect = new Rect(0, thinkingY, width, liveTraceEntry.height);
DrawReactTracePanel(traceRect, liveTraceEntry);
}
}
else
{
Rect thinkingRect = new Rect(0, thinkingY, width, 30f); Rect thinkingRect = new Rect(0, thinkingY, width, 30f);
if (thinkingY + 30f >= viewTop && thinkingY <= viewBottom) if (thinkingY + 30f >= viewTop && thinkingY <= viewBottom)
{ {
DrawThinkingIndicator(thinkingRect); DrawThinkingIndicator(thinkingRect);
} }
} }
}
Widgets.EndScrollView(); Widgets.EndScrollView();
} }
@@ -616,19 +697,19 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
return text.Remove(index, fragment.Length).Trim(); return text.Remove(index, fragment.Length).Trim();
} }
private List<string> BuildTraceLines(List<string> toolcallBuffer, List<string> toolResultBuffer) private List<string> BuildTraceLines(List<string> toolcallBuffer, List<string> toolResultBuffer, List<string> traceNotes)
{ {
var lines = new List<string>(); var lines = new List<string>();
if (toolcallBuffer == null || toolcallBuffer.Count == 0) return lines; bool hasToolCalls = toolcallBuffer != null && toolcallBuffer.Count > 0;
int stepIndex = 0; int stepIndex = 0;
int maxSteps = Math.Max(toolcallBuffer.Count, toolResultBuffer.Count); int maxSteps = Math.Max(toolcallBuffer?.Count ?? 0, toolResultBuffer?.Count ?? 0);
for (int i = 0; i < maxSteps; i++) for (int i = 0; i < maxSteps; i++)
{ {
bool anyStepContent = false; bool anyStepContent = false;
stepIndex++; stepIndex++;
if (i < toolcallBuffer.Count && if (hasToolCalls && i < toolcallBuffer.Count &&
JsonToolCallParser.TryParseToolCallsFromText(toolcallBuffer[i], out var calls, out _)) JsonToolCallParser.TryParseToolCallsFromText(toolcallBuffer[i], out var calls, out _))
{ {
foreach (var call in calls) foreach (var call in calls)
@@ -643,7 +724,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
} }
} }
if (i < toolResultBuffer.Count) if (toolResultBuffer != null && i < toolResultBuffer.Count)
{ {
foreach (string resultLine in ExtractToolResultLines(toolResultBuffer[i], 4)) foreach (string resultLine in ExtractToolResultLines(toolResultBuffer[i], 4))
{ {
@@ -658,6 +739,16 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
} }
} }
if (traceNotes != null && traceNotes.Count > 0)
{
foreach (string note in traceNotes)
{
string trimmed = (note ?? "").Trim();
if (string.IsNullOrWhiteSpace(trimmed)) continue;
lines.Add($"模型 · {TrimForDisplay(trimmed, 220)}");
}
}
return lines; return lines;
} }
@@ -692,11 +783,68 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
return text.Substring(0, maxChars) + "..."; return text.Substring(0, maxChars) + "...";
} }
private string BuildReactTraceHeader() private List<string> BuildLiveTraceLines()
{ {
string state = _core != null && _core.IsThinking ? "思考中" : "已思考"; if (_core == null) return new List<string>();
var history = _core.GetHistorySnapshot();
if (history == null || history.Count == 0) return new List<string>();
int lastUserIndex = -1;
for (int i = history.Count - 1; i >= 0; i--)
{
if (history[i].role == "user")
{
lastUserIndex = i;
break;
}
}
if (lastUserIndex == -1) return new List<string>();
var toolcallBuffer = new List<string>();
var toolResultBuffer = new List<string>();
var traceNoteBuffer = new List<string>();
for (int i = lastUserIndex + 1; i < history.Count; i++)
{
var entry = history[i];
if (entry.role == "toolcall")
{
toolcallBuffer.Add(entry.message ?? "");
}
else if (entry.role == "tool")
{
toolResultBuffer.Add(entry.message ?? "");
}
else if (entry.role == "trace")
{
traceNoteBuffer.Add(entry.message ?? "");
}
}
return BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
}
private string GetTraceHeader(int traceKey, bool isLive)
{
if (isLive)
{
return BuildReactTraceHeader(true);
}
if (_traceHeaderByAssistantIndex.TryGetValue(traceKey, out string header))
{
return header;
}
header = BuildReactTraceHeader(false);
_traceHeaderByAssistantIndex[traceKey] = header;
return header;
}
private string BuildReactTraceHeader(bool isLive)
{
string state = isLive ? "思考中" : "已思考";
float startTime = _core?.ThinkingStartTime ?? 0f; float startTime = _core?.ThinkingStartTime ?? 0f;
float elapsed = _core != null && _core.IsThinking float elapsed = isLive && _core != null
? Mathf.Max(0f, Time.realtimeSinceStartup - startTime) ? Mathf.Max(0f, Time.realtimeSinceStartup - startTime)
: _core?.LastThinkingDuration ?? 0f; : _core?.LastThinkingDuration ?? 0f;
string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture) : "0.0"; string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture) : "0.0";

View File

@@ -99,15 +99,15 @@ namespace WulaFallenEmpire
listingStandard.GapLine(); listingStandard.GapLine();
listingStandard.Label("<color=cyan>ReAct Loop Settings</color>"); listingStandard.Label("<color=cyan>ReAct Loop Settings</color>");
listingStandard.Label("Default Steps:"); listingStandard.Label("Default Steps (min 1):");
Rect stepsRect = listingStandard.GetRect(Text.LineHeight); Rect stepsRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(stepsRect, ref settings.reactMaxSteps, ref _reactMaxStepsBuffer, 1, int.MaxValue); Widgets.TextFieldNumeric(stepsRect, ref settings.reactMaxSteps, ref _reactMaxStepsBuffer, 1, int.MaxValue);
listingStandard.Label("Max Steps Limit (step_budget upper bound):"); listingStandard.Label("Max Steps Limit (step_budget upper bound, min 1):");
Rect stepsMaxRect = listingStandard.GetRect(Text.LineHeight); Rect stepsMaxRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(stepsMaxRect, ref settings.reactMaxStepsMax, ref _reactMaxStepsMaxBuffer, 1, int.MaxValue); Widgets.TextFieldNumeric(stepsMaxRect, ref settings.reactMaxStepsMax, ref _reactMaxStepsMaxBuffer, 1, int.MaxValue);
listingStandard.Label("Max Seconds (2-60):"); listingStandard.Label("Max Seconds (min 2):");
Rect secondsRect = listingStandard.GetRect(Text.LineHeight); Rect secondsRect = listingStandard.GetRect(Text.LineHeight);
Widgets.TextFieldNumeric(secondsRect, ref settings.reactMaxSeconds, ref _reactMaxSecondsBuffer, 10f, 600f); Widgets.TextFieldNumeric(secondsRect, ref settings.reactMaxSeconds, ref _reactMaxSecondsBuffer, 10f, 600f);