experimental-ext-interceptors/csharp/sdk/samples/AvatarMoodInterceptorSample/AvatarRenderer.cs
Peder Holdgaard Pedersen d01a626b76
C# Re-implementation (#5)
Re-implementation of C# interceptors SDK with clearer focus on
server-side / gateway interceptors.
Fixes a lot of issues with the existing implementation.
Still more boilerplate than I'm happy with, but this is better than what
was there. Will refine further in later PRs.
2026-05-14 15:11:41 +02:00

106 lines
3.7 KiB
C#

namespace AvatarMoodInterceptorSample;
/// <summary>
/// Renders the avatar pinned to the top of the terminal while the REPL
/// scrolls underneath. Uses ANSI escape sequences (DECSTBM scroll region
/// + cursor save/restore) so avatar updates redraw in place without
/// disturbing the conversation below.
/// </summary>
public static class AvatarRenderer
{
// Rows reserved at the top of the screen for the avatar block.
// 4 face lines + 1 status line + 1 separator = 6.
public const int ReservedRows = 6;
private static readonly object ConsoleLock = new();
private static string FaceFor(Mood mood) => mood switch
{
Mood.Happy => "^_^",
Mood.Curious => "o_O",
Mood.Focused => "-_-",
Mood.Confused => "?_?",
Mood.Frustrated => ">_<",
_ => "._.",
};
// ANSI 256-color foreground codes per mood.
private static string ColorFor(Mood mood) => mood switch
{
Mood.Happy => "\x1b[92m", // bright green
Mood.Curious => "\x1b[96m", // bright cyan
Mood.Focused => "\x1b[94m", // bright blue
Mood.Confused => "\x1b[93m", // bright yellow
Mood.Frustrated => "\x1b[91m", // bright red
_ => "\x1b[90m", // bright black (gray)
};
/// <summary>
/// Clears the screen, reserves the top rows for the avatar, and parks
/// the cursor below so normal Console writes scroll in the lower region.
/// </summary>
public static void Initialize()
{
lock (ConsoleLock)
{
var bottom = Math.Max(Console.WindowHeight, ReservedRows + 4);
// Clear screen + home cursor.
Console.Out.Write("\x1b[2J\x1b[H");
// DECSTBM: restrict scrolling to rows (ReservedRows+1)..bottom.
Console.Out.Write($"\x1b[{ReservedRows + 1};{bottom}r");
// Move cursor to the first row of the scrolling region.
Console.Out.Write($"\x1b[{ReservedRows + 1};1H");
Console.Out.Flush();
}
}
/// <summary>
/// Restores the terminal to a normal full-screen scroll region.
/// Call before exiting.
/// </summary>
public static void Reset()
{
lock (ConsoleLock)
{
// Reset scroll region to full screen.
Console.Out.Write("\x1b[r");
// Park cursor at bottom.
Console.Out.Write($"\x1b[{Console.WindowHeight};1H");
Console.Out.Flush();
}
}
public static void Render(AvatarState state)
{
var mood = state.Current;
var label = mood.ToString().ToLowerInvariant();
var face = FaceFor(mood);
var color = ColorFor(mood);
var reset = "\x1b[0m";
var model = state.ClassifierModel ?? "(pending)";
lock (ConsoleLock)
{
// Save cursor (scroll region + bottom-half cursor both preserved).
Console.Out.Write("\x1b[s");
// Redraw each avatar row in place. \x1b[2K clears the whole line.
WriteRow(1, $"{color} ___{reset}");
WriteRow(2, $"{color} / \\{reset}");
WriteRow(3, $"{color} | {face} |{reset} mood: {label,-11} confidence: {state.Confidence:F2} via: {model}");
WriteRow(4, $"{color} \\___/{reset}");
WriteRow(5, string.Empty);
WriteRow(6, new string('─', Math.Max(40, Console.WindowWidth - 1)));
// Restore cursor to the REPL's position below.
Console.Out.Write("\x1b[u");
Console.Out.Flush();
}
}
private static void WriteRow(int row, string content)
{
Console.Out.Write($"\x1b[{row};1H\x1b[2K{content}");
}
}