summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock91
-rw-r--r--Cargo.toml4
-rw-r--r--README.md8
-rw-r--r--src/render.rs265
-rw-r--r--src/repl.rs9
5 files changed, 36 insertions, 341 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cea59c3..ec57f28 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,17 +9,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
-name = "ahash"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
-dependencies = [
- "getrandom",
- "once_cell",
- "version_check",
-]
-
-[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -32,12 +21,10 @@ dependencies = [
name = "aichat"
version = "0.2.0"
dependencies = [
- "ansi-to-tui",
"anyhow",
"bytes",
"clap",
"crossbeam",
- "crossterm 0.26.1",
"dirs",
"eventsource-stream",
"futures-util",
@@ -51,9 +38,7 @@ dependencies = [
"serde_json",
"serde_yaml",
"syntect",
- "textwrap",
"tokio",
- "tui",
]
[[package]]
@@ -81,17 +66,6 @@ dependencies = [
]
[[package]]
-name = "ansi-to-tui"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3460d7beaf8b192c09a55933da038ccd514f00efdb37d7d87f3ce078336b47e9"
-dependencies = [
- "nom",
- "thiserror",
- "tui",
-]
-
-[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -232,12 +206,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
-name = "cassowary"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
-
-[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -452,22 +420,6 @@ dependencies = [
]
[[package]]
-name = "crossterm"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
-dependencies = [
- "bitflags",
- "crossterm_winapi",
- "libc",
- "mio",
- "parking_lot",
- "signal-hook",
- "signal-hook-mio",
- "winapi",
-]
-
-[[package]]
name = "crossterm_winapi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -854,9 +806,6 @@ name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-dependencies = [
- "ahash",
-]
[[package]]
name = "heck"
@@ -1990,12 +1939,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
-name = "smawk"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
-
-[[package]]
name = "socket2"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2145,17 +2088,6 @@ dependencies = [
]
[[package]]
-name = "textwrap"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
-dependencies = [
- "smawk",
- "unicode-linebreak",
- "unicode-width",
-]
-
-[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2405,19 +2337,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633"
[[package]]
-name = "tui"
-version = "0.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
-dependencies = [
- "bitflags",
- "cassowary",
- "crossterm 0.25.0",
- "unicode-segmentation",
- "unicode-width",
-]
-
-[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2457,16 +2376,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
-name = "unicode-linebreak"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
-dependencies = [
- "hashbrown",
- "regex",
-]
-
-[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index ba0d5fc..1879e77 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,6 @@ keywords = ["chatgpt", "ai"]
anyhow = "1.0.69"
bytes = "1.4.0"
clap = { version = "4.1.8", features = ["derive", "string"] }
-crossterm = "0.26.1"
dirs = "4.0.0"
eventsource-stream = "0.2.3"
futures-util = "0.3.26"
@@ -31,9 +30,6 @@ tokio = { version = "1.26.0", features = ["full"] }
mdcat = { version = "1.1.0", default_features = false, features =["static"] }
pulldown-cmark = { version = "0.9.2", default-features = false, features = ['simd'] }
crossbeam = "0.8.2"
-tui = "0.19.0"
-ansi-to-tui = "2.0.0"
-textwrap = "0.16.0"
[dependencies.syntect]
version = "5.0.0"
diff --git a/README.md b/README.md
index 70639a8..013be65 100644
--- a/README.md
+++ b/README.md
@@ -60,10 +60,10 @@ The default config dir is as follows, You can override config dir with `$AICHAT_
aichat may generate the following files in the config dir:
-- `config.yaml`: the config file.
-- `roles.yaml`: the roles definition file.
-- `history.txt`: the repl history file.
-- `messages.md`: the chat messages storage file.
+- `config.yaml`: configuration file.
+- `roles.yaml`: role definition file.
+- `history.txt`: repl history file.
+- `messages.md`: chat messages file.
### Roles
diff --git a/src/render.rs b/src/render.rs
index 19dd470..4af0d59 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -1,24 +1,14 @@
use anyhow::Result;
use crossbeam::channel::Receiver;
-use crossterm::{
- event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers},
- execute,
- style::{self, Color},
- terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
-};
use mdcat::{
push_tty,
terminal::{TerminalProgram, TerminalSize},
Environment, ResourceAccess, Settings,
};
use pulldown_cmark::Parser;
-use std::{
- io,
- sync::{
- atomic::{AtomicBool, Ordering},
- Arc,
- },
- time::{Duration, Instant},
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
};
use syntect::parsing::SyntaxSet;
@@ -29,235 +19,44 @@ pub fn render_stream(
ctrlc: Arc<AtomicBool>,
markdown_render: Arc<MarkdownRender>,
) -> Result<()> {
- // setup terminal
- enable_raw_mode()?;
- let mut stdout = io::stdout();
- execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
- let backend = tui::backend::CrosstermBackend::new(stdout);
- let mut terminal = tui::Terminal::new(backend)?;
-
- // create app and run it
- let app = render_stream_tui::App::new(ctrlc, markdown_render);
- let res = render_stream_tui::run(&mut terminal, app, rx);
-
- // restore terminal
- disable_raw_mode()?;
- execute!(
- terminal.backend_mut(),
- LeaveAlternateScreen,
- DisableMouseCapture
- )?;
- terminal.show_cursor()?;
-
- res
-}
-
-mod render_stream_tui {
- use super::*;
- use ansi_to_tui::IntoText;
- use crossterm::event::MouseEventKind;
- use tui::{
- backend::Backend,
- layout::{Constraint, Direction, Layout},
- style::Style,
- widgets::{List, ListItem, ListState},
- Frame, Terminal,
- };
-
- pub fn run<B: Backend>(
- terminal: &mut Terminal<B>,
- mut app: App,
- rx: Receiver<RenderStreamEvent>,
- ) -> Result<()> {
- let mut last_tick = Instant::now();
- let tick_rate = Duration::from_millis(250);
-
- loop {
- terminal.draw(|f| ui(f, &mut app))?;
-
- if app.ctrlc.load(Ordering::SeqCst) {
- return Ok(());
- }
-
- if let Ok(evt) = rx.try_recv() {
- let want_to_flush = app.handle(evt)?;
- if !want_to_flush {
- continue;
- }
- }
-
- if app.is_done() {
- return Ok(());
- }
-
- let timeout = tick_rate
- .checked_sub(last_tick.elapsed())
- .unwrap_or_else(|| Duration::from_secs(0));
- if crossterm::event::poll(timeout)? {
- match event::read()? {
- Event::Key(key) => match key.code {
- KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
- app.quit();
- return Ok(());
- }
- KeyCode::Down => app.next(),
- KeyCode::Up => app.previous(),
- _ => {}
- },
- Event::Mouse(ev) => match ev.kind {
- MouseEventKind::ScrollDown => app.next(),
- MouseEventKind::ScrollUp => app.previous(),
- _ => {}
- },
- _ => {}
- }
- }
- if last_tick.elapsed() >= tick_rate {
- app.on_tick();
- last_tick = Instant::now();
- }
- }
- }
-
- pub struct App {
- buffer: String,
- items: Vec<String>,
- list_state: ListState,
- finish_at: Option<Instant>,
- ctrlc: Arc<AtomicBool>,
- markdown_render: Arc<MarkdownRender>,
- auto_scroll: bool,
- num_rows: usize,
- num_cols: usize,
- entry_index: usize,
- }
-
- impl App {
- pub fn new(ctrlc: Arc<AtomicBool>, markdown_render: Arc<MarkdownRender>) -> Self {
- Self {
- buffer: String::new(),
- ctrlc,
- finish_at: None,
- markdown_render,
- num_rows: 0,
- num_cols: 0,
- auto_scroll: true,
- entry_index: 0,
- items: vec![],
- list_state: ListState::default(),
- }
+ let mut buffer = String::new();
+ let mut line_index = 0;
+ loop {
+ if ctrlc.load(Ordering::SeqCst) {
+ return Ok(());
}
-
- pub fn handle(&mut self, evt: RenderStreamEvent) -> Result<bool> {
- let want_to_flush = match evt {
- RenderStreamEvent::Start(question) => {
- let mut buf = Vec::with_capacity(8);
- execute!(
- buf,
- style::SetForegroundColor(Color::Cyan),
- style::Print("〉"),
- style::ResetColor
- )?;
- let indicator = String::from_utf8_lossy(&buf);
- self.buffer.push_str(&format!("{indicator}{question}\n"));
- true
- }
+ if let Ok(evt) = rx.try_recv() {
+ match evt {
+ RenderStreamEvent::Start(_) => {}
RenderStreamEvent::Text(text) => {
- self.buffer.push_str(&text);
- text.contains('\n')
+ buffer.push_str(&text);
+ if text.contains('\n') {
+ let markdown = markdown_render.render(&buffer)?;
+ let lines: Vec<&str> = markdown.lines().collect();
+ let (_, print_lines) = lines.split_at(line_index);
+ let mut print_lines = print_lines.to_vec();
+ print_lines.pop();
+ if !print_lines.is_empty() {
+ line_index += print_lines.len();
+ dump(print_lines.join("\n").to_string(), 1);
+ }
+ }
}
RenderStreamEvent::Done => {
- self.finish_at = Some(Instant::now());
- true
- }
- };
- let wrapped_buffer = textwrap::wrap(&self.buffer, self.num_cols).join("\n");
- let markdown = self.markdown_render.render(&wrapped_buffer)?;
- self.items = markdown.split('\n').map(|v| v.to_string()).collect();
-
- if self.auto_scroll {
- self.scroll_to_end();
- }
-
- Ok(want_to_flush)
- }
-
- pub fn quit(&mut self) {
- self.ctrlc.store(true, Ordering::SeqCst);
- }
-
- pub fn is_done(&mut self) -> bool {
- if self.auto_scroll {
- if let Some(finish_at) = self.finish_at {
- if finish_at.elapsed() >= Duration::from_secs(1) {
- return true;
- }
+ let markdown = markdown_render.render(&buffer)?;
+ let tail = markdown
+ .lines()
+ .skip(line_index)
+ .collect::<Vec<&str>>()
+ .join("\n");
+ dump(tail, 2);
+ break;
}
}
- false
}
-
- pub fn set_size(&mut self, rows: u16, cols: u16) {
- self.num_rows = rows as usize;
- self.num_cols = cols as usize;
- }
-
- pub fn next(&mut self) {
- self.auto_scroll = false;
- let index = if self.entry_index < self.num_rows {
- self.num_rows.min(self.items.len() - 1)
- } else {
- self.entry_index + 1
- };
- self.entry_index = index;
- self.list_state.select(Some(index));
- }
-
- pub fn previous(&mut self) {
- self.auto_scroll = false;
- let index = self
- .entry_index
- .saturating_sub(1)
- .min(self.items.len().saturating_sub(self.num_rows + 1));
- self.entry_index = index;
- self.list_state.select(Some(index));
- }
-
- pub fn scroll_to_end(&mut self) {
- let len = self.items.len();
- self.entry_index = if len < self.num_rows {
- 0
- } else {
- len - self.num_rows
- };
- self.list_state.select(Some(len - 1))
- }
-
- pub fn on_tick(&mut self) {}
}
- fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
- // Create two chunks with equal horizontal screen space
- let chunks = Layout::default()
- .direction(Direction::Vertical)
- .constraints([Constraint::Percentage(100)].as_ref())
- .split(f.size());
-
- let items: Vec<ListItem> = app
- .items
- .iter()
- .map(|line| {
- let text = line.into_text().unwrap_or_default();
- ListItem::new(text)
- })
- .collect();
-
- let area = chunks[0];
-
- app.set_size(area.height, area.width);
- let items = List::new(items).highlight_style(Style::default());
- f.render_stateful_widget(items, area, &mut app.list_state);
- }
+ Ok(())
}
pub struct MarkdownRender {
diff --git a/src/repl.rs b/src/repl.rs
index e1cf4cf..3ccc7e4 100644
--- a/src/repl.rs
+++ b/src/repl.rs
@@ -251,15 +251,6 @@ impl ReplCmdHandler {
&receiver.output,
);
wg.wait();
- match self.render.clone() {
- Some(markdown_render) => {
- markdown_render.print(&receiver.output)?;
- dump("", 1);
- }
- None => {
- dump(&receiver.output, 2);
- }
- }
self.state.borrow_mut().output = receiver.output;
}
ReplCmd::SetRole(name) => match self.config.find_role(&name) {