chatty-rs 0.0.1-alpha1

A terminal-based chat client for OpenAI's GPT models.
Documentation
use ratatui::{
    Frame,
    layout::{Alignment, Rect},
    style::{Style, Stylize},
    text::{Line, Text},
    widgets::{Block, BorderType, Borders, Clear, Padding},
};
use ratatui_macros::span;

use super::utils;

#[derive(Default)]
pub struct Question<'a> {
    showing: bool,
    question: Line<'a>,
    title: Option<Line<'a>>,
}

impl<'a> Question<'a> {
    pub fn with_title(mut self, title: impl Into<Line<'a>>) -> Question<'a> {
        self.set_title(title);
        self
    }

    pub fn set_title(&mut self, title: impl Into<Line<'a>>) {
        self.title = Some(title.into());
    }

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

    pub fn open(&mut self, question: impl Into<Line<'a>>) {
        self.question = question.into();
        self.showing = true;
    }

    pub fn close(&mut self) {
        self.showing = false;
    }

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

        let max_width = (area.width as f32 * 0.8).ceil() as u16;
        let lines = utils::split_to_lines(self.question.spans.clone(), (max_width - 2) as usize);
        let area = build_area(area, max_width, lines.len() as u16 + 2);

        let mut block = Block::default()
            .borders(Borders::ALL)
            .border_type(BorderType::Rounded)
            .padding(Padding::symmetric(1, 0))
            .title_bottom(vec![
                span!(" "),
                span!("q").green().bold(),
                span!(" to close, ").white(),
                span!("y").green().bold(),
                span!(" to confirm, ").white(),
                span!("n").green().bold(),
                span!(" to cancel ").white(),
            ])
            .title_alignment(Alignment::Center)
            .border_style(Style::default().light_blue());

        if let Some(title) = &self.title {
            block = block
                .title(title.clone())
                .title_alignment(Alignment::Center);
        }

        f.render_widget(Clear, area);
        let inner = block.inner(area);
        f.render_widget(block, area);

        let text = Text::from(lines);
        f.render_widget(text, inner);
    }
}

fn build_area(area: Rect, w: u16, h: u16) -> Rect {
    let x = area.x + (area.width - w) / 2;
    let y = area.y + (area.height - 1) / 3;
    Rect::new(x, y, w, h)
}