Chat/Lilith.Chat.cscsharp

Documentation

Chat

User-facing chat loop, conversation management, and slash commands.

Lilith.Chat.cs

  • ChatAsync — streams a user message (optional images), logs token usage, and returns the assistant reply (for console TTS).
  • NewConversation / SaveConversation / LoadConversation — session persistence via Agent-Core chat history.
  • HandleInputAsync — parses commands (exit, /new, /history, etc.) or forwards text to ChatAsync; returns whether to keep running and any assistant reply to speak.
using Agent.Core.Logging;using Agent.Core.Ollama;namespace Lilith.Agent;public partial class Lilith{    public async Task<string?> ChatAsync(string input, List<string>? images = null)    {        Logger.Write(Name, $"{Name}: ", LogColors.Yellow);        string reply = await _client.StreamReply(input, images);        Logger.WriteLine(Name, color: LogColors.Yellow);        LogTokenCounts();        return reply;    }    public void NewConversation()    {        _client.SaveCurrentConversation();        _client.StartNewSession();        _client.ClearHistory();        Logger.WriteLine("System", "New conversation started.", LogColors.Cyan);    }    public void SaveConversation() => _client.SaveCurrentConversation();    public IReadOnlyList<ChatSessionSummary> ListConversations() => _client.ListSavedConversations();    public bool LoadConversation(int displayIndex) => _client.LoadSavedConversation(displayIndex);    public bool LoadConversation(string id) => _client.LoadSavedConversation(id);    public void HandleHistoryCommand(string input)    {        var parts = input.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);        if (parts.Length == 1)        {            var sessions = ListConversations();            if (sessions.Count == 0)            {                Logger.WriteLine("History", "No saved chats found.", LogColors.Gray);                return;            }            Logger.WriteLine("History", "Saved chats:", LogColors.Cyan);            for (int i = 0; i < sessions.Count; i++)            {                var session = sessions[i];                Logger.WriteLine(                    "History",                    $"{i + 1}. {session.Title} ({session.UpdatedAt.LocalDateTime:g}, {session.MessageCount} messages)",                    LogColors.Gray);            }            Logger.WriteLine("History", "Use /history <number> to load a chat.", LogColors.Gray);            return;        }        if (!int.TryParse(parts[1], out int index))        {            Logger.WriteLine("History", "Usage: /history or /history <number>", LogColors.Orange);            return;        }        if (LoadConversation(index))        {            Logger.WriteLine("History", $"Loaded saved chat #{index}.", LogColors.Cyan);        }        else        {            Logger.WriteLine("History", $"Saved chat #{index} was not found.", LogColors.Orange);        }    }    public void LogTokenCounts()    {        if (History.Count < 2)        {            return;        }        int? promptTokens = History[^2].Tokens;        int? completionTokens = History[^1].Tokens;        if (promptTokens.HasValue || completionTokens.HasValue)        {            Logger.WriteLine(                "Tokens",                $"prompt: {promptTokens ?? 0}  completion: {completionTokens ?? 0}  total: {(promptTokens ?? 0) + (completionTokens ?? 0)}",                LogColors.Gray);        }    }    public async Task<(bool ContinueRunning, string? AssistantReply)> HandleInputAsync(string input)    {        if (input.Equals("/history", StringComparison.OrdinalIgnoreCase) ||            input.StartsWith("/history ", StringComparison.OrdinalIgnoreCase))        {            bool ok = TryCatchExtensions.ReturnTrueTryCatch(                () => HandleHistoryCommand(input),                new CatchLog("History", "History command failed.", Logger));            return (ok, null);        }        return input.ToLowerInvariant() switch        {            "exit" => (false, null),            "/new" => (                TryCatchExtensions.ReturnTrueTryCatch(                    NewConversation,                    new CatchLog("System", "Failed to start new conversation.", Logger)),                null),            _ => await HandleChatInputAsync(input)        };    }    private async Task<(bool ContinueRunning, string? AssistantReply)> HandleChatInputAsync(string input)    {        try        {            string? reply = await ChatAsync(input);            return (true, reply);        }        catch (Exception ex)        {            Logger.WriteLine(Name, $"Chat failed: [{ex.GetType().Name}] {ex.Message}", LogColors.Red);            return (true, null);        }    }}