This commit is contained in:
2025-12-31 03:32:03 +08:00
parent c345655ca5
commit c98fd802c2
5 changed files with 488 additions and 279 deletions

View File

@@ -30,13 +30,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
private Dictionary<int, Texture2D> _portraits = new Dictionary<int, Texture2D>();
private int _currentPortraitId = 0;
private static readonly Regex ExpressionTagRegex = new Regex(@"\[EXPR\s*:\s*([1-6])\s*\]", RegexOptions.IgnoreCase);
private bool _reactTraceExpanded = false;
private bool _hasReactTrace = false;
private float _reactTraceHeight = 0f;
private float _reactTraceYOffset = 0f;
private float _reactTraceHeaderHeight = 0f;
private string _reactTraceHeader = "";
private List<string> _reactTraceLines = new List<string>();
private readonly Dictionary<int, bool> _traceExpandedByAssistantIndex = new Dictionary<int, bool>();
private class CachedMessage
{
@@ -46,13 +40,12 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
public float height;
public float yOffset;
public GameFont font;
}
private class ReactTraceStep
{
public int Step;
public List<string> Calls = new List<string>();
public List<string> Results = new List<string>();
public bool isTrace;
public int traceKey;
public string traceHeader;
public List<string> traceLines;
public bool traceExpanded;
public float traceHeaderHeight;
}
public static Dialog_AIConversation Instance { get; private set; }
@@ -338,15 +331,91 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float curY = 0f;
float innerPadding = 5f;
float contentWidth = width - innerPadding * 2;
var toolcallBuffer = new List<string>();
var toolResultBuffer = new List<string>();
bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true;
for (int i = 0; i < history.Count; i++)
{
var entry = history[i];
string messageText = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : AIIntelligenceCore.StripContextInfo(entry.message);
if (entry.role == "tool" || entry.role == "system" || entry.role == "toolcall") continue;
if (entry.role == "user")
{
toolcallBuffer.Clear();
toolResultBuffer.Clear();
}
if (entry.role == "toolcall")
{
if (traceEnabled)
{
toolcallBuffer.Add(entry.message ?? "");
}
continue;
}
if (entry.role == "tool")
{
if (traceEnabled)
{
toolResultBuffer.Add(entry.message ?? "");
}
continue;
}
string messageText = entry.role == "assistant"
? ParseResponseForDisplay(entry.message)
: AIIntelligenceCore.StripContextInfo(entry.message);
if (entry.role == "system") continue;
// Hide auto-commentary system messages (user-side) from display
if (entry.role == "user" && entry.message.Contains("[AUTO_COMMENTARY]")) continue;
if (entry.role == "assistant" && traceEnabled && toolcallBuffer.Count > 0)
{
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer);
if (traceLines.Count > 0)
{
int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader();
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 traceLines)
{
linesHeight += Text.CalcHeight(line, lineWidth) + 2f;
}
linesHeight += 8f;
}
float traceHeight = headerHeight + linesHeight;
_cachedMessages.Add(new CachedMessage
{
role = "trace",
message = "",
displayText = "",
height = traceHeight,
yOffset = curY,
font = GameFont.Tiny,
isTrace = true,
traceKey = traceKey,
traceHeader = header,
traceLines = traceLines,
traceExpanded = expanded,
traceHeaderHeight = headerHeight
});
curY += traceHeight + 10f;
}
toolcallBuffer.Clear();
toolResultBuffer.Clear();
}
if (string.IsNullOrEmpty(messageText) || (entry.role == "user" && messageText.StartsWith("[Tool Results]"))) continue;
bool isLastMessage = i == history.Count - 1;
@@ -368,8 +437,6 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
curY += height + 10f;
}
UpdateReactTraceCache(history, contentWidth, ref curY);
_cachedTotalHeight = curY;
}
@@ -407,7 +474,11 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
Text.Font = entry.font;
Rect labelRect = new Rect(innerPadding, entry.yOffset, contentWidth, entry.height);
if (entry.role == "user")
if (entry.isTrace)
{
DrawReactTracePanel(labelRect, entry);
}
else if (entry.role == "user")
{
Text.Anchor = TextAnchor.MiddleRight;
Widgets.Label(labelRect, $"<color=#add8e6>{entry.displayText}</color>");
@@ -426,15 +497,6 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
}
}
if (_hasReactTrace)
{
Rect traceRect = new Rect(innerPadding, _reactTraceYOffset, contentWidth, _reactTraceHeight);
if (traceRect.yMax >= viewTop && traceRect.y <= viewBottom)
{
DrawReactTracePanel(traceRect);
}
}
if (_isThinking)
{
float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 0f;
@@ -471,83 +533,20 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
return text.Split(new[] { "OPTIONS:" }, StringSplitOptions.None)[0].Trim();
}
private void UpdateReactTraceCache(List<(string role, string message)> history, float contentWidth, ref float curY)
private List<string> BuildTraceLines(List<string> toolcallBuffer, List<string> toolResultBuffer)
{
_hasReactTrace = false;
_reactTraceLines.Clear();
_reactTraceHeader = "";
_reactTraceHeight = 0f;
_reactTraceYOffset = 0f;
_reactTraceHeaderHeight = 0f;
if (WulaFallenEmpireMod.settings?.showReactTraceInUI != true)
{
return;
}
List<ReactTraceStep> steps = BuildReactTraceSteps(history);
if (steps.Count == 0)
{
return;
}
_hasReactTrace = true;
_reactTraceHeader = BuildReactTraceHeader();
foreach (var step in steps)
{
foreach (string call in step.Calls)
{
_reactTraceLines.Add($"Step {step.Step}: call {call}");
}
foreach (string result in step.Results)
{
_reactTraceLines.Add($"Step {step.Step}: result {result}");
}
}
var originalFont = Text.Font;
Text.Font = GameFont.Tiny;
float panelPadding = 6f;
float headerWidth = Mathf.Max(10f, contentWidth - panelPadding * 2f);
string headerLine = $"{(_reactTraceExpanded ? "v" : ">")} {_reactTraceHeader}";
_reactTraceHeaderHeight = Text.CalcHeight(headerLine, headerWidth) + 6f;
float linesHeight = 0f;
if (_reactTraceExpanded && _reactTraceLines.Count > 0)
{
float lineWidth = Mathf.Max(10f, contentWidth - panelPadding * 2f);
foreach (string line in _reactTraceLines)
{
linesHeight += Text.CalcHeight(line, lineWidth) + 2f;
}
linesHeight += 4f;
}
_reactTraceHeight = _reactTraceHeaderHeight + linesHeight;
Text.Font = originalFont;
curY += 6f;
_reactTraceYOffset = curY;
curY += _reactTraceHeight + 8f;
}
private List<ReactTraceStep> BuildReactTraceSteps(List<(string role, string message)> history)
{
var steps = new List<ReactTraceStep>();
if (history == null || history.Count == 0) return steps;
int lastUserIndex = history.FindLastIndex(entry => entry.role == "user");
if (lastUserIndex < 0) return steps;
var lines = new List<string>();
if (toolcallBuffer == null || toolcallBuffer.Count == 0) return lines;
int stepIndex = 0;
for (int i = lastUserIndex + 1; i < history.Count; i++)
int maxSteps = Math.Max(toolcallBuffer.Count, toolResultBuffer.Count);
for (int i = 0; i < maxSteps; i++)
{
var entry = history[i];
if (entry.role != "toolcall") continue;
bool anyStepContent = false;
stepIndex++;
var step = new ReactTraceStep { Step = stepIndex };
if (JsonToolCallParser.TryParseToolCallsFromText(entry.message, out var calls, out _))
if (i < toolcallBuffer.Count &&
JsonToolCallParser.TryParseToolCallsFromText(toolcallBuffer[i], out var calls, out _))
{
foreach (var call in calls)
{
@@ -556,23 +555,27 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
string callText = string.IsNullOrWhiteSpace(args) || args == "{}"
? call.Name
: $"{call.Name} {TrimForDisplay(args, 160)}";
step.Calls.Add(callText);
lines.Add($"步骤 {stepIndex} · 调用 {callText}");
anyStepContent = true;
}
}
if (i + 1 < history.Count && history[i + 1].role == "tool")
if (i < toolResultBuffer.Count)
{
step.Results.AddRange(ExtractToolResultLines(history[i + 1].message, 4));
i++;
foreach (string resultLine in ExtractToolResultLines(toolResultBuffer[i], 4))
{
lines.Add($"步骤 {stepIndex} · 结果 {resultLine}");
anyStepContent = true;
}
}
if (step.Calls.Count > 0 || step.Results.Count > 0)
if (!anyStepContent)
{
steps.Add(step);
stepIndex--;
}
}
return steps;
return lines;
}
private static List<string> ExtractToolResultLines(string toolMessage, int maxLines)
@@ -586,6 +589,13 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
string line = raw.Trim();
if (string.IsNullOrEmpty(line)) continue;
if (line.StartsWith("[Tool Results]", StringComparison.OrdinalIgnoreCase)) continue;
if (!line.StartsWith("Tool '", StringComparison.OrdinalIgnoreCase) &&
!line.StartsWith("ToolRunner", StringComparison.OrdinalIgnoreCase) &&
!line.StartsWith("Query Result:", StringComparison.OrdinalIgnoreCase) &&
!line.StartsWith("Error:", StringComparison.OrdinalIgnoreCase))
{
continue;
}
lines.Add(TrimForDisplay(line, 200));
if (lines.Count >= maxLines) break;
}
@@ -606,37 +616,43 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
? Mathf.Max(0f, Time.realtimeSinceStartup - (_core?.ThinkingStartTime ?? 0f))
: _core?.LastThinkingDuration ?? 0f;
string elapsedText = elapsed > 0f ? elapsed.ToString("0.0", CultureInfo.InvariantCulture) : "0.0";
return $"{state} ({elapsedText}s · Loop {_core?.ThinkingPhaseIndex ?? 0}/{_core?.ThinkingPhaseTotal ?? 0})";
return $"{state} (用时 {elapsedText}s · Loop {_core?.ThinkingPhaseIndex ?? 0}/{_core?.ThinkingPhaseTotal ?? 0})";
}
private void DrawReactTracePanel(Rect rect)
private void DrawReactTracePanel(Rect rect, CachedMessage traceEntry)
{
var originalColor = GUI.color;
var originalFont = Text.Font;
var originalAnchor = Text.Anchor;
float padding = 6f;
Rect headerRect = new Rect(rect.x, rect.y, rect.width, _reactTraceHeaderHeight);
float padding = 8f;
Rect headerRect = new Rect(rect.x, rect.y, rect.width, traceEntry.traceHeaderHeight);
GUI.color = new Color(0.15f, 0.15f, 0.15f, 0.8f);
Widgets.DrawBoxSolid(headerRect, GUI.color);
GUI.color = Color.white;
Text.Font = GameFont.Tiny;
Text.Anchor = TextAnchor.MiddleLeft;
string headerLine = $"{(_reactTraceExpanded ? "v" : ">")} {_reactTraceHeader}";
Widgets.Label(headerRect.ContractedBy(padding), headerLine);
string headerLine = $"{(traceEntry.traceExpanded ? "v" : ">")} {traceEntry.traceHeader}";
Widgets.Label(headerRect.ContractedBy(padding, 4f), headerLine);
if (Widgets.ButtonInvisible(headerRect))
{
_reactTraceExpanded = !_reactTraceExpanded;
traceEntry.traceExpanded = !traceEntry.traceExpanded;
_traceExpandedByAssistantIndex[traceEntry.traceKey] = traceEntry.traceExpanded;
_lastHistoryCount = -1;
_lastUsedWidth = -1f;
}
if (_reactTraceExpanded && _reactTraceLines.Count > 0)
if (traceEntry.traceExpanded && traceEntry.traceLines != null && traceEntry.traceLines.Count > 0)
{
float y = headerRect.yMax + 2f;
foreach (string line in _reactTraceLines)
Rect bodyRect = new Rect(rect.x, headerRect.yMax, rect.width, rect.height - traceEntry.traceHeaderHeight);
GUI.color = new Color(0.12f, 0.12f, 0.12f, 0.45f);
Widgets.DrawBoxSolid(bodyRect, GUI.color);
GUI.color = Color.white;
float y = headerRect.yMax + 6f;
foreach (string line in traceEntry.traceLines)
{
float lineHeight = Text.CalcHeight(line, rect.width - padding * 2f) + 2f;
Rect lineRect = new Rect(rect.x + padding, y, rect.width - padding * 2f, lineHeight);