wip
This commit is contained in:
+15
-183
@@ -16,6 +16,7 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using System.Xml.Linq;
|
||||
using static AnotherReplayReader.AIProviderSettingsControl;
|
||||
|
||||
namespace AnotherReplayReader
|
||||
{
|
||||
@@ -76,8 +77,7 @@ namespace AnotherReplayReader
|
||||
private readonly CancellationTokenSource _cancellation = new();
|
||||
private Model _model = new();
|
||||
private string? _cached;
|
||||
private AIAnalyze.TimeIndexedPrefixSums? _cachedPrefixSums;
|
||||
private DateTimeOffset _aiStartTime;
|
||||
private TimeIndexedPrefixSums? _cachedPrefixSums;
|
||||
|
||||
public EventDump()
|
||||
{
|
||||
@@ -180,7 +180,7 @@ namespace AnotherReplayReader
|
||||
_tokenUsageLabel.Content = $"大小: {bytesCount / 1024.0:0.00} KiB,估计Token数: {estimatedTokenCount / 1000.0:0.00} K";
|
||||
}
|
||||
|
||||
private static string GeneratePlainText(Model model, out AIAnalyze.TimeIndexedPrefixSums prefixSums)
|
||||
private static string GeneratePlainText(Model model, out TimeIndexedPrefixSums prefixSums)
|
||||
{
|
||||
prefixSums = new([], []);
|
||||
var sb = new StringBuilder();
|
||||
@@ -431,194 +431,26 @@ namespace AnotherReplayReader
|
||||
|
||||
private async void OnAIAnalyzeClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var cached = _cached;
|
||||
if (cached is null)
|
||||
if (_model.Replay is not { } replay)
|
||||
{
|
||||
MessageBox.Show(this, "请先选择录像");
|
||||
return;
|
||||
}
|
||||
if (_cached is not { } cached || _cachedPrefixSums is not { } cachedPrefixSums)
|
||||
{
|
||||
MessageBox.Show(this, "请先生成文本");
|
||||
return;
|
||||
}
|
||||
|
||||
_aiStartTime = DateTimeOffset.MaxValue;
|
||||
_elapsedTimeTextBlock.Text = "";
|
||||
_contentCountTextBlock.Text = "";
|
||||
_rateTextBlock.Text = "";
|
||||
_tokensDetailsTextBlock.Text = "";
|
||||
_extraStatusTextBlock.Text = "";
|
||||
|
||||
_aiTextBox.Clear();
|
||||
_aiReasoningTextBox.Clear();
|
||||
|
||||
var pending = new ConcurrentQueue<AIAnalyzeUI.AIAnalyzeProgressData>();
|
||||
var emaSpeedCalculator = new AIAnalyzeUI.EmaSpeed();
|
||||
var totalCharacters = 0;
|
||||
|
||||
void TimerUpdateStatus(object sender, EventArgs ea)
|
||||
var context = _aiSettings.GetCurrentContext();
|
||||
if (context == null)
|
||||
{
|
||||
if (_aiStartTime == DateTimeOffset.MaxValue)
|
||||
{
|
||||
_elapsedTimeTextBlock.Text = "";
|
||||
return;
|
||||
}
|
||||
var buffer = new Dictionary<AIAnalyze.AIChunkType, (TextBox Target, StringBuilder Text)>
|
||||
{
|
||||
[AIAnalyze.AIChunkType.Content] = (_aiTextBox, new StringBuilder()),
|
||||
[AIAnalyze.AIChunkType.Reasoning] = (_aiReasoningTextBox, new StringBuilder()),
|
||||
};
|
||||
while (pending.TryDequeue(out var result))
|
||||
{
|
||||
var (delta, timestamp, isExtra) = result;
|
||||
buffer[delta.Type].Text.Append(delta.Text);
|
||||
if (!isExtra)
|
||||
{
|
||||
totalCharacters += delta.Text.Length;
|
||||
}
|
||||
emaSpeedCalculator.ProcessEvent(result);
|
||||
}
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var elapsed = now - _aiStartTime;
|
||||
_elapsedTimeTextBlock.Text = $"{elapsed:mm\\:ss}";
|
||||
_contentCountTextBlock.Text = $"内容字数: {totalCharacters}";
|
||||
|
||||
var display = emaSpeedCalculator.GetDisplaySpeed(now);
|
||||
_rateTextBlock.Text = $"{display:0.00} 字/秒;平均{totalCharacters / elapsed.TotalSeconds:0.00} 字/秒";
|
||||
|
||||
foreach (var kv in buffer)
|
||||
{
|
||||
var (target, text) = kv.Value;
|
||||
if (text.Length > 0)
|
||||
{
|
||||
target.AppendText(text.ToString());
|
||||
text.Clear();
|
||||
}
|
||||
}
|
||||
MessageBox.Show("请先在“AI 设置”页配置提供商并选择模型");
|
||||
return;
|
||||
}
|
||||
_aiPanel.GetRequestContext = _aiSettings.GetCurrentContext!;
|
||||
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(0.1),
|
||||
};
|
||||
timer.Tick += TimerUpdateStatus;
|
||||
timer.Start();
|
||||
try
|
||||
{
|
||||
await LaunchAIAnalyze(cached, _cachedPrefixSums, pending.Enqueue);
|
||||
TimerUpdateStatus(this, EventArgs.Empty);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
MessageBox.Show(this, "AI分析已取消");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, $"AI分析失败: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_aiStartTime = DateTimeOffset.MaxValue;
|
||||
timer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
Type = AIAnalyze.AIChunkType.Reasoning,
|
||||
Text = message,
|
||||
};
|
||||
newContent(new(Delta: delta, TimeStamp: null, IsExtra: true));
|
||||
delta.Type = AIAnalyze.AIChunkType.Content;
|
||||
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://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,
|
||||
["chat_template_kwargs"] = new
|
||||
{
|
||||
thinking = true,
|
||||
reasoning_effort = "high"
|
||||
},
|
||||
["reasoning_effort"] = "high",
|
||||
["thinking"] = new { type = "enabled" }
|
||||
};
|
||||
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);
|
||||
var totalPromptSize = systemPromptSize + userPromptSize;
|
||||
var totalPromptTokenCount = systemPromptTokenCount + userPromptTokenCount;
|
||||
var estimateText = $"系统提示大小: {systemPromptSize / 1024.0:0.00}KiB,估计Token数: {systemPromptTokenCount / 1000.0:0.00}K\r\n" +
|
||||
$"用户提示大小: {userPromptSize / 1024.0:0.00} KiB,估计Token数: {userPromptTokenCount / 1000.0:0.00} K\r\n" +
|
||||
$"总大小: {totalPromptSize / 1024.0:0.00} KiB,估计总Token数: {totalPromptTokenCount / 1000.0:0.00} K\r\n";
|
||||
MessageBox.Show(this, estimateText, "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
#endregion
|
||||
_extraStatusTextBlock.Text = "AI正在了解录像……";
|
||||
_aiStartTime = DateTimeOffset.UtcNow;
|
||||
var firstReader = AIAnalyzeUI.BuildAIChunkReader(newContent);
|
||||
var result = await Task.Run(() => analyzer.AnalyzeAsync(
|
||||
systemPrompt,
|
||||
userPrompt,
|
||||
deepSeekExtraParams,
|
||||
firstReader,
|
||||
_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");
|
||||
await _aiPanel.StartAnalysisAsync(replay, _model.Players, cached, cachedPrefixSums, _cancellation.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user