wip
This commit is contained in:
575
EventDump.xaml.cs
Normal file
575
EventDump.xaml.cs
Normal file
@@ -0,0 +1,575 @@
|
||||
using AnotherReplayReader.Apm;
|
||||
using AnotherReplayReader.ReplayFile;
|
||||
using AnotherReplayReader.Utils;
|
||||
using Microsoft.Win32;
|
||||
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;
|
||||
using System.Text.RegularExpressions;
|
||||
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;
|
||||
|
||||
namespace AnotherReplayReader
|
||||
{
|
||||
/// <summary>
|
||||
/// EventDump.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public sealed partial class EventDump : Window
|
||||
{
|
||||
public enum CompactLevel
|
||||
{
|
||||
NoCompact,
|
||||
ForAI,
|
||||
VeryCompactedForAI,
|
||||
}
|
||||
|
||||
private class Model(
|
||||
Mod mod,
|
||||
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
|
||||
? null
|
||||
: AIAnalyze.PlayerNamesForAI(mod, players);
|
||||
|
||||
public bool IsDefault => Players.IsEmpty && Commands.IsEmpty;
|
||||
|
||||
public Model() : this(
|
||||
new("RA3"),
|
||||
ImmutableSortedDictionary<int, Player>.Empty,
|
||||
ImmutableArray<(TimeSpan, ImmutableArray<CommandChunk>)>.Empty, CompactLevel.NoCompact
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public string PlayerNameByPlayerListIndex(int playerId)
|
||||
{
|
||||
var playerRawName = $"玩家#{playerId}";
|
||||
var playerName = Players.TryGetValue(playerId, out var player)
|
||||
? player.PlayerName
|
||||
: playerRawName;
|
||||
if (PlayersNamesForAI?.TryGetValue(playerId, out var playerNameForAI) is true)
|
||||
{
|
||||
playerName = playerNameForAI;
|
||||
}
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public string PlayerNameByGameSlotIndex(int index) => PlayerNameByPlayerListIndex(Players.ElementAt(index).Key);
|
||||
}
|
||||
|
||||
private delegate string? ToStringHook(int argumentIndex, CommandArgumentType type, int elementIndex, object value, string currentTextValue);
|
||||
|
||||
private static readonly Regex _matchHotkey = new("^(.*)((左右键|[A-Za-z]+))$");
|
||||
private static ImmutableDictionary<uint, string> _stringHashes = ImmutableDictionary<uint, string>.Empty;
|
||||
private readonly CancellationTokenSource _cancellation = new();
|
||||
private Model _model = new();
|
||||
private string? _cached;
|
||||
private DateTimeOffset _aiStartTime;
|
||||
|
||||
public EventDump()
|
||||
{
|
||||
InitializeComponent();
|
||||
Closing += EventDump_Closing;
|
||||
Closed += EventDump_Closed;
|
||||
// add enum values of CompactLevel to _playerComboBox
|
||||
var values = Enum.GetValues(typeof(CompactLevel)).Cast<CompactLevel>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
_compactLevelComboBox.Items.Add(value.ToString());
|
||||
}
|
||||
_compactLevelComboBox.SelectedIndex = (int)CompactLevel.VeryCompactedForAI;
|
||||
}
|
||||
|
||||
private void EventDump_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellation.Cancel();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"EventDump_Closing: {ex}\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
private void EventDump_Closed(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellation.Cancel();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Instance.DebugMessage += $"EventDump_Closed: {ex}\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadStringHashes()
|
||||
{
|
||||
var stringHashes = File.ReadAllText(@"C:\Apps\RA3-MODSDK-X\builtmods\StringHashes.xml");
|
||||
XDocument doc = XDocument.Parse(stringHashes);
|
||||
XNamespace ns = "uri:ea.com:eala:asset";
|
||||
var table = doc
|
||||
.Descendants(ns + "StringHashTable")
|
||||
.FirstOrDefault(x => (string?)x.Attribute("id") == "StringHashBin_INSTANCEID");
|
||||
|
||||
if (table == null)
|
||||
throw new InvalidOperationException("StringHashBin_INSTANCEID not found.");
|
||||
|
||||
_stringHashes = table
|
||||
.Elements(ns + "StringAndHash")
|
||||
.ToImmutableDictionary(
|
||||
x => uint.Parse(x.Attribute("Hash")!.Value),
|
||||
x => x.Attribute("Text")!.Value);
|
||||
}
|
||||
|
||||
internal void SetDumpData(Mod mod, ApmPlotter plotter)
|
||||
{
|
||||
var level = (CompactLevel)_compactLevelComboBox.SelectedIndex;
|
||||
_model = new Model(mod, plotter.PlayersMap, plotter.Commands, level);
|
||||
}
|
||||
|
||||
public async Task ShowPlainText()
|
||||
{
|
||||
_cached = null;
|
||||
if (_model.IsDefault)
|
||||
{
|
||||
_textBox.Text = "";
|
||||
_tokenUsageLabel.Content = "";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var playerData = _model.Players.Values.Select(x =>
|
||||
{
|
||||
var factionName = ModData.GetFaction(_model.Mod, x.FactionId).Name;
|
||||
return $"{x.PlayerName},队伍{x.Team},{factionName}";
|
||||
});
|
||||
//await analyzer.AnalyzeAsync("deepseek-v4-flash",
|
||||
// AIAnalyze.GetSystemPrompt(_model.Mod, _model.Players),
|
||||
// AIAnalyze.BuildUserPrompt(_model.Mod, _model.Players, text),
|
||||
// deepSeekExtraParams);
|
||||
|
||||
_textBox.Text = "正在加载,请稍候";
|
||||
_tokenUsageLabel.Content = "";
|
||||
Show();
|
||||
var text = await Task.Run(() => GeneratePlainText(_model));
|
||||
var (bytesCount, estimatedTokenCount) = AIAnalyze.EstimateTokenCount(text);
|
||||
_textBox.Text = text;
|
||||
_cached = text;
|
||||
// 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)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (int chunkIndex = 0; chunkIndex < model.Commands.Length; ++chunkIndex)
|
||||
{
|
||||
var (time, commands) = model.Commands[chunkIndex];
|
||||
var filtered = commands
|
||||
.Where((c, i) => ShouldDisplay(model.Level, commands, i))
|
||||
.ToList();
|
||||
if (filtered.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sb.AppendLine($"[{TimeStampToString(time, model.Level)}]");
|
||||
foreach (var command in filtered)
|
||||
{
|
||||
var commandName = RA3Commands.GetCommandName(command.CommandId);
|
||||
if (model.Level > CompactLevel.NoCompact)
|
||||
{
|
||||
commandName = command.CommandId switch
|
||||
{
|
||||
0x1F5 => ((bool[])command.Data[0].Value)[0] switch
|
||||
{
|
||||
true when command.Data.Length <= 1 || command.Data[1].Count == 0 => "取消选择",
|
||||
true => "重新选择单位",
|
||||
false => "追加选择单位"
|
||||
},
|
||||
0x22E => "切换姿态",
|
||||
_ => CommandNameRemoveDescription(commandName),
|
||||
};
|
||||
}
|
||||
var playerName = model.PlayerNameByPlayerListIndex(command.PlayerIndex);
|
||||
|
||||
sb.AppendLine($"{playerName}: {commandName}");
|
||||
AppendCommandArguments(sb, command, (i, type, j, value, text) => command.CommandId switch
|
||||
{
|
||||
0x1F5 when i == 0 => j switch
|
||||
{
|
||||
0 when model.Level > CompactLevel.NoCompact => null,
|
||||
0 => (bool)value ? "替换现有选择" : "加入到当前选择",
|
||||
1 => null,
|
||||
_ => throw new NotImplementedException(),
|
||||
},
|
||||
0x1F8 when i == 0 && model.Level > CompactLevel.NoCompact => null,
|
||||
0x205 or 0x206 when i == 2 => (bool)value ? "连续5个" : null,
|
||||
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}(出兵建筑)",
|
||||
0x252 => $"{model.PlayerNameByGameSlotIndex((int)command.Data[0].Value)}已主动退出游戏",
|
||||
0x22E when i == 0 => j == 0 ? StanceToString((int)value) : null,
|
||||
_ => text,
|
||||
});
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static bool ShouldDisplay(CompactLevel level, ImmutableArray<CommandChunk> commands, int i)
|
||||
{
|
||||
var chunk = commands[i];
|
||||
var commandId = chunk.CommandId;
|
||||
if (commandId is 0x1 or 0x252)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (ApmPlotter.IsUnknown(commandId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ApmPlotter.IsAuto(commandId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (commandId is 0x1F5) // 选择单位
|
||||
{
|
||||
var isNewSelection = ((bool[])chunk.Data[0].Value)[0];
|
||||
if (chunk.Data.Length <= 1 || chunk.Data[1].Count == 0)
|
||||
{
|
||||
// 空选择:
|
||||
// 假如是追加到当前选择,那么等于无操作,没什么意义,可以过滤掉
|
||||
// 假如是新建选择,那么等于取消当前选择,在最高 compact level 下也可以过滤掉
|
||||
return level switch
|
||||
{
|
||||
<= CompactLevel.NoCompact => true,
|
||||
CompactLevel.ForAI => isNewSelection,
|
||||
>= CompactLevel.VeryCompactedForAI => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (level >= CompactLevel.ForAI && commandId is 0x1F8) // 取消选择
|
||||
{
|
||||
if (level >= CompactLevel.VeryCompactedForAI)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var hasSelectGroupAfterThis = commands
|
||||
.Skip(i + 1)
|
||||
.Any(c => c.PlayerIndex == chunk.PlayerIndex && c.CommandId == 0x1FB);
|
||||
if (hasSelectGroupAfterThis)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string TimeStampToString(TimeSpan t, CompactLevel level) => level switch
|
||||
{
|
||||
<= CompactLevel.NoCompact => $"{t:hh\\:mm\\:ss\\.ff}",
|
||||
_ => $"{(int)t.TotalMinutes}:{t:ss\\.ff}",
|
||||
};
|
||||
|
||||
private static string CommandNameRemoveDescription(string commandName)
|
||||
{
|
||||
var match = _matchHotkey.Match(commandName);
|
||||
if (match.Success)
|
||||
{
|
||||
commandName = match.Groups[1].Value;
|
||||
}
|
||||
return commandName;
|
||||
}
|
||||
|
||||
private static string ProductionQueueTypeToString(int value) => value switch
|
||||
{
|
||||
0 => "主要建筑",
|
||||
1 => "其他建筑",
|
||||
2 => "步兵",
|
||||
3 => "载具",
|
||||
4 => "飞行器",
|
||||
5 => "升级",
|
||||
6 => "舰船",
|
||||
_ => $"无效({value})"
|
||||
};
|
||||
|
||||
private static string StanceToString(int value) => value switch
|
||||
{
|
||||
0 => "Guard",
|
||||
1 => "Aggressive",
|
||||
2 => "HoldPosition",
|
||||
3 => "HoldFire",
|
||||
_ => $"Unknown({value})"
|
||||
};
|
||||
|
||||
private static void AppendCommandArguments(StringBuilder sb, CommandChunk command, ToStringHook hook)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var (argType, argValue, argCount) in command.Data)
|
||||
{
|
||||
++count;
|
||||
var prefix = argType is CommandArgumentType.ObjectId or CommandArgumentType.ObjectId_2
|
||||
? "[UnitId]"
|
||||
: string.Empty;
|
||||
if (argCount == 1)
|
||||
{
|
||||
var textValue = CommandArgumentToString(argType, argValue);
|
||||
textValue = hook(count - 1, argType, 0, argValue, textValue);
|
||||
if (textValue is not null)
|
||||
{
|
||||
sb.AppendLine($" {prefix}{textValue}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var textValues = ((Array)argValue).Cast<object>().Select((x, i) =>
|
||||
{
|
||||
var textValue = CommandArgumentToString(argType, x);
|
||||
textValue = hook(count - 1, argType, i, x, textValue);
|
||||
return textValue;
|
||||
});
|
||||
if (textValues.All(x => x is null))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sb.AppendLine($" {prefix}{string.Join(",", textValues.Where(x => x is not null))}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string CommandArgumentToString(CommandArgumentType type, object value)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
CommandArgumentType.Int32 => Int32BitToString((int)value),
|
||||
CommandArgumentType.UInt32 or CommandArgumentType.UInt32_2 => Int32BitToString((uint)value),
|
||||
|
||||
CommandArgumentType.Float32 => ((float)value).ToString("0.##"),
|
||||
|
||||
CommandArgumentType.Bool
|
||||
or CommandArgumentType.UInt16
|
||||
or CommandArgumentType.ObjectId
|
||||
or CommandArgumentType.ObjectId_2
|
||||
or CommandArgumentType.AsciiString
|
||||
or CommandArgumentType.UnicodeString
|
||||
or CommandArgumentType.AssetId
|
||||
or CommandArgumentType.Vector3 => value.ToString(),
|
||||
_ => throw new InvalidOperationException($"Unknown argument type {value}"),
|
||||
};
|
||||
}
|
||||
|
||||
private static string Int32BitToString<T>(T value) where T : struct
|
||||
{
|
||||
// try to convert int32 or uint32 to hashes and retrieve text
|
||||
uint hashValue = value switch
|
||||
{
|
||||
int intValue => unchecked((uint)intValue),
|
||||
uint uintValue => uintValue,
|
||||
_ => throw new InvalidOperationException($"Unexpected type {typeof(T)}"),
|
||||
};
|
||||
|
||||
return _stringHashes.TryGetValue(hashValue, out var text) ? text : hashValue.ToString();
|
||||
}
|
||||
|
||||
private void OnExportButtonClick(object sender, RoutedEventArgs e) => Export(this);
|
||||
|
||||
private void Export(Window owner)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = "文本文档 (*.txt)|*.txt|所有文件 (*.*)|*.*",
|
||||
OverwritePrompt = true,
|
||||
};
|
||||
|
||||
var result = saveFileDialog.ShowDialog(owner);
|
||||
if (result == true)
|
||||
{
|
||||
using var file = saveFileDialog.OpenFile();
|
||||
using var writer = new StreamWriter(file);
|
||||
writer.Write(_textBox.Text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
await ShowPlainText();
|
||||
}
|
||||
|
||||
private async void OnAIAnalyzeClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var cached = _cached;
|
||||
if (cached is null)
|
||||
{
|
||||
MessageBox.Show(this, "请先生成文本");
|
||||
return;
|
||||
}
|
||||
|
||||
_aiStartTime = DateTimeOffset.MaxValue;
|
||||
_elapsedTimeTextBlock.Text = "";
|
||||
_contentCountTextBlock.Text = "";
|
||||
_rateTextBlock.Text = "";
|
||||
_tokensDetailsTextBlock.Text = "";
|
||||
_extraStatusTextBlock.Text = "";
|
||||
|
||||
var pending = new ConcurrentQueue<AIAnalyzeUI.AIAnalyzeProgressData>();
|
||||
var emaSpeedCalculator = new AIAnalyzeUI.EmaSpeed();
|
||||
var totalCharacters = 0;
|
||||
|
||||
void TimerUpdateStatus(object sender, EventArgs ea)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(0.1),
|
||||
};
|
||||
timer.Tick += TimerUpdateStatus;
|
||||
timer.Start();
|
||||
try
|
||||
{
|
||||
await LaunchAIAnalyze(cached, pending.Enqueue);
|
||||
TimerUpdateStatus(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(this, $"AI分析失败: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_aiStartTime = DateTimeOffset.MaxValue;
|
||||
timer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LaunchAIAnalyze(string replayData, Action<AIAnalyzeUI.AIAnalyzeProgressData> newContent)
|
||||
{
|
||||
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://integrate.api.nvidia.com/v1/", "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");
|
||||
var deepSeekExtraParams = new Dictionary<string, object>
|
||||
{
|
||||
["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.Mod, _model.Players);
|
||||
var userPrompt = AIAnalyze.BuildUserPrompt(_model.Mod, _model.Players, replayData);
|
||||
#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("deepseek-ai/deepseek-v4-flash",
|
||||
systemPrompt,
|
||||
userPrompt,
|
||||
deepSeekExtraParams,
|
||||
firstReader,
|
||||
_cancellation.Token
|
||||
));
|
||||
_tokensDetailsTextBlock.Text = $"Token: {result.TotalTokens}(输入{result.PromptTokens})";
|
||||
AddExtraContent("\r\n--- // 开始分段分析\r\n");
|
||||
while (result.CurrentSegment < result.Segments.Count)
|
||||
{
|
||||
var currentSegmentName = $"{result.CurrentSegment + 1}";
|
||||
_extraStatusTextBlock.Text = $"AI正在分析录像{currentSegmentName}/{result.Segments.Count}";
|
||||
var segmentReader = AIAnalyzeUI.BuildAIChunkReader(newContent);
|
||||
result = await Task.Run(() => analyzer.ContinueAnalyzeAsync(
|
||||
segmentReader,
|
||||
_cancellation.Token
|
||||
));
|
||||
_tokensDetailsTextBlock.Text = $"Token: {result.TotalTokens}(输入{result.PromptTokens})";
|
||||
AddExtraContent($"\r\n--- // 第{currentSegmentName}段已分析完毕\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user