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;
}
private void AddTraceNote(string note)
{
if (string.IsNullOrWhiteSpace(note)) return;
_history.Add(("trace", note.Trim()));
PersistHistory();
}
private bool IsToolAvailable(string toolName)
{
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;
}
if (string.Equals(entry.role, "trace", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (string.Equals(entry.role, "tool", StringComparison.OrdinalIgnoreCase))
{
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))
{
toolPhaseReplyCandidate = normalizedResponse;
AddTraceNote(normalizedResponse);
break;
}
@@ -1902,6 +1914,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
if (LooksLikeNaturalReply(normalizedFixed))
{
toolPhaseReplyCandidate = normalizedFixed;
AddTraceNote(normalizedFixed);
}
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
.Where(c => !IsToolAvailable(c.Name))
.Select(c => c.Name)

View File

@@ -31,6 +31,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
private int _currentPortraitId = 0;
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, string> _traceHeaderByAssistantIndex = new Dictionary<int, string>();
private class CachedMessage
{
@@ -325,6 +326,11 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (Math.Abs(_lastUsedWidth - width) < 0.1f && history.Count == _lastHistoryCount) return;
_lastUsedWidth = width;
if (_lastHistoryCount >= 0 && history.Count < _lastHistoryCount)
{
_traceExpandedByAssistantIndex.Clear();
_traceHeaderByAssistantIndex.Clear();
}
_lastHistoryCount = history.Count;
_cachedMessages.Clear();
_cachedTotalHeight = 0f;
@@ -333,6 +339,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float contentWidth = width - innerPadding * 2;
var toolcallBuffer = new List<string>();
var toolResultBuffer = new List<string>();
var traceNoteBuffer = new List<string>();
bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true;
for (int i = 0; i < history.Count; i++)
@@ -342,6 +349,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
{
toolcallBuffer.Clear();
toolResultBuffer.Clear();
traceNoteBuffer.Clear();
}
if (entry.role == "toolcall")
@@ -362,6 +370,15 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
continue;
}
if (entry.role == "trace")
{
if (traceEnabled)
{
traceNoteBuffer.Add(entry.message ?? "");
}
continue;
}
string messageText = entry.role == "assistant"
? ParseResponseForDisplay(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 == "assistant" && traceEnabled && toolcallBuffer.Count > 0)
{
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer);
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
if (traceLines.Count > 0)
{
int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader();
string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny;
float tracePadding = 8f;
@@ -415,13 +432,18 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
toolcallBuffer.Clear();
toolResultBuffer.Clear();
traceNoteBuffer.Clear();
}
else if (entry.role == "assistant" && traceEnabled && toolcallBuffer.Count == 0)
{
var traceLines = new List<string> { "无工具调用,直接回复" };
int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader();
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
if (traceLines.Count == 0)
{
traceLines.Add("无工具调用,直接回复");
}
int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny;
float tracePadding = 8f;
@@ -456,6 +478,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
traceHeaderHeight = headerHeight
});
curY += traceHeight + 10f;
traceNoteBuffer.Clear();
}
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 contentWidth = rect.width - 16f - innerPadding * 2;
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;
if (_isThinking) totalHeight += 40f;
if (_isThinking)
{
totalHeight += liveTraceEntry != null ? liveTraceHeight : 40f;
}
Rect viewRect = new Rect(0f, 0f, rect.width - 16f, totalHeight);
if (_scrollToBottom)
@@ -541,7 +614,15 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (_isThinking)
{
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));
}
@@ -574,19 +655,19 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
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>();
if (toolcallBuffer == null || toolcallBuffer.Count == 0) return lines;
bool hasToolCalls = toolcallBuffer != null && toolcallBuffer.Count > 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++)
{
bool anyStepContent = false;
stepIndex++;
if (i < toolcallBuffer.Count &&
if (hasToolCalls && i < toolcallBuffer.Count &&
JsonToolCallParser.TryParseToolCallsFromText(toolcallBuffer[i], out var calls, out _))
{
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))
{
@@ -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;
}
@@ -650,10 +741,67 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
return text.Substring(0, maxChars) + "...";
}
private string BuildReactTraceHeader()
private List<string> BuildLiveTraceLines()
{
string state = _isThinking ? "思考中" : "已思考";
float elapsed = _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 elapsed = isLive
? Mathf.Max(0f, Time.realtimeSinceStartup - (_core?.ThinkingStartTime ?? 0f))
: _core?.LastThinkingDuration ?? 0f;
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 float _cachedTotalHeight = 0f;
private readonly Dictionary<int, bool> _traceExpandedByAssistantIndex = new Dictionary<int, bool>();
private readonly Dictionary<int, string> _traceHeaderByAssistantIndex = new Dictionary<int, string>();
private class CachedMessage
{
@@ -377,6 +378,11 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
}
_lastUsedWidth = width;
if (_lastHistoryCount >= 0 && history.Count < _lastHistoryCount)
{
_traceExpandedByAssistantIndex.Clear();
_traceHeaderByAssistantIndex.Clear();
}
_lastHistoryCount = history.Count;
_cachedMessages.Clear();
_cachedTotalHeight = 0f;
@@ -384,6 +390,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float curY = 10f;
var toolcallBuffer = new List<string>();
var toolResultBuffer = new List<string>();
var traceNoteBuffer = new List<string>();
bool traceEnabled = WulaFallenEmpireMod.settings?.showReactTraceInUI == true;
for (int i = 0; i < history.Count; i++)
@@ -394,6 +401,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
{
toolcallBuffer.Clear();
toolResultBuffer.Clear();
traceNoteBuffer.Clear();
}
if (msg.role == "toolcall")
@@ -414,6 +422,15 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
continue;
}
if (msg.role == "trace")
{
if (traceEnabled)
{
traceNoteBuffer.Add(msg.message ?? "");
}
continue;
}
if (msg.role == "system" && !Prefs.DevMode) continue;
// 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)
{
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer);
var traceLines = BuildTraceLines(toolcallBuffer, toolResultBuffer, traceNoteBuffer);
if (traceLines.Count > 0)
{
int traceKey = i;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader();
string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny;
float padding = 8f;
@@ -474,13 +491,18 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
toolcallBuffer.Clear();
toolResultBuffer.Clear();
traceNoteBuffer.Clear();
}
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;
bool expanded = _traceExpandedByAssistantIndex.TryGetValue(traceKey, out bool saved) && saved;
string header = BuildReactTraceHeader();
string header = GetTraceHeader(traceKey, false);
Text.Font = GameFont.Tiny;
float tracePadding = 8f;
@@ -514,6 +536,7 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
traceHeaderHeight = headerHeight
});
curY += traceHeight + reducedSpacing;
traceNoteBuffer.Clear();
}
if (string.IsNullOrWhiteSpace(displayText)) continue;
@@ -540,10 +563,57 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
float width = rect.width - 26f; // Scrollbar space
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;
if (_core != null && _core.IsThinking)
{
totalContentHeight += 40f;
totalContentHeight += liveTraceEntry != null ? liveTraceHeight : 40f;
}
Rect viewRect = new Rect(0, 0, width, totalContentHeight);
@@ -593,10 +663,21 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
if (_core != null && _core.IsThinking)
{
float thinkingY = _cachedTotalHeight > 0 ? _cachedTotalHeight : 10f;
Rect thinkingRect = new Rect(0, thinkingY, width, 30f);
if (thinkingY + 30f >= viewTop && thinkingY <= viewBottom)
if (liveTraceEntry != null)
{
DrawThinkingIndicator(thinkingRect);
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);
if (thinkingY + 30f >= viewTop && thinkingY <= viewBottom)
{
DrawThinkingIndicator(thinkingRect);
}
}
}
@@ -616,19 +697,19 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
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>();
if (toolcallBuffer == null || toolcallBuffer.Count == 0) return lines;
bool hasToolCalls = toolcallBuffer != null && toolcallBuffer.Count > 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++)
{
bool anyStepContent = false;
stepIndex++;
if (i < toolcallBuffer.Count &&
if (hasToolCalls && i < toolcallBuffer.Count &&
JsonToolCallParser.TryParseToolCallsFromText(toolcallBuffer[i], out var calls, out _))
{
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))
{
@@ -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;
}
@@ -692,11 +783,68 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
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 elapsed = _core != null && _core.IsThinking
float elapsed = isLive && _core != null
? Mathf.Max(0f, Time.realtimeSinceStartup - startTime)
: _core?.LastThinkingDuration ?? 0f;
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.Label("<color=cyan>ReAct Loop Settings</color>");
listingStandard.Label("Default Steps:");
listingStandard.Label("Default Steps (min 1):");
Rect stepsRect = listingStandard.GetRect(Text.LineHeight);
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);
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);
Widgets.TextFieldNumeric(secondsRect, ref settings.reactMaxSeconds, ref _reactMaxSecondsBuffer, 10f, 600f);