using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AnotherReplayReader.Utils { internal static class AIAnalyzeUI { public record AIAnalyzeProgressData(AIAnalyze.AIChunk Delta, DateTimeOffset? TimeStamp, bool IsExtra); public class EmaSpeed { const double Tau = 5; private DateTimeOffset lastEventTime = DateTimeOffset.UtcNow; private DateTimeOffset timeSinceLastSpeedMeasure = DateTimeOffset.UtcNow; private int bufferedCharactersSinceLastSpeedMeasure = 0; private double emaSpeed = double.NaN; public void ProcessEvent(AIAnalyzeProgressData data) { if (data.IsExtra) { return; } if (data.TimeStamp is { } timestamp) { lastEventTime = timestamp; } bufferedCharactersSinceLastSpeedMeasure += data.Delta.Text.Length; } public double GetDisplaySpeed(DateTimeOffset now) { // ========================= // 2. 计算 instant speed(真实时间) // ========================= double instant = 0; double dt = (now - timeSinceLastSpeedMeasure).TotalSeconds; if (bufferedCharactersSinceLastSpeedMeasure > 0 && dt > 0.05) { instant = bufferedCharactersSinceLastSpeedMeasure / dt; // reset window bufferedCharactersSinceLastSpeedMeasure = 0; timeSinceLastSpeedMeasure = now; } double alpha = 1 - Math.Exp(-dt / Tau); if (double.IsNaN(emaSpeed)) { emaSpeed = instant; } else { emaSpeed += alpha * (instant - emaSpeed); } double idle = (now - lastEventTime).TotalSeconds; double display = emaSpeed; if (idle > 0.5) { double decay = Math.Exp(-(idle - 0.5) / Tau); display *= decay; } return display; } } public static Action BuildAIChunkReader(Action newContent) { void OnAIChunk(AIAnalyze.AIChunk c) { if (c.Type == AIAnalyze.AIChunkType.Json) { return; } newContent(new(Delta: c, TimeStamp: DateTimeOffset.UtcNow, IsExtra: false)); } return OnAIChunk; } } }