refactoring

partially implement tts for human interface
This commit is contained in:
milan
2026-04-14 21:26:10 +02:00
parent c2e9709aa0
commit ef468e8fd6
9 changed files with 160 additions and 37 deletions
+1
View File
@@ -105,6 +105,7 @@ pub struct OllamaConfig {
#[derive(Debug, Deserialize)]
pub struct OllamaModelConfig {
#[serde(rename = "from-model")]
pub from_model: String,
pub name: String,
pub system_prompt: Option<String>,
+71
View File
@@ -0,0 +1,71 @@
use crate::human_interface::{HumanInterface, HumanInterfaceError};
use own_mcp::audio::models::VoiceRequest;
use own_mcp::audio::{AudioClient, AudioClientTrait};
use own_mcp::mcp::chat::PermissionAnswer;
use std::path::Path;
use tokio::fs::create_dir_all;
const TEMPORARY_AUDIO_PATH: &str = "temporary_audio";
struct Audio {
client: AudioClient,
}
impl Audio {
async fn new(client: AudioClient) -> Result<Self, HumanInterfaceError> {
create_dir_all(TEMPORARY_AUDIO_PATH)
.await
.map_err(HumanInterfaceError::IoError)?;
Ok(Self { client })
}
}
impl HumanInterface for Audio {
async fn agent_message(&mut self, message: String) -> Result<(), HumanInterfaceError> {
let bytes = self
.client
.tts(VoiceRequest {
text: message,
config: None,
})
.await
.map_err(|e| HumanInterfaceError::Other(format!("Failed getting tts: {e}")))?;
tokio::fs::write(Path::new(TEMPORARY_AUDIO_PATH).join("tts.wav"), &bytes)
.await
.map_err(|e| HumanInterfaceError::IoError(e))
}
async fn expect_user_message(&mut self) -> Result<String, HumanInterfaceError> {
todo!()
}
async fn ask_for_permission(
&self,
mcp_server_name: String,
tool_name: String,
) -> Result<PermissionAnswer, HumanInterfaceError> {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_tts_fetching() {
let mut human_interface = Audio::new(
AudioClient::new(
url::Url::parse("https://audio.mboemer.de"
).unwrap()).
with_authorization(
"Basic bWlsYW46a3lLRVIjOVJAaDlAXldBI3pVXkAhb0pRIXViczNNV0g3U1NzQDVUenppJVFmciY5WEpxJXB1YmN6YiEkdmY1Z2FQd0N2aEd3".to_string())
).await.unwrap();
human_interface
.agent_message(String::from("Peter ist jetzt in deinem PC."))
.await
.unwrap();
}
}
+2 -5
View File
@@ -22,7 +22,7 @@ impl CommandLine {
}
impl HumanInterface for CommandLine {
fn agent_message(&mut self, message: String) {
async fn agent_message(&mut self, message: String) -> Result<(), HumanInterfaceError> {
// stopped waiting for message
if let Some(progress_bar) = &self.progress_bar {
progress_bar.finish_and_clear();
@@ -30,6 +30,7 @@ impl HumanInterface for CommandLine {
}
println!("{}: {}",style( tlt!("assistant")).bold().italic(),message);
Ok(())
}
async fn expect_user_message(&mut self) -> Result<String, HumanInterfaceError> {
@@ -63,9 +64,5 @@ impl HumanInterface for CommandLine {
false => Ok(PermissionAnswer::Denied),
}
}
async fn run(&mut self) -> Result<(), HumanInterfaceError> {
Ok(())
}
}
+2 -3
View File
@@ -1,4 +1,5 @@
pub mod cli;
pub mod audio;
use thiserror::Error;
use own_mcp::mcp::chat::PermissionAnswer;
@@ -12,11 +13,9 @@ pub enum HumanInterfaceError {
}
pub trait HumanInterface {
fn agent_message(&mut self, message: String);
fn agent_message(&mut self, message: String) -> impl Future<Output = Result<(), 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(&mut self) -> impl Future<Output = Result<(), HumanInterfaceError>>;
}
+13 -6
View File
@@ -7,9 +7,9 @@ use crate::config::Config;
use crate::human_interface::HumanInterface;
use crate::i18n::translate;
use crate::model::create_model_from_config;
use base64::Engine;
use clap::Parser;
use std::fmt::Display;
use base64::Engine;
/// partial mcp client with cli and voice interaction
#[derive(Parser, Debug)]
@@ -31,7 +31,7 @@ fn basic_auth_tool() {
.interact()
.unwrap();
let encoded= base64::prelude::BASE64_STANDARD.encode(format!("{}:{}", username, password));
let encoded = base64::prelude::BASE64_STANDARD.encode(format!("{}:{}", username, password));
println!("Basic {}", encoded);
}
@@ -86,7 +86,6 @@ async fn main() {
);
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();
@@ -96,13 +95,21 @@ async fn main() {
immutable_interface
.ask_for_permission(mcp_server_name, tool_name)
.await
.inspect_err(exit_msg!("Human interface error"))
.inspect_err(exit_msg!(
"Human interface error while asking for permission"
))
.unwrap()
})
.await
.inspect_err(exit_msg!("failed communicating with agent"))
.inspect_err(exit_msg!("Failed communicating with agent."))
.unwrap();
human_interface
.agent_message(agent_message.content)
.await
.inspect_err(exit_msg!(
"Human interface error while trying to display message"
))
.unwrap();
human_interface.agent_message(agent_message.content);
}
}
+18 -3
View File
@@ -1,10 +1,10 @@
use crate::translate;
use crate::config::OllamaModelConfig;
use crate::tlt;
use crate::translate;
use ollama_rs::Ollama;
use ollama_rs::error::OllamaError;
use ollama_rs::models::ModelOptions;
use ollama_rs::models::create::{CreateModelRequest, CreateModelStatus};
use crate::tlt;
pub async fn create_model_from_config(
ollama: &Ollama,
@@ -14,7 +14,22 @@ pub async fn create_model_from_config(
.num_ctx(config.context_size.unwrap_or(2048)) // 2048 is the ollama default.
.temperature(config.temperature.unwrap_or(0.8)); // 0.8 is the ollama default
let system_prompt = config.system_prompt.clone().unwrap_or(tlt!("system_prompt"));
let system_prompt = config
.system_prompt
.clone()
.unwrap_or(tlt!("system_prompt"));
// print message to warn user of long waiting times
if let Ok(models) = ollama.list_local_models().await
&& !models.iter().any(|m| m.name == config.from_model)
{
let message = format!(
"Model `{}` is not installed and will be downloaded first.",
config.from_model
);
log::info!("{}", message);
println!("{}", console::style(message).yellow().bold());
}
ollama
.create_model(