refactor: apply Single Responsibility Principle to Program.cs and ReplLoop.cs
extracted responsibilities from Program.cs (208→46 lines) and ReplLoop.cs (274→174 lines) into focused service classes: HeaderRenderer, SessionManager, ApplicationStartup, ResponseStreamer, SpinnerService, UsageDisplayer, and ContextCompactionService. Each class now has a single, well-defined responsibility, improving testability and maintainability.
This commit is contained in:
100
SpinnerService.cs
Normal file
100
SpinnerService.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AnchorCli;
|
||||
|
||||
/// <summary>
|
||||
/// Manages the "thinking" spinner animation during AI response generation.
|
||||
/// </summary>
|
||||
internal sealed class SpinnerService : IDisposable
|
||||
{
|
||||
private readonly object _consoleLock = new();
|
||||
private CancellationTokenSource? _spinnerCts;
|
||||
private Task? _spinnerTask;
|
||||
private bool _showSpinner = true;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the spinner animation.
|
||||
/// </summary>
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
_spinnerCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_showSpinner = true;
|
||||
|
||||
_spinnerTask = Task.Run(async () =>
|
||||
{
|
||||
var frames = Spinner.Known.BouncingBar.Frames;
|
||||
var interval = Spinner.Known.BouncingBar.Interval;
|
||||
int i = 0;
|
||||
|
||||
Console.Write("\x1b[?25l");
|
||||
try
|
||||
{
|
||||
while (!_spinnerCts.Token.IsCancellationRequested)
|
||||
{
|
||||
lock (_consoleLock)
|
||||
{
|
||||
if (_showSpinner && !_spinnerCts.Token.IsCancellationRequested)
|
||||
{
|
||||
var frame = frames[i % frames.Count];
|
||||
Console.Write($"\r\x1b[38;5;69m{frame}\x1b[0m Thinking...");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
try { await Task.Delay(interval, _spinnerCts.Token); } catch { }
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_consoleLock)
|
||||
{
|
||||
if (_showSpinner)
|
||||
Console.Write("\r" + new string(' ', 40) + "\r");
|
||||
Console.Write("\x1b[?25h");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the spinner animation and waits for it to complete.
|
||||
/// </summary>
|
||||
public async Task StopAsync()
|
||||
{
|
||||
_spinnerCts?.Cancel();
|
||||
if (_spinnerTask != null)
|
||||
{
|
||||
await Task.WhenAny(_spinnerTask);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the spinner (e.g., during tool execution).
|
||||
/// </summary>
|
||||
public void Pause()
|
||||
{
|
||||
lock (_consoleLock)
|
||||
{
|
||||
_showSpinner = false;
|
||||
Console.Write("\r" + new string(' ', 40) + "\r");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resumes the spinner after being paused.
|
||||
/// </summary>
|
||||
public void Resume()
|
||||
{
|
||||
lock (_consoleLock)
|
||||
{
|
||||
_showSpinner = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_spinnerCts?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user