← All source files

V13 / 7879e8b5740b

Lilith.Chat.cs

Lilith/Agent/Lilith.Chat.cs

Unchanged from the preceding version
CS
using Lilith.Core.Logging;
using Lilith.Core.Ollama;
using Lilith.Core.Providers;
using System.Diagnostics;

namespace Lilith.Agent;

public partial class Lilith
{
    public async Task ChatAsync(string input, List<string>? images = null, CancellationToken cancellationToken = default)
    {
        using var operation = await _operation.EnterAsync(cancellationToken);
        ObjectDisposedException.ThrowIf(_disposed, this);
        if (!IsBooted) throw new InvalidOperationException("Boot the agent before chatting.");
        await ChatCoreAsync(input, images, cancellationToken);
    }

    private async Task ChatCoreAsync(string input, List<string>? images = null, CancellationToken cancellationToken = default)
    {
        var timer = Stopwatch.StartNew();
        string reply;
        try { reply = await Client.StreamReply(input, images, cancellationToken); }
        finally { timer.Stop(); }
        this.LogTokenCounts(timer.Elapsed);
        await SpeakReplyAsync(reply, cancellationToken);
    }

    private T WithConversation<T>(Func<ProviderClient, T> action)
    {
        using var operation = EnterOperation("Boot the agent before using its conversation.");
        return action(Client);
    }

    private void WithConversation(Action<ProviderClient> action)
    {
        using var operation = EnterOperation("Boot the agent before using its conversation.");
        action(Client);
    }

    public void NewConversation() => WithConversation(client => client.NewConversation());
    public async Task ApproveWorkspaceAsync(string id, CancellationToken cancellationToken = default)
    {
        using var operation = EnterOperation("Boot before approving workspace changes.");
        string reply = await Client.ApproveWorkspaceAsync(id, cancellationToken);
        await SpeakReplyAsync(reply, cancellationToken);
    }
    public async Task RejectWorkspaceAsync(string id, CancellationToken cancellationToken = default)
    {
        using var operation = EnterOperation("Boot before rejecting workspace changes.");
        var result = await Workspace.ExecuteAsync(new("reject", BinId: id), cancellationToken);
        Logger.SystemWriteLine(result.Message);
    }

    public void SaveConversation() => WithConversation(client => client.SaveConversation());

    public IReadOnlyList<ChatSessionSummary> ListConversations() => WithConversation(client => client.ListConversations());

    public bool LoadConversation(int displayIndex) => WithConversation(client => client.LoadConversation(displayIndex));

    public bool LoadConversation(string id) => WithConversation(client => client.LoadConversation(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.HistoryWriteLine("No saved chats found.");
                return;
            }

            Logger.HistoryWriteLine("Saved chats:", LogColors.Cyan);
            for (int i = 0; i < sessions.Count; i++)
            {
                var session = sessions[i];
                Logger.HistoryWriteLine($"{i + 1}. {session.Title} ({session.UpdatedAt.LocalDateTime:g}, {session.MessageCount} messages)");
            }

            Logger.HistoryWriteLine("Use /history <number> to load a chat.");
            return;
        }

        if (!int.TryParse(parts[1], out int index))
        {
            Logger.HistoryWriteLine("Usage: /history or /history <number>", LogColors.Orange);
            return;
        }

        if (LoadConversation(index))
        {
            Logger.HistoryWriteLine($"Loaded saved chat #{index}.", LogColors.Cyan);
        }
        else
        {
            Logger.HistoryWriteLine($"Saved chat #{index} was not found.", LogColors.Orange);
        }
    }
}