Skip to content

Commit

Permalink
feat(cli): use editor for workon and chat on rs CLI (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
skyl authored Nov 24, 2024
1 parent 1f7cfe9 commit 9db7571
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 73 deletions.
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ services:
CORPORA_CLIENT_SECRET: "${CORPORA_CLIENT_SECRET}"
GITHUB_TOKEN: "${GITHUB_TOKEN}"
# OPENAI_API_KEY: "${OPENAI_API_KEY}"
# EDITOR: "code"
command: sleep infinity
working_dir: /workspace
networks:
Expand Down
41 changes: 14 additions & 27 deletions rs/core/corpora_cli/src/commands/chat.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
use crate::context::Context;
use corpora_client::models::{CorpusChatSchema, MessageSchema};
use dialoguer::{theme::ColorfulTheme, Input};
use std::fs;
use termimad::MadSkin;

/// The `workon` command operation
/// Executes the chat command operation
pub fn run(ctx: &Context) {
let mut messages: Vec<MessageSchema> = Vec::new();
let mut messages = Vec::new();

// REPL loop
loop {
// TODO: multiline input
let user_input: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Can I help you?")
.allow_empty(false)
.interact_text()
.unwrap();
let user_input = ctx
.get_user_input_via_editor("Put your prompt here and close")
.expect("Failed to obtain user input");

// Add the user's input as a new message
messages.push(MessageSchema {
role: "user".to_string(),
text: user_input.trim().to_string(),
Expand All @@ -30,11 +24,7 @@ pub fn run(ctx: &Context) {
let structure =
fs::read_to_string(root_path.join(".corpora/STRUCTURE.md")).unwrap_or_default();

// println!("Voice: {}", voice);
// println!("Purpose: {}", purpose);
// println!("Structure: {}", structure);

let response = match corpora_client::apis::corpus_api::chat(
match corpora_client::apis::corpus_api::chat(
&ctx.api_config,
CorpusChatSchema {
messages: messages.clone(),
Expand All @@ -49,21 +39,18 @@ pub fn run(ctx: &Context) {
directions: None,
},
) {
Ok(response) => response,
Ok(response) => {
let skin = MadSkin::default();
skin.print_text(&response);
messages.push(MessageSchema {
role: "assistant".to_string(),
text: response.clone(),
});
}
Err(err) => {
ctx.error(&format!("Failed to get response: {:?}", err));
continue;
}
};

// ctx.print(&response, dialoguer::console::Style::new().dim());
let skin = MadSkin::default();
skin.print_text(&response);

// println!("{}", relative_path.display());
messages.push(MessageSchema {
role: "assistant".to_string(),
text: response.clone(),
});
}
}
63 changes: 18 additions & 45 deletions rs/core/corpora_cli/src/commands/workon.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::context::Context;
use clap::Args;
use corpora_client::models::{CorpusFileChatSchema, MessageSchema};
use dialoguer::{theme::ColorfulTheme, Confirm, Input};
use dialoguer::{theme::ColorfulTheme, Confirm};
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
Expand All @@ -14,48 +14,31 @@ pub struct WorkonArgs {

/// The `workon` command operation
pub fn run(ctx: &Context, args: WorkonArgs) {
// get path as a path from the string
let path = Path::new(&args.path);
// get cwd
let cwd = std::env::current_dir().unwrap();
ctx.print(
&format!("Current working directory: {}", cwd.display()),
dialoguer::console::Style::new().dim(),
);
// add cwd and args.path
let absolute_path = Path::join(&cwd, path);
// rm the config.root_path to get the relative path
let cwd = std::env::current_dir().expect("Failed to get current directory");
ctx.dim(&format!("Current working directory: {}", cwd.display()));
let absolute_path = cwd.join(path);
let relative_path = absolute_path
.strip_prefix(&ctx.corpora_config.root_path)
.unwrap();
// ctx.success(&format!("Working on file: {}", relative_path.display()));
ctx.print(
&format!("Working on file: {}", relative_path.display()),
dialoguer::console::Style::new().dim().green(),
);
.expect("Failed to get relative path");

ctx.magenta(&format!("Working on file: {}", relative_path.display()));
let ext = relative_path
.extension()
.unwrap_or_default()
.to_str()
.and_then(|s| s.to_str())
.unwrap_or("");

// Show current file content, load the content from the
let current_file_content = match fs::read_to_string(path) {
Ok(content) => content,
Err(_) => {
File::create(path).expect("Failed to create file");
String::new()
}
};

ctx.success("Current file content:");
ctx.print(
&current_file_content,
dialoguer::console::Style::new().dim(),
);

let mut messages: Vec<MessageSchema> = Vec::new();
ctx.dim(&current_file_content);

let mut messages: Vec<MessageSchema> = vec![];
if !current_file_content.is_empty() {
messages.push(MessageSchema {
role: "user".to_string(),
Expand All @@ -67,25 +50,19 @@ pub fn run(ctx: &Context, args: WorkonArgs) {
});
}

// REPL loop
loop {
let user_input: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt(if messages.is_empty() {
"What to do?"
} else {
"How to revise?"
})
.allow_empty(false)
.interact_text()
.unwrap();
let user_input = ctx
.get_user_input_via_editor(&format!(
"Put your prompt here for {}, save and close",
path.display()
))
.expect("Failed to get user input");

// Add the user's input as a new message
messages.push(MessageSchema {
role: "user".to_string(),
text: user_input.trim().to_string(),
});

// Generate revision
ctx.success("Generating revision...");

let root_path = &ctx.corpora_config.root_path;
Expand Down Expand Up @@ -120,12 +97,8 @@ pub fn run(ctx: &Context, args: WorkonArgs) {
}
};

ctx.print(&revision, dialoguer::console::Style::new().dim());
// println!("{}", relative_path.display());
ctx.print(
&format!("Revision for `{}`:", relative_path.display()),
dialoguer::console::Style::new().dim().magenta().on_green(),
);
ctx.dim(&revision);
ctx.highlight(&format!("^^Revision for `{}`^^", relative_path.display()));
messages.push(MessageSchema {
role: "assistant".to_string(),
text: revision.clone(),
Expand Down
71 changes: 70 additions & 1 deletion rs/core/corpora_cli/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ pub mod auth;
pub mod collector;
pub mod config;

use std::sync::Arc;

use console::{Style, Term};
// use dialoguer::Editor;
use std::process::Command;
use tempfile::NamedTempFile;

use indicatif::{ProgressBar, ProgressStyle};
use std::sync::Arc;

use corpora_client::apis::configuration::Configuration;

Expand Down Expand Up @@ -151,4 +156,68 @@ impl Context {
let dim_style = Style::new().dim();
self.print(message, dim_style);
}

/// Print a magenta message
/// Magenta messages are used for highlighting information
/// # Arguments
/// * `message` - The message to print.
pub fn magenta(&self, message: &str) {
let magenta_style = Style::new().magenta();
self.print(message, magenta_style);
}

/// Print a highlighted message
/// Highlighted messages are used for important information
/// # Arguments
/// * `message` - The message to print.
pub fn highlight(&self, message: &str) {
let style = Style::new().bold().green().on_magenta();
self.print(message, style);
}

/// Get user input via an editor
/// Opens the user's default editor with the provided content
/// # Arguments
/// * `initial_content` - The initial content to display in the editor.
/// # Returns
/// The edited content as a `String`.
pub fn get_user_input_via_editor(&self, initial_content: &str) -> Result<String, String> {
get_user_input_via_editor(initial_content)
}
}

pub fn get_user_input_via_editor(initial_content: &str) -> Result<String, String> {
// Create a temporary file
let temp_file =
NamedTempFile::new().map_err(|e| format!("Failed to create temp file: {}", e))?;

// Write the initial content to the temporary file
std::fs::write(temp_file.path(), initial_content)
.map_err(|e| format!("Failed to write to temp file: {}", e))?;

// Get the editor from $EDITOR or default to 'vim'
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string());
let mut command = Command::new(&editor);

// Add flags for non-blocking editors
if editor.contains("code") || editor.contains("subl") {
command.arg("--wait");
} else if editor.contains("gedit") {
command.arg("--standalone");
}

// Open the file in the editor and wait for it to close
let status = command
.arg(temp_file.path())
.status()
.map_err(|e| format!("Failed to launch editor '{}': {}", editor, e))?;

// Check if the editor exited successfully
if !status.success() {
return Err(format!("Editor '{}' exited with a non-zero status", editor));
}

// Read the edited content back from the file
std::fs::read_to_string(temp_file.path())
.map_err(|e| format!("Failed to read edited file: {}", e))
}

0 comments on commit 9db7571

Please sign in to comment.