wip
This commit is contained in:
+77
-28
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -15,7 +14,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Threading;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -33,27 +31,24 @@ namespace AnotherReplayReader
|
||||
VeryCompactedForAI,
|
||||
}
|
||||
|
||||
private class Model(
|
||||
Mod mod,
|
||||
ImmutableSortedDictionary<int, Player> players,
|
||||
ImmutableArray<(TimeSpan, ImmutableArray<CommandChunk>)> commands,
|
||||
CompactLevel level
|
||||
private record Model(
|
||||
Replay? Replay,
|
||||
ImmutableSortedDictionary<int, Player> Players,
|
||||
ImmutableArray<(TimeSpan, ImmutableArray<CommandChunk>)> Commands,
|
||||
CompactLevel Level
|
||||
)
|
||||
{
|
||||
public Mod Mod { get; } = mod;
|
||||
public ImmutableSortedDictionary<int, Player> Players { get; } = players;
|
||||
public ImmutableArray<(TimeSpan, ImmutableArray<CommandChunk>)> Commands { get; } = commands;
|
||||
public CompactLevel Level { get; } = level;
|
||||
public ImmutableSortedDictionary<int, string>? PlayersNamesForAI { get; } = level <= CompactLevel.NoCompact
|
||||
public Mod Mod => Replay?.Mod ?? new("RA3");
|
||||
public ImmutableSortedDictionary<int, string>? PlayersNamesForAI { get; } = Replay is null || Level <= CompactLevel.NoCompact
|
||||
? null
|
||||
: AIAnalyze.PlayerNamesForAI(mod, players);
|
||||
|
||||
: AIAnalyze.PlayerNamesForAI(Replay.Mod, Players);
|
||||
public bool IsDefault => Players.IsEmpty && Commands.IsEmpty;
|
||||
|
||||
public Model() : this(
|
||||
new("RA3"),
|
||||
null,
|
||||
ImmutableSortedDictionary<int, Player>.Empty,
|
||||
ImmutableArray<(TimeSpan, ImmutableArray<CommandChunk>)>.Empty, CompactLevel.NoCompact
|
||||
ImmutableArray<(TimeSpan, ImmutableArray<CommandChunk>)>.Empty,
|
||||
CompactLevel.NoCompact
|
||||
)
|
||||
{
|
||||
}
|
||||
@@ -81,6 +76,7 @@ namespace AnotherReplayReader
|
||||
private readonly CancellationTokenSource _cancellation = new();
|
||||
private Model _model = new();
|
||||
private string? _cached;
|
||||
private AIAnalyze.TimeIndexedPrefixSums? _cachedPrefixSums;
|
||||
private DateTimeOffset _aiStartTime;
|
||||
|
||||
public EventDump()
|
||||
@@ -140,10 +136,10 @@ namespace AnotherReplayReader
|
||||
x => x.Attribute("Text")!.Value);
|
||||
}
|
||||
|
||||
internal void SetDumpData(Mod mod, ApmPlotter plotter)
|
||||
internal void SetDumpData(ApmPlotter plotter)
|
||||
{
|
||||
var level = (CompactLevel)_compactLevelComboBox.SelectedIndex;
|
||||
_model = new Model(mod, plotter.PlayersMap, plotter.Commands, level);
|
||||
_model = new Model(plotter.Replay, plotter.PlayersMap, plotter.Commands, level);
|
||||
}
|
||||
|
||||
public async Task ShowPlainText()
|
||||
@@ -171,16 +167,22 @@ namespace AnotherReplayReader
|
||||
_textBox.Text = "正在加载,请稍候";
|
||||
_tokenUsageLabel.Content = "";
|
||||
Show();
|
||||
var text = await Task.Run(() => GeneratePlainText(_model));
|
||||
var (text, prefixSums) = await Task.Run(() =>
|
||||
{
|
||||
var text = GeneratePlainText(_model, out var prefixSums);
|
||||
return (text, prefixSums);
|
||||
});
|
||||
var (bytesCount, estimatedTokenCount) = AIAnalyze.EstimateTokenCount(text);
|
||||
_textBox.Text = text;
|
||||
_cached = text;
|
||||
_cachedPrefixSums = prefixSums;
|
||||
// display KB and K tokens in _tokenUsageLabel
|
||||
_tokenUsageLabel.Content = $"大小: {bytesCount / 1024.0:0.00} KiB,估计Token数: {estimatedTokenCount / 1000.0:0.00} K";
|
||||
}
|
||||
|
||||
private static string GeneratePlainText(Model model)
|
||||
private static string GeneratePlainText(Model model, out AIAnalyze.TimeIndexedPrefixSums prefixSums)
|
||||
{
|
||||
prefixSums = new([], []);
|
||||
var sb = new StringBuilder();
|
||||
for (int chunkIndex = 0; chunkIndex < model.Commands.Length; ++chunkIndex)
|
||||
{
|
||||
@@ -192,6 +194,7 @@ namespace AnotherReplayReader
|
||||
{
|
||||
continue;
|
||||
}
|
||||
prefixSums.Add(time, filtered.Count);
|
||||
sb.AppendLine($"[{TimeStampToString(time, model.Level)}]");
|
||||
foreach (var command in filtered)
|
||||
{
|
||||
@@ -227,7 +230,7 @@ namespace AnotherReplayReader
|
||||
0x205 or 0x206 when i == 3 => $"序列:{ProductionQueueTypeToString((int)value)}",
|
||||
0x207 when i == 1 && j == 1 => $"序列:{ProductionQueueTypeToString((int)value)}",
|
||||
0x207 or 0x208 or 0x209 when i == 0 => $"{text}(建造者)",
|
||||
0x517 or 0x518 when i == 0 => $"{text}(出兵建筑)",
|
||||
0x205 or 0x206 when i == 0 => $"{text}(出兵建筑)",
|
||||
0x252 => $"{model.PlayerNameByGameSlotIndex((int)command.Data[0].Value)}已主动退出游戏",
|
||||
0x22E when i == 0 => j == 0 ? StanceToString((int)value) : null,
|
||||
_ => text,
|
||||
@@ -246,6 +249,10 @@ namespace AnotherReplayReader
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (level == CompactLevel.NoCompact)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (ApmPlotter.IsUnknown(commandId))
|
||||
{
|
||||
return false;
|
||||
@@ -418,7 +425,7 @@ namespace AnotherReplayReader
|
||||
private async void OnCompactLevelComboBoxSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||
{
|
||||
var selectedLevel = (CompactLevel)_compactLevelComboBox.SelectedIndex;
|
||||
_model = new Model(_model.Mod, _model.Players, _model.Commands, selectedLevel);
|
||||
_model = new Model(_model.Replay, _model.Players, _model.Commands, selectedLevel);
|
||||
await ShowPlainText();
|
||||
}
|
||||
|
||||
@@ -438,6 +445,9 @@ namespace AnotherReplayReader
|
||||
_tokensDetailsTextBlock.Text = "";
|
||||
_extraStatusTextBlock.Text = "";
|
||||
|
||||
_aiTextBox.Clear();
|
||||
_aiReasoningTextBox.Clear();
|
||||
|
||||
var pending = new ConcurrentQueue<AIAnalyzeUI.AIAnalyzeProgressData>();
|
||||
var emaSpeedCalculator = new AIAnalyzeUI.EmaSpeed();
|
||||
var totalCharacters = 0;
|
||||
@@ -491,9 +501,13 @@ namespace AnotherReplayReader
|
||||
timer.Start();
|
||||
try
|
||||
{
|
||||
await LaunchAIAnalyze(cached, pending.Enqueue);
|
||||
await LaunchAIAnalyze(cached, _cachedPrefixSums, pending.Enqueue);
|
||||
TimerUpdateStatus(this, EventArgs.Empty);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
MessageBox.Show(this, "AI分析已取消");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, $"AI分析失败: {ex}");
|
||||
@@ -505,8 +519,13 @@ namespace AnotherReplayReader
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LaunchAIAnalyze(string replayData, Action<AIAnalyzeUI.AIAnalyzeProgressData> newContent)
|
||||
private async Task LaunchAIAnalyze(
|
||||
string replayData,
|
||||
AIAnalyze.TimeIndexedPrefixSums eventCounts,
|
||||
Action<AIAnalyzeUI.AIAnalyzeProgressData> newContent
|
||||
)
|
||||
{
|
||||
AIAnalyzeUI.Debug.Clear();
|
||||
void AddExtraContent(string message)
|
||||
{
|
||||
var delta = new AIAnalyze.AIChunk
|
||||
@@ -519,10 +538,15 @@ namespace AnotherReplayReader
|
||||
newContent(new(Delta: delta, TimeStamp: null, IsExtra: true));
|
||||
}
|
||||
|
||||
// var analyzer = new AIAnalyze("https://integrate.api.nvidia.com/v1/", "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");
|
||||
var analyzer = new AIAnalyze("https://integrate.api.nvidia.com/v1/", "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");
|
||||
// var analyzer = new AIAnalyze("https://api.deepseek.com", "sk-a6ffa8e74bfc419bbb4722b5d4c79907");
|
||||
var deepSeekExtraParams = new Dictionary<string, object>
|
||||
{
|
||||
["model"] = "deepseek-ai/deepseek-v4-flash", // nvidia
|
||||
// ["model"] = "nvidia/nemotron-3-super-120b-a12b", "nvidia/nemotron-3-nano-30b-a3b" // slow
|
||||
// ["model"] = "nvidia/nemotron-3-nano-30b-a3b", // not smark
|
||||
// ["model"] = "deepseek-v4-flash", // deepseek official
|
||||
|
||||
["temperature"] = 0.75,
|
||||
["top_p"] = 0.95,
|
||||
["max_tokens"] = 16384,
|
||||
@@ -534,8 +558,9 @@ namespace AnotherReplayReader
|
||||
["reasoning_effort"] = "high",
|
||||
["thinking"] = new { type = "enabled" }
|
||||
};
|
||||
var systemPrompt = AIAnalyze.GetSystemPrompt(_model.Mod, _model.Players);
|
||||
var userPrompt = AIAnalyze.BuildUserPrompt(_model.Mod, _model.Players, replayData);
|
||||
var systemPrompt = AIAnalyze.GetSystemPrompt(_model.Replay ?? throw new InvalidOperationException(), _model.Players);
|
||||
var userPrompt = AIAnalyze.BuildUserPrompt(_model.Mod, _model.Players, replayData, out var userPromptPrefix);
|
||||
AddExtraContent($"--- // 输入\r\n{userPromptPrefix}[输入:操作信息流水账]\r\n---\r\n");
|
||||
#region info
|
||||
var (systemPromptSize, systemPromptTokenCount) = AIAnalyze.EstimateTokenCount(systemPrompt);
|
||||
var (userPromptSize, userPromptTokenCount) = AIAnalyze.EstimateTokenCount(userPrompt);
|
||||
@@ -549,7 +574,7 @@ namespace AnotherReplayReader
|
||||
_extraStatusTextBlock.Text = "AI正在了解录像……";
|
||||
_aiStartTime = DateTimeOffset.UtcNow;
|
||||
var firstReader = AIAnalyzeUI.BuildAIChunkReader(newContent);
|
||||
var result = await Task.Run(() => analyzer.AnalyzeAsync("deepseek-ai/deepseek-v4-flash",
|
||||
var result = await Task.Run(() => analyzer.AnalyzeAsync(
|
||||
systemPrompt,
|
||||
userPrompt,
|
||||
deepSeekExtraParams,
|
||||
@@ -557,19 +582,43 @@ namespace AnotherReplayReader
|
||||
_cancellation.Token
|
||||
));
|
||||
_tokensDetailsTextBlock.Text = $"Token: {result.TotalTokens}(输入{result.PromptTokens})";
|
||||
|
||||
AddExtraContent("\r\n--- // 分段大小\r\n");
|
||||
foreach (var (Start, End, Description) in result.Segments)
|
||||
{
|
||||
var count = eventCounts.Query(Start, End);
|
||||
AddExtraContent($"[{Start:mm\\:ss\\.ff} - {End:mm\\:ss\\.ff}],事件数: {count}\r\n");
|
||||
}
|
||||
|
||||
AddExtraContent("\r\n--- // 开始分段分析\r\n");
|
||||
while (result.CurrentSegment < result.Segments.Count)
|
||||
{
|
||||
var (Start, End, Description) = result.Segments[result.CurrentSegment];
|
||||
var currentSegmentName = $"{result.CurrentSegment + 1}";
|
||||
_extraStatusTextBlock.Text = $"AI正在分析录像{currentSegmentName}/{result.Segments.Count}";
|
||||
var segmentReader = AIAnalyzeUI.BuildAIChunkReader(newContent);
|
||||
var eventCount = eventCounts.Query(Start, End);
|
||||
var segmentUserPrompt = AIAnalyze.BuildSegmentUserPrompt(result.Segments, result.CurrentSegment, eventCount);
|
||||
AddExtraContent($"--- // 输入\r\n{segmentUserPrompt}\r\n---\r\n");
|
||||
result = await Task.Run(() => analyzer.ContinueAnalyzeAsync(
|
||||
segmentReader,
|
||||
segmentUserPrompt,
|
||||
_cancellation.Token
|
||||
));
|
||||
_tokensDetailsTextBlock.Text = $"Token: {result.TotalTokens}(输入{result.PromptTokens})";
|
||||
AddExtraContent($"\r\n--- // 第{currentSegmentName}段已分析完毕\r\n");
|
||||
}
|
||||
var totalEvents = eventCounts.GetTotal();
|
||||
var finalUserPrompt = AIAnalyze.BuildFinalUserPrompt(totalEvents);
|
||||
AddExtraContent($"--- // 输入\r\n{finalUserPrompt}\r\n---\r\n");
|
||||
_extraStatusTextBlock.Text = $"AI正在分析录像总结";
|
||||
result = await Task.Run(() => analyzer.FinishAnalyzeAsync(
|
||||
AIAnalyzeUI.BuildAIChunkReader(newContent),
|
||||
finalUserPrompt,
|
||||
_cancellation.Token
|
||||
));
|
||||
_tokensDetailsTextBlock.Text = $"Token: {result.TotalTokens}(输入{result.PromptTokens})";
|
||||
AddExtraContent($"\r\n--- // 录像分析完毕\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user