chatty-rs 0.0.1-alpha1

A terminal-based chat client for OpenAI's GPT models.
Documentation
use crate::models::Event;
use ratatui::{
    Frame,
    layout::Rect,
    style::{Color, Style, Stylize},
    text::Line,
    widgets::{Block, BorderType, Borders, Clear, Padding, Widget},
};
use tui_textarea::{CursorMove, TextArea};

pub struct InputBox<'a> {
    showing: bool,
    text: String,
    input: TextArea<'a>,

    title: String,
    placeholder: String,
}

impl<'a> InputBox<'a> {
    pub fn with_title(mut self, title: &str) -> InputBox<'a> {
        self.set_title(title);
        self
    }

    pub fn with_placeholder(mut self, placeholder: &str) -> InputBox<'a> {
        self.set_placeholder(placeholder);
        self
    }

    pub fn set_title(&mut self, title: &str) {
        if !title.is_empty() {
            self.title = title.to_string();
        }
    }

    pub fn set_placeholder(&mut self, placeholder: &str) {
        if !placeholder.is_empty() {
            self.placeholder = placeholder.to_string();
        }
    }

    pub fn showing(&self) -> bool {
        self.showing
    }

    pub fn open(&mut self, text: impl Into<String>) {
        self.text = text.into();
        self.input = self.build_input();
        self.showing = true;
    }

    pub fn close(&mut self) -> Option<String> {
        if self.showing {
            self.showing = false;
            let text = self.input.lines().join("\n");
            return Some(text);
        }
        None
    }

    pub fn render(&mut self, f: &mut Frame, area: Rect) {
        if !self.showing {
            return;
        }

        f.render_widget(Clear, area);
        self.input.render(area, f.buffer_mut());
    }

    pub fn handle_key_event(&mut self, event: &Event) {
        match event {
            Event::KeyboardCharInput(input) => {
                self.input.input(input.clone());
            }
            Event::KeyboardPaste(text) => {
                self.input.set_yank_text(text.replace('\r', "\n"));
                self.input.paste();
            }
            _ => {}
        }
    }

    fn build_input(&self) -> TextArea<'a> {
        let mut text_area = TextArea::new(vec![self.text.clone()]);
        let block = Block::default()
            .title(Line::from(self.title.clone()).bold())
            .borders(Borders::ALL)
            .border_type(BorderType::Rounded)
            .border_style(Style::default().fg(Color::LightMagenta))
            .padding(Padding::symmetric(1, 0));
        text_area.set_block(block);
        text_area.set_placeholder_text(&self.placeholder);
        text_area.move_cursor(CursorMove::End);
        text_area
    }
}

impl Default for InputBox<'_> {
    fn default() -> Self {
        Self {
            showing: false,
            text: String::new(),
            input: TextArea::default(),
            title: "Input".to_string(),
            placeholder: "Type here...".to_string(),
        }
    }
}

pub fn build_area(area: Rect, width: u16) -> Rect {
    let x = area.x + (area.width - width) / 2;
    let y = area.y + (area.height - (area.height as f32 * 0.7).ceil() as u16) / 2;
    Rect::new(x, y, width, 3)
}