monothink 0.3.1

A totally non-agentic coding assistant
/*
* This file is part of monothink.
*
* monothink is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* monothink is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Wavething.
* If not, see <https://www.gnu.org/licenses/>.

*/

use directories::ProjectDirs;
use tokio::{
    sync::mpsc::{Receiver, Sender},
    time::{Duration, interval},
};

use crate::{
    core::conversation::{Conversation, ConversationItemKind},
    session::SessionMessage,
};

#[derive(Clone, Debug, PartialEq)]
enum DummyConversationItem {
    UserPrompt(String),
    AssistantMessage(Vec<String>),
}

#[derive(Clone, Debug, PartialEq)]
pub enum LlmMessage {
    SubmitPrompt(String),
    //CancelGeneration,
    Quit,
}

#[derive(Debug)]
pub struct Llm {
    session_tx: Sender<SessionMessage>,
    rx: Receiver<LlmMessage>,
    data: Vec<DummyConversationItem>,
}

impl Llm {
    pub fn new(session_tx: Sender<SessionMessage>, rx: Receiver<LlmMessage>) -> Self {
        Self {
            session_tx,
            rx,
            data: Vec::new(),
        }
    }

    pub async fn run(&mut self) {
        //core::conversation::generate_dummy_conversation();
        let conversation: Conversation =
            if let Some(proj_dirs) = ProjectDirs::from("org", "codemonk", "monothink") {
                // println!("{:?}", proj_dirs.config_dir());
                // println!(
                //     "{:?}",
                //     proj_dirs
                //         .state_dir()
                //         .expect("Running on other OS than Linux is not supported.")
                // );

                let mut state_dir = proj_dirs
                    .state_dir()
                    .expect("Running on other OS than Linux is not supported.")
                    .to_path_buf();
                state_dir.push("sessions");
                // state_dir.push("test.json");
                state_dir.push("test_long.json");
                let test_path = state_dir.as_path();
                Conversation::from_file(test_path).expect("Test file must be present")
            } else {
                panic!("Could not read test data from test.json.");
            };

        for item in conversation.messages() {
            match &item.content {
                ConversationItemKind::UserMessage(message_content) => {
                    self.data.push(DummyConversationItem::UserPrompt(
                        message_content.raw().to_owned(),
                    ));
                }
                ConversationItemKind::AssistantMessage(message_content) => {
                    let raw: Vec<char> = message_content.raw().chars().collect();
                    let mut msg: Vec<String> = raw.chunks(20).map(|c| c.iter().collect()).collect();
                    msg.reverse();
                    self.data.push(DummyConversationItem::AssistantMessage(msg));
                }
                ConversationItemKind::AssistantThinking(_message_content) => {}
                ConversationItemKind::ToolInvocation(_tool_invocation_content) => {}
                ConversationItemKind::ToolOutput(_tool_output_content) => {}
                ConversationItemKind::System(_) => {}
            }
        }
        self.data.reverse();
        self.data.pop(); // get rid of first user message

        // slowly eat the data
        let mut redraw_tick = interval(Duration::from_millis(25));
        let mut hold = true;
        loop {
            redraw_tick.tick().await;
            if hold {
                let event = self.rx.recv().await;
                if let Some(msg) = event {
                    match msg {
                        LlmMessage::SubmitPrompt(_) => {
                            hold = false;
                            redraw_tick = interval(Duration::from_millis(25));
                        }
                        LlmMessage::Quit => break,
                    }
                }
            }
            let mut pop = false;
            if let Some(msg) = self.data.last_mut() {
                match msg {
                    DummyConversationItem::UserPrompt(_) => {
                        hold = true;
                        pop = true;
                    }
                    DummyConversationItem::AssistantMessage(items) => {
                        if let Some(item) = items.pop() {
                            self.session_tx
                                .send(SessionMessage::AgentResponse(item.to_string()))
                                .await
                                .unwrap();
                        }
                        if items.is_empty() {
                            pop = true;
                        }
                    }
                }
                if pop {
                    self.data.pop();
                }
            } else {
                break;
            };
        }
    }
}