add more cli features and styling
refactoring
This commit is contained in:
Generated
+63
@@ -262,6 +262,46 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
@@ -1053,6 +1093,19 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.18.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb"
|
||||
dependencies = [
|
||||
"console",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"unit-prefix",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.0"
|
||||
@@ -1547,8 +1600,12 @@ dependencies = [
|
||||
name = "own_assist"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"clap",
|
||||
"console",
|
||||
"dialoguer",
|
||||
"env_logger",
|
||||
"indicatif",
|
||||
"log",
|
||||
"ollama-rs",
|
||||
"own_assist_common",
|
||||
@@ -2748,6 +2805,12 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unit-prefix"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
||||
@@ -15,3 +15,7 @@ own_mcp = { path = "own_mcp" }
|
||||
own_assist_common = { path = "common" }
|
||||
thiserror = "2.0.18"
|
||||
dialoguer = "0.12.0"
|
||||
indicatif = "0.18.4"
|
||||
console = "0.16.3"
|
||||
clap = { version = "4.6.0", features = ["derive"] }
|
||||
base64 = "0.22.1"
|
||||
@@ -1,20 +1,47 @@
|
||||
use std::time::Duration;
|
||||
use console::style;
|
||||
use crate::translate;
|
||||
use crate::human_interface::{HumanInterface, HumanInterfaceError};
|
||||
use dialoguer::theme::ColorfulTheme;
|
||||
use dialoguer::{Confirm, Input};
|
||||
use own_mcp::mcp::chat::PermissionAnswer;
|
||||
use crate::tlt;
|
||||
|
||||
pub struct CommandLine;
|
||||
|
||||
impl HumanInterface for CommandLine {
|
||||
fn agent_message(&self, message: String) {
|
||||
println!("ai: {}", message);
|
||||
pub struct CommandLine {
|
||||
progress_bar: Option<indicatif::ProgressBar>,
|
||||
}
|
||||
|
||||
async fn expect_user_message(&self) -> Result<String, HumanInterfaceError> {
|
||||
impl CommandLine {
|
||||
pub fn new() -> CommandLine {
|
||||
CommandLine { progress_bar: None }
|
||||
}
|
||||
|
||||
fn spinner() -> indicatif::ProgressBar {
|
||||
indicatif::ProgressBar::new_spinner()
|
||||
}
|
||||
}
|
||||
|
||||
impl HumanInterface for CommandLine {
|
||||
fn agent_message(&mut self, message: String) {
|
||||
// stopped waiting for message
|
||||
if let Some(progress_bar) = &self.progress_bar {
|
||||
progress_bar.finish_and_clear();
|
||||
self.progress_bar = None;
|
||||
}
|
||||
|
||||
println!("{}: {}",style( tlt!("assistant")).bold().italic(),message);
|
||||
}
|
||||
|
||||
async fn expect_user_message(&mut self) -> Result<String, HumanInterfaceError> {
|
||||
let user_message: String = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("you")
|
||||
.with_prompt(tlt!("you"))
|
||||
.interact_text()
|
||||
.map_err(|e| HumanInterfaceError::IoError(e.into()))?;
|
||||
|
||||
// waiting for agent response
|
||||
self.progress_bar = Some(Self::spinner());
|
||||
self.progress_bar.clone().unwrap().enable_steady_tick(Duration::from_millis(100));
|
||||
|
||||
Ok(user_message)
|
||||
}
|
||||
|
||||
@@ -22,17 +49,23 @@ impl HumanInterface for CommandLine {
|
||||
mcp_server_name: String,
|
||||
tool_name: String,
|
||||
) -> Result<PermissionAnswer, HumanInterfaceError> {
|
||||
if let Some(progress_bar) = &self.progress_bar {
|
||||
progress_bar.finish_and_clear();
|
||||
}
|
||||
|
||||
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt(format!("Allow usage of {mcp_server_name}:{tool_name}"))
|
||||
.interact()
|
||||
.map_err(|e| HumanInterfaceError::IoError(e.into()))?;
|
||||
|
||||
match confirmation {
|
||||
true => Ok(PermissionAnswer::Granted),
|
||||
false => Ok(PermissionAnswer::Denied),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), HumanInterfaceError> {
|
||||
async fn run(&mut self) -> Result<(), HumanInterfaceError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ pub enum HumanInterfaceError {
|
||||
}
|
||||
|
||||
pub trait HumanInterface {
|
||||
fn agent_message(&self, message: String);
|
||||
fn agent_message(&mut self, message: String);
|
||||
|
||||
fn expect_user_message(&self) -> impl Future<Output = Result<String, HumanInterfaceError>>;
|
||||
fn expect_user_message(&mut self) -> impl Future<Output = Result<String, HumanInterfaceError>>;
|
||||
|
||||
fn ask_for_permission(&self, mcp_server_name: String, tool_name:String) -> impl Future<Output = Result<PermissionAnswer, HumanInterfaceError>>;
|
||||
|
||||
fn run(&self) -> impl Future<Output = Result<(), HumanInterfaceError>>;
|
||||
fn run(&mut self) -> impl Future<Output = Result<(), HumanInterfaceError>>;
|
||||
}
|
||||
+1
-1
@@ -47,7 +47,7 @@ fn format_dynamically(mut template: String, arguments: Vec<impl Into<String>>) -
|
||||
}
|
||||
|
||||
pub fn translate(template_name: String, arguments: Vec<impl Into<String>>) -> String {
|
||||
let locale: Locale = LOCALE_SELECTION.read().unwrap().clone();
|
||||
let locale: Locale = *LOCALE_SELECTION.read().unwrap();
|
||||
let languages = load_languages();
|
||||
let translation = languages
|
||||
.get(&locale)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"translations": {
|
||||
"system_prompt": "Du bist ein Assistent, der per Sprache bedient wird. Du erhälst die Transkription und dein Output wird per Sprache ausgegeben und sollte dementsprechend auch kurz sein und kein Markdown enthalten. Wichtiger als deine Antworten sind deine Aktionen. Nutze bitte die tools, falls du sie brauchst um Informationen zu bekommen (z.B. über das aktuelle Datum oder den aktuellen Wochentag). Du bist in einem Agent Loop und kannst mehrere Tools hintereinander nutzen. Falls der Nutzer das Nutzen eines Tools ablehnt, sag ihm bescheid, dass du es brauchst."
|
||||
"system_prompt": "Du bist ein Assistent, der per Sprache bedient wird. Du erhälst die Transkription und dein Output wird per Sprache ausgegeben und sollte dementsprechend auch kurz sein und kein Markdown enthalten. Wichtiger als deine Antworten sind deine Aktionen. Nutze bitte die tools, falls du sie brauchst um Informationen zu bekommen (z.B. über das aktuelle Datum oder den aktuellen Wochentag). Du bist in einem Agent Loop und kannst mehrere Tools hintereinander nutzen. Falls der Nutzer das Nutzen eines Tools ablehnt, sag ihm bescheid, dass du es brauchst.",
|
||||
"you": "Du",
|
||||
"assistant": "Assistent",
|
||||
"username": "Nutzername",
|
||||
"password": "Passwort"
|
||||
}
|
||||
}
|
||||
+85
-26
@@ -1,23 +1,56 @@
|
||||
mod config;
|
||||
mod model;
|
||||
mod human_interface;
|
||||
mod i18n;
|
||||
mod model;
|
||||
|
||||
|
||||
use crate::i18n::translate;
|
||||
use own_mcp::audio::{AudioClientTrait};
|
||||
use crate::config::Config;
|
||||
use crate::model::create_model_from_config;
|
||||
use crate::human_interface::HumanInterface;
|
||||
use crate::i18n::translate;
|
||||
use crate::model::create_model_from_config;
|
||||
use clap::Parser;
|
||||
use std::fmt::Display;
|
||||
use base64::Engine;
|
||||
|
||||
/// partial mcp client with cli and voice interaction
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, long_about = None)]
|
||||
struct Args {
|
||||
/// generate an encoded basic auth header
|
||||
#[clap(long, short, action)]
|
||||
basic_auth: bool,
|
||||
}
|
||||
|
||||
fn basic_auth_tool() {
|
||||
let username: String = dialoguer::Input::new()
|
||||
.with_prompt(tlt!("username"))
|
||||
.interact_text()
|
||||
.unwrap();
|
||||
|
||||
let password: String = dialoguer::Password::new()
|
||||
.with_prompt(tlt!("password"))
|
||||
.interact()
|
||||
.unwrap();
|
||||
|
||||
let encoded= base64::prelude::BASE64_STANDARD.encode(format!("{}:{}", username, password));
|
||||
println!("Basic {}", encoded);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
if args.basic_auth {
|
||||
basic_auth_tool();
|
||||
return;
|
||||
}
|
||||
|
||||
env_logger::init();
|
||||
|
||||
let config = Config::from_file().inspect_err(|e|{
|
||||
let config = Config::from_file()
|
||||
.inspect_err(|e| {
|
||||
log::error!("error loading assist.toml: {}", e);
|
||||
std::process::exit(1);
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
/*let audio_client = config.audio_client();
|
||||
log::debug!("Audio server status {:?}", audio_client.status().await.inspect_err(|e|{
|
||||
@@ -28,34 +61,60 @@ async fn main() {
|
||||
let ollama = config.ollama_instance();
|
||||
let model_name = &config.ollama_config().model.name;
|
||||
|
||||
create_model_from_config(&ollama, &config.ollama_config().model).await.inspect_err(|e|{
|
||||
log::error!("failed creating ollama model {model_name}: {e}");
|
||||
std::process::exit(1);
|
||||
}).unwrap();
|
||||
create_model_from_config(&ollama, &config.ollama_config().model)
|
||||
.await
|
||||
.inspect_err(exit_msg!(format!(
|
||||
"failed creating ollama model {model_name}"
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
let mcp_clients = config.mcp_clients().await.inspect_err(|e|{
|
||||
log::error!("failed creating MCP clients: {e}");
|
||||
std::process::exit(1);
|
||||
}).unwrap();
|
||||
let mcp_clients = config
|
||||
.mcp_clients()
|
||||
.await
|
||||
.inspect_err(exit_msg!("failed creating MCP clients"))
|
||||
.unwrap();
|
||||
|
||||
let mut agent_chat = own_mcp::AgentChat::new(ollama, model_name.clone(), mcp_clients).await.inspect_err(
|
||||
|e| {
|
||||
log::error!("error creating agent: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
).unwrap();
|
||||
let mut agent_chat = own_mcp::AgentChat::new(ollama, model_name.clone(), mcp_clients)
|
||||
.await
|
||||
.inspect_err(exit_msg!("failed to create agent"))
|
||||
.unwrap();
|
||||
config.set_tool_permissions(&mut agent_chat).await.unwrap();
|
||||
|
||||
log::info!("all tools: {:#?}", agent_chat.get_all_tools().collect::<Vec<_>>());
|
||||
log::info!(
|
||||
"all tools: {:#?}",
|
||||
agent_chat.get_all_tools().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let human_interface = human_interface::cli::CommandLine{};
|
||||
let mut human_interface = human_interface::cli::CommandLine::new();
|
||||
human_interface.run().await.unwrap();
|
||||
|
||||
loop {
|
||||
let user_message = human_interface.expect_user_message().await.unwrap();
|
||||
let agent_message = agent_chat.message(user_message, async |mcp_server_name, tool_name|{
|
||||
human_interface.ask_for_permission(mcp_server_name, tool_name).await.expect("io should not fail")
|
||||
}).await.expect("failed communicating with agent");
|
||||
let immutable_interface = &human_interface;
|
||||
let agent_message = agent_chat
|
||||
.message(user_message, async |mcp_server_name, tool_name| {
|
||||
immutable_interface
|
||||
.ask_for_permission(mcp_server_name, tool_name)
|
||||
.await
|
||||
.inspect_err(exit_msg!("Human interface error"))
|
||||
.unwrap()
|
||||
})
|
||||
.await
|
||||
.inspect_err(exit_msg!("failed communicating with agent"))
|
||||
.unwrap();
|
||||
human_interface.agent_message(agent_message.content);
|
||||
}
|
||||
}
|
||||
|
||||
fn exit_with_error_message(error: impl Display, message: impl Display) {
|
||||
log::error!("{message}: {error}");
|
||||
println!("{}", message); // makes sure that message is printed even if logging is deactivated
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! exit_msg {
|
||||
($message:expr) => {
|
||||
|e| exit_with_error_message(e, $message)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user