clippy++ and report internal error types from tools

This commit is contained in:
2026-06-22 13:53:01 +02:00
parent 242771332c
commit 7563ab85ca
12 changed files with 139 additions and 162 deletions
+13 -13
View File
@@ -56,7 +56,7 @@ impl Archive {
// track, album, artist // track, album, artist
pub fn stats(&self) -> (usize, usize, usize) { pub fn stats(&self) -> (usize, usize, usize) {
self.contents.iter().map(|(_id, artifact)| { self.contents.values().map(|artifact| {
match artifact.contents() { match artifact.contents() {
Contents::Track(_) => (1, 0, 0), Contents::Track(_) => (1, 0, 0),
Contents::Album(_) => (0, 1, 0), Contents::Album(_) => (0, 1, 0),
@@ -68,16 +68,16 @@ impl Archive {
} }
pub fn get<'a>(&'a self, id: &Uuid) -> Option<ArtifactRef<'a>> { pub fn get<'a>(&'a self, id: &Uuid) -> Option<ArtifactRef<'a>> {
if self.contents.get(id).is_some() { if self.contents.contains_key(id) {
Some(ArtifactRef { id: id.clone(), archive: self }) Some(ArtifactRef { id: *id, archive: self })
} else { } else {
None None
} }
} }
pub fn get_mut<'a>(&'a mut self, id: &Uuid) -> Option<ArtifactRefMut<'a>> { pub fn get_mut<'a>(&'a mut self, id: &Uuid) -> Option<ArtifactRefMut<'a>> {
if self.contents.get(id).is_some() { if self.contents.contains_key(id) {
Some(ArtifactRefMut { id: id.clone(), archive: self }) Some(ArtifactRefMut { id: *id, archive: self })
} else { } else {
None None
} }
@@ -136,7 +136,7 @@ impl Archive {
pub fn insert<'a>(&'a mut self, artifact: Artifact) -> ArtifactRef<'a> { pub fn insert<'a>(&'a mut self, artifact: Artifact) -> ArtifactRef<'a> {
// If we are inserting a new artifact with a complete MBID... // If we are inserting a new artifact with a complete MBID...
if let Some(mbid) = artifact.mbid.clone() { if let Some(mbid) = artifact.mbid {
let search_id = mbid; let search_id = mbid;
// If an entry already exists keyed by this MBID, merge into it // If an entry already exists keyed by this MBID, merge into it
if let Some(existing) = self.contents.get_mut(&search_id) { if let Some(existing) = self.contents.get_mut(&search_id) {
@@ -145,7 +145,7 @@ impl Archive {
} else { } else {
// Otherwise, attempt to find existing artifacts with the same contents (but no MBID) // Otherwise, attempt to find existing artifacts with the same contents (but no MBID)
let mut targets: Vec<(Uuid, Artifact)> = self.contents.extract_if(|_, v| { v.contents == artifact.contents }).collect(); let mut targets: Vec<(Uuid, Artifact)> = self.contents.extract_if(|_, v| { v.contents == artifact.contents }).collect();
if let Some((target_id, mut target)) = targets.pop() { if let Some((_target_id, mut target)) = targets.pop() {
// Merge any other extracted targets into the primary one // Merge any other extracted targets into the primary one
for (_, next) in targets { for (_, next) in targets {
target.merge(next); target.merge(next);
@@ -153,11 +153,11 @@ impl Archive {
// Merge the incoming artifact into the merged target // Merge the incoming artifact into the merged target
target.merge(artifact); target.merge(artifact);
// Insert merged target under the canonical MBID key // Insert merged target under the canonical MBID key
self.contents.insert(search_id.clone(), target); self.contents.insert(search_id, target);
ArtifactRef { id: search_id, archive: self } ArtifactRef { id: search_id, archive: self }
} else { } else {
// No matching content found: insert under the MBID key // No matching content found: insert under the MBID key
self.contents.insert(search_id.clone(), artifact); self.contents.insert(search_id, artifact);
ArtifactRef { id: search_id, archive: self } ArtifactRef { id: search_id, archive: self }
} }
} }
@@ -165,9 +165,9 @@ impl Archive {
// Otherwise, we attempt to merge it in. In the end, there will somehow still be a record with this mbid // Otherwise, we attempt to merge it in. In the end, there will somehow still be a record with this mbid
let mut targets: Vec<(Uuid, Artifact)> = self.contents.extract_if(|_, v| { v.contents == artifact.contents }).collect(); let mut targets: Vec<(Uuid, Artifact)> = self.contents.extract_if(|_, v| { v.contents == artifact.contents }).collect();
if let Some((target_id, mut target)) = targets.pop() { if let Some((target_id, mut target)) = targets.pop() {
let next_id = if let Some(ref mbid) = artifact.mbid { let next_id = if let Some(mbid) = artifact.mbid {
// If the new artifact has an mbid, we start using that as the archive key // If the new artifact has an mbid, we start using that as the archive key
mbid.clone() mbid
} else { } else {
// Otherwise, why regenerate a new one? // Otherwise, why regenerate a new one?
target_id target_id
@@ -178,11 +178,11 @@ impl Archive {
} }
target.merge(artifact); target.merge(artifact);
// Re-insert the merged target back into the archive under the chosen id // Re-insert the merged target back into the archive under the chosen id
self.contents.insert(next_id.clone(), target); self.contents.insert(next_id, target);
ArtifactRef { id: next_id, archive: self } ArtifactRef { id: next_id, archive: self }
} else { } else {
let new_id = Uuid::new_v4(); let new_id = Uuid::new_v4();
self.contents.insert(new_id.clone(), artifact); self.contents.insert(new_id, artifact);
ArtifactRef { id: new_id, archive: self } ArtifactRef { id: new_id, archive: self }
} }
} }
+10 -10
View File
@@ -9,21 +9,21 @@ pub struct BandcampQueryArgs {
pub query: String pub query: String
} }
impl Into<Artifact> for bandcamp::Artist { impl From<bandcamp::Artist> for Artifact {
fn into(self) -> Artifact { fn from(val: bandcamp::Artist) -> Self {
ArtifactBuilder::new(SourceID::Bandcamp).contents(Artist { name: self.name, bio: self.bio, location: self.location }).build() ArtifactBuilder::new(SourceID::Bandcamp).contents(Artist { name: val.name, bio: val.bio, location: val.location }).build()
} }
} }
impl Into<Artifact> for bandcamp::Album { impl From<bandcamp::Album> for Artifact {
fn into(self) -> Artifact { fn from(val: bandcamp::Album) -> Self {
ArtifactBuilder::new(SourceID::Bandcamp) ArtifactBuilder::new(SourceID::Bandcamp)
.contents(Album { .contents(Album {
about: self.about, about: val.about,
title: self.title, title: val.title,
artist: self.band.name, artist: val.band.name,
credits: self.credits, credits: val.credits,
release_date: Some(self.release_date) release_date: Some(val.release_date)
}).build() }).build()
} }
} }
+10 -14
View File
@@ -35,20 +35,20 @@ struct BeetsTrack {
mb_trackid: Option<String> mb_trackid: Option<String>
} }
impl Into<Artifact> for BeetsTrack { impl From<BeetsTrack> for Artifact {
fn into(self) -> Artifact { fn from(val: BeetsTrack) -> Self {
let track_data = Track { let track_data = Track {
title: self.title, title: val.title,
label: self.label, label: val.label,
year: Some(self.year), year: Some(val.year),
genres: self.genres.unwrap_or_default(), genres: val.genres.unwrap_or_default(),
album: Some(self.album), album: Some(val.album),
artist: Some(self.artist), artist: Some(val.artist),
bpm: None, bpm: None,
}; };
let builder = ArtifactBuilder::new(SourceID::Beets) let builder = ArtifactBuilder::new(SourceID::Beets)
.contents(track_data); .contents(track_data);
if let Ok(mbid) = Uuid::parse_str(&self.mb_trackid.unwrap_or_default()) { if let Ok(mbid) = Uuid::parse_str(&val.mb_trackid.unwrap_or_default()) {
builder.mbid(mbid).build() builder.mbid(mbid).build()
} else { } else {
builder.build() builder.build()
@@ -136,8 +136,7 @@ impl DataSource for BeetsDB {
type Error = BeetsError; type Error = BeetsError;
async fn synchronize(&self, artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> { async fn synchronize(&self, artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> {
match artifact.contents { if let Contents::Track(ref mut target_track) = artifact.contents {
Contents::Track(ref mut target_track) => {
let args = BeatsQueryArgs { let args = BeatsQueryArgs {
title: Some(target_track.title.clone()), title: Some(target_track.title.clone()),
artist: target_track.artist.clone(), artist: target_track.artist.clone(),
@@ -152,9 +151,6 @@ impl DataSource for BeetsDB {
} else { } else {
log::debug!("Beets could not find {:?}", target_track); log::debug!("Beets could not find {:?}", target_track);
} }
},
_ => ()
} }
Ok(vec![]) Ok(vec![])
-12
View File
@@ -37,10 +37,6 @@ impl PartialEq for Artist {
true true
} }
fn ne(&self, other: &Self) -> bool {
!self.eq(other)
}
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
@@ -63,10 +59,6 @@ impl PartialEq for Album {
true true
} }
fn ne(&self, other: &Self) -> bool {
!self.eq(other)
}
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
@@ -103,10 +95,6 @@ impl PartialEq for Track {
true true
} }
fn ne(&self, other: &Self) -> bool {
!self.eq(other)
}
} }
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
+7 -23
View File
@@ -11,17 +11,8 @@ use crate::artifacts::{Album, Artifact, ArtifactBuilder, Artist, Contents, Merge
impl From<Recording> for Track { impl From<Recording> for Track {
fn from(value: Recording) -> Self { fn from(value: Recording) -> Self {
let artist = if let Some(artist) = value.artist_credit.unwrap_or_default().first() { let artist = value.artist_credit.unwrap_or_default().first().map(|x| x.name.clone() );
Some(artist.name.clone()) let album = value.releases.unwrap_or_default().first().map(|x| x.title.clone() );
} else {
None
};
let album = if let Some(album) = value.releases.unwrap_or_default().first() {
Some(album.title.clone())
} else {
None
};
Self { Self {
title: value.title, title: value.title,
@@ -34,16 +25,12 @@ impl From<Recording> for Track {
impl From<Release> for Album { impl From<Release> for Album {
fn from(value: Release) -> Self { fn from(value: Release) -> Self {
let artist = if let Some(artist) = value.artist_credit.unwrap_or_default().first() { let artist = value.artist_credit.unwrap_or_default().first().map(|x| x.name.clone() );
Some(artist.name.clone())
} else {
None
}.unwrap_or_default();
Self { Self {
about: value.annotation, about: value.annotation,
title: value.title, title: value.title,
artist, artist: artist.unwrap_or_default(),
..Default::default() ..Default::default()
} }
} }
@@ -55,7 +42,6 @@ impl From<ArtistCredit> for Artist {
bio: value.artist.annotation, bio: value.artist.annotation,
location: value.artist.country, location: value.artist.country,
name: value.name, name: value.name,
..Default::default()
} }
} }
} }
@@ -98,10 +84,10 @@ impl DataSource for MBQuery {
if artifact.mbid.is_none() { if artifact.mbid.is_none() {
return Ok(ret); return Ok(ret);
} }
let artifact_id = artifact.mbid.clone().unwrap(); let artifact_id = artifact.mbid.unwrap();
log::debug!("Synchronizing {} with musicbrainz", artifact_id); log::debug!("Synchronizing {} with musicbrainz", artifact_id);
match artifact.contents { // FIXME: Need to also synchronize albums and artists
Contents::Track(_) => { if let Contents::Track(_) = artifact.contents {
let mb_track = Recording::fetch() let mb_track = Recording::fetch()
.id(&artifact_id.to_string()) .id(&artifact_id.to_string())
.with_releases().with_artists().with_annotations().execute_async().await; .with_releases().with_artists().with_annotations().execute_async().await;
@@ -119,8 +105,6 @@ impl DataSource for MBQuery {
ret.push(track.clone()); ret.push(track.clone());
ret.append(&mut new_artifacts); ret.append(&mut new_artifacts);
artifact.merge(track); artifact.merge(track);
},
_ => ()
} }
Ok(ret) Ok(ret)
+8 -8
View File
@@ -32,19 +32,19 @@ impl Tool {
} }
} }
impl Into<ChatCompletionTool> for Tool { impl From<Tool> for ChatCompletionTool {
fn into(self) -> ChatCompletionTool { fn from(val: Tool) -> Self {
ChatCompletionTool { ChatCompletionTool {
function: FunctionObjectArgs::default() function: FunctionObjectArgs::default()
.name(self.name) .name(val.name)
.description(self.description) .description(val.description)
.parameters(self.schema).build().unwrap() .parameters(val.schema).build().unwrap()
} }
} }
} }
impl Into<ChatCompletionTools> for Tool { impl From<Tool> for ChatCompletionTools {
fn into(self) -> ChatCompletionTools { fn from(val: Tool) -> Self {
ChatCompletionTools::Function(self.into()) ChatCompletionTools::Function(val.into())
} }
} }
+1 -1
View File
@@ -78,7 +78,7 @@ impl<T: std::io::Write + Send + Sync> log::Log for SysMessageLogger<T> {
fn log(&self, record: &log::Record) { fn log(&self, record: &log::Record) {
let msg = format!("{}", record.args()); let msg = format!("{}", record.args());
write!(self.1.lock().unwrap(), "{}\n", msg).unwrap(); writeln!(self.1.lock().unwrap(), "{}", msg).unwrap();
if record.level() <= LevelFilter::Info { if record.level() <= LevelFilter::Info {
self.0.send(ConversationEntry::SystemMessage(msg)).unwrap(); self.0.send(ConversationEntry::SystemMessage(msg)).unwrap();
} }
+14 -9
View File
@@ -48,7 +48,7 @@ pub struct Character {
} }
impl Character { impl Character {
pub async fn regenerate<'a, T: Toolbox>(&mut self, client: &mut Client<OpenAIConfig>, context: ChatCompletionRequestMessage, toolbox: &mut T, output: &mut mpsc::UnboundedSender<PredictionAction>, schema: &Value) -> Result<(usize, Option<Value>), CharacterError> { pub async fn regenerate<T: Toolbox>(&mut self, client: &mut Client<OpenAIConfig>, context: ChatCompletionRequestMessage, toolbox: &mut T, output: &mut mpsc::UnboundedSender<PredictionAction>, schema: &Value) -> Result<(usize, Option<Value>), CharacterError> {
let mut full_conversation = vec![ let mut full_conversation = vec![
self.header_message.clone(), self.header_message.clone(),
context context
@@ -106,15 +106,21 @@ impl Character {
ChatCompletionMessageToolCalls::Function(call) => { ChatCompletionMessageToolCalls::Function(call) => {
log::debug!("Tool {} {}/{}", call.function.name, idx+1, calls.len()); log::debug!("Tool {} {}/{}", call.function.name, idx+1, calls.len());
log::debug!("Args {}", call.function.arguments); log::debug!("Args {}", call.function.arguments);
if let Some(tool_result) = toolbox.execute_tool(call).await { match toolbox.execute_tool(call).await {
Ok(tool_result) => {
// Push tool output messages directly into the conversation as fast as we can // Push tool output messages directly into the conversation as fast as we can
for message in &tool_result.messages { for message in &tool_result.messages {
output.send(PredictionAction::ConversationAppend(message.clone())).unwrap(); output.send(PredictionAction::ConversationAppend(message.clone())).unwrap();
} }
results.push((&call.id, tool_result)); results.push((&call.id, tool_result));
} else { },
results.push((&call.id, ToolResults::default())); Err(err) => {
log::error!("Attemped to call {:?}, but no result was returned.", call); results.push((&call.id, ToolResults {
result: Some(format!("Error while calling tool: {:?}", err)),
..Default::default()
}));
log::error!("Attemped to call {:?}, but got an error instead: {:?}", call, err);
}
} }
}, },
_ => panic!("Unknown tool was called") _ => panic!("Unknown tool was called")
@@ -141,13 +147,12 @@ impl Character {
} }
if let Some(content) = message.message.content.as_ref() { if let Some(content) = message.message.content.as_ref() {
let options = serde_json::from_str(content.as_str())?; let options = serde_json::from_str(content.as_str())?;
return Ok((tokens_used as usize, Some(options))); Ok((tokens_used as usize, Some(options)))
} else { } else {
return Ok((tokens_used as usize, None)); Ok((tokens_used as usize, None))
} }
} else { } else {
log::info!("No messages were received!"); Err(CharacterError::NoOutput)
return Err(CharacterError::NoOutput);
} }
} }
+2 -6
View File
@@ -164,7 +164,7 @@ impl Conversation {
self.insert(ConversationEntry::Spoken(Speaker::ShipComputer, response.message)).await; self.insert(ConversationEntry::Spoken(Speaker::ShipComputer, response.message)).await;
if response.finished.unwrap_or_default() { if response.finished.unwrap_or_default() {
self.characters.get_mut(&Speaker::ShipComputer).unwrap().forget().await; self.characters.get_mut(&Speaker::ShipComputer).unwrap().forget().await;
if self.computer_todo.lock().await.iter().filter(|(_, is_finished)| !*is_finished).next().is_none() { if !self.computer_todo.lock().await.iter().any(|(_, is_finished)| !*is_finished) {
self.insert(ConversationEntry::StageDirection("The ship computer goes idle.".into())).await; self.insert(ConversationEntry::StageDirection("The ship computer goes idle.".into())).await;
self.event_sink.send(SessionUpdate::Thinking(Speaker::ShipComputer, false)).unwrap(); self.event_sink.send(SessionUpdate::Thinking(Speaker::ShipComputer, false)).unwrap();
} else { } else {
@@ -290,11 +290,7 @@ pub async fn conversation_task(save_data: SaveData, sys_log_messages: tokio::syn
}; };
let backlog: Vec<_> = save_data.messages.iter().filter_map(|msg| { let backlog: Vec<_> = save_data.messages.iter().filter_map(|msg| {
if let Ok(entry) = msg.clone().try_into() { msg.clone().try_into().ok()
Some(entry)
} else {
None
}
}).collect(); }).collect();
event_sink.send(SessionUpdate::Conversation(backlog.clone())).unwrap(); event_sink.send(SessionUpdate::Conversation(backlog.clone())).unwrap();
+35 -23
View File
@@ -5,11 +5,11 @@ use schemars::{JsonSchema, schema_for};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{artifacts::{archive::Archive, bandcamp::BandcampSource, beets::BeetsDB, mixxx::MixxxDB, musicbrainz::MBQuery, tools::{DataSource, Tool}}, scene::conversation::{ConversationEntry, Speaker}}; use crate::{artifacts::{archive::Archive, beets::BeetsDB, mixxx::MixxxDB, musicbrainz::MBQuery, tools::{DataSource, Tool}}, scene::conversation::{ConversationEntry, Speaker}};
pub trait Toolbox { pub trait Toolbox {
fn tools(&self) -> Vec<ChatCompletionTools>; fn tools(&self) -> Vec<ChatCompletionTools>;
fn execute_tool(&mut self, call: &ChatCompletionMessageToolCall) -> impl Future<Output = Option<ToolResults>> + Send; fn execute_tool(&mut self, call: &ChatCompletionMessageToolCall) -> impl Future<Output = Result<ToolResults, ToolError>> + Send;
} }
pub struct StageToolbox; pub struct StageToolbox;
@@ -27,6 +27,18 @@ impl StageToolbox {
} }
} }
#[derive(Debug)]
pub enum ToolError {
InvalidToolName,
Json(serde_json::Error)
}
impl From<serde_json::Error> for ToolError {
fn from(value: serde_json::Error) -> Self {
Self::Json(value)
}
}
impl Toolbox for StageToolbox { impl Toolbox for StageToolbox {
fn tools(&self) -> Vec<ChatCompletionTools> { fn tools(&self) -> Vec<ChatCompletionTools> {
vec![ vec![
@@ -34,13 +46,13 @@ impl Toolbox for StageToolbox {
] ]
} }
async fn execute_tool(&mut self, call: &ChatCompletionMessageToolCall) -> Option<ToolResults> { async fn execute_tool(&mut self, call: &ChatCompletionMessageToolCall) -> Result<ToolResults, ToolError> {
let func_name = call.function.name.as_str(); let func_name = call.function.name.as_str();
let args = call.function.arguments.as_str(); let args = call.function.arguments.as_str();
Some(match func_name { match func_name {
"log_stage_event" => self.tool_stage_event(serde_json::from_str(args).unwrap()).await, "log_stage_event" => Ok(self.tool_stage_event(serde_json::from_str(args)?).await),
_ => return None _ => Err(ToolError::InvalidToolName)
}) }
} }
} }
@@ -82,24 +94,24 @@ impl Toolbox for ArchiveToolbox {
] ]
} }
async fn execute_tool(&mut self, call: &ChatCompletionMessageToolCall) -> Option<ToolResults> { async fn execute_tool(&mut self, call: &ChatCompletionMessageToolCall) -> Result<ToolResults, ToolError> {
let func_name = call.function.name.as_str(); let func_name = call.function.name.as_str();
let args = call.function.arguments.as_str(); let args = call.function.arguments.as_str();
Some(match func_name { match func_name {
"query_bandcamp" => ToolResults { result: None, messages: vec![] }, "query_bandcamp" => Ok(ToolResults { result: None, messages: vec![] }),
"query_beets" => self.tool_artifact_query(&mut BeetsDB, args).await, "query_beets" => self.tool_artifact_query(&mut BeetsDB, args).await,
"query_musicbrainz" => self.tool_artifact_query(&mut MBQuery, args).await, "query_musicbrainz" => self.tool_artifact_query(&mut MBQuery, args).await,
"query_mixxx" => self.tool_artifact_query(&mut MixxxDB, args).await, "query_mixxx" => self.tool_artifact_query(&mut MixxxDB, args).await,
"synchronize_artifacts" => self.synchronize_artifacts().await, "synchronize_artifacts" => self.synchronize_artifacts().await,
"task_list" => self.tasklist_operation(args).await, "task_list" => self.tasklist_operation(args).await,
_ => return None _ => Err(ToolError::InvalidToolName)
}) }
} }
} }
impl ArchiveToolbox { impl ArchiveToolbox {
async fn tasklist_operation(&mut self, json_args: &str) -> ToolResults { async fn tasklist_operation(&mut self, json_args: &str) -> Result<ToolResults, ToolError> {
let args: TaskListArgs = serde_json::from_str(json_args).unwrap_or_default(); let args: TaskListArgs = serde_json::from_str(json_args)?;
let mut locked = self.todo_list.lock().await; let mut locked = self.todo_list.lock().await;
@@ -117,22 +129,22 @@ impl ArchiveToolbox {
} }
} }
ToolResults { Ok(ToolResults {
..Default::default() ..Default::default()
} })
} }
async fn synchronize_artifacts(&mut self) -> ToolResults { async fn synchronize_artifacts(&mut self) -> Result<ToolResults, ToolError> {
let updated_count = self.archive.lock().await.synchronize().await; let updated_count = self.archive.lock().await.synchronize().await;
ToolResults { Ok(ToolResults {
messages: vec![ConversationEntry::Spoken(Speaker::ShipComputer, format!("Synchronized {} items", updated_count))], messages: vec![ConversationEntry::Spoken(Speaker::ShipComputer, format!("Synchronized {} items", updated_count))],
..Default::default() ..Default::default()
} })
} }
async fn tool_artifact_query<Src: DataSource>(&mut self, src: &mut Src, json_args: &str) -> ToolResults where Src::Args: core::fmt::Debug, Src::Error: core::fmt::Debug { async fn tool_artifact_query<Src: DataSource>(&mut self, src: &mut Src, json_args: &str) -> Result<ToolResults, ToolError> where Src::Args: core::fmt::Debug, Src::Error: core::fmt::Debug {
let args: Src::Args = serde_json::from_str(json_args).unwrap(); let args: Src::Args = serde_json::from_str(json_args)?;
log::debug!("Executing query {:?}", args); log::debug!("Executing query {:?}", args);
let result; let result;
match src.query(&args).await { match src.query(&args).await {
@@ -147,10 +159,10 @@ impl ArchiveToolbox {
} }
} }
ToolResults { Ok(ToolResults {
result: Some(result), result: Some(result),
messages: vec![] messages: vec![]
} })
} }
} }
+6 -10
View File
@@ -91,7 +91,7 @@ impl Ui {
let throbber = Throbber::default().throbber_style(Style::new().cyan()); let throbber = Throbber::default().throbber_style(Style::new().cyan());
frame.render_stateful_widget(throbber, throb_area, &mut self.throbber_state); frame.render_stateful_widget(throbber, throb_area, &mut self.throbber_state);
} else { } else {
frame.render_widget(Clear::default(), throb_area); frame.render_widget(Clear, throb_area);
} }
} }
@@ -101,7 +101,7 @@ impl Ui {
let throbber = Throbber::default().throbber_style(Style::new().red()).throbber_set(throbber_widgets_tui::WHITE_SQUARE); let throbber = Throbber::default().throbber_style(Style::new().red()).throbber_set(throbber_widgets_tui::WHITE_SQUARE);
frame.render_stateful_widget(throbber, throb_area, &mut self.throbber_state); frame.render_stateful_widget(throbber, throb_area, &mut self.throbber_state);
} else { } else {
frame.render_widget(Clear::default(), throb_area); frame.render_widget(Clear, throb_area);
} }
} }
@@ -170,7 +170,6 @@ impl Ui {
self.predictions.insert(PredictionAction::SetPlaylist(playlist_name)).await; self.predictions.insert(PredictionAction::SetPlaylist(playlist_name)).await;
} else { } else {
log::error!("Invalid episode number format. Use /episode [number]"); log::error!("Invalid episode number format. Use /episode [number]");
return;
} }
}, },
"/playlist" => { "/playlist" => {
@@ -193,7 +192,6 @@ impl Ui {
}, },
"/computer" => { "/computer" => {
self.predictions.insert(PredictionAction::ComputerCommand(arg.to_string())).await; self.predictions.insert(PredictionAction::ComputerCommand(arg.to_string())).await;
return;
}, },
_ => { _ => {
log::error!("Unknown command. Available commands: /episode [number], /narrative [text], /event [text], /computer [text], /timer [minutes]"); log::error!("Unknown command. Available commands: /episode [number], /narrative [text], /event [text], /computer [text], /timer [minutes]");
@@ -221,7 +219,7 @@ impl Ui {
KeyCode::Enter => { KeyCode::Enter => {
let row_num = self.conversation_state.selected().unwrap(); let row_num = self.conversation_state.selected().unwrap();
if let ConversationEntry::Spoken(Speaker::Eva, text) = &self.conversation[self.conversation.len() - 1 - row_num] { if let ConversationEntry::Spoken(Speaker::Eva, text) = &self.conversation[self.conversation.len() - 1 - row_num] {
self.tts.speak(text.clone()).await; self.tts.speak(text.clone()).await.unwrap();
self.focus_state = FocusState::UserInput; self.focus_state = FocusState::UserInput;
self.conversation_state.select(None); self.conversation_state.select(None);
self.reply_state.select_first(); self.reply_state.select_first();
@@ -241,10 +239,10 @@ impl Ui {
KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => { KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
if self.recording_audio { if self.recording_audio {
self.recording_audio = false; self.recording_audio = false;
self.transcription.stop(); self.transcription.stop().unwrap();
} else { } else {
self.recording_audio = true; self.recording_audio = true;
self.transcription.start(); self.transcription.start().unwrap();
} }
}, },
KeyCode::Down => self.reply_state.select_next(), KeyCode::Down => self.reply_state.select_next(),
@@ -256,13 +254,11 @@ impl Ui {
let next_msg = self.user_input.value_and_reset(); let next_msg = self.user_input.value_and_reset();
if next_msg.trim().is_empty() { if next_msg.trim().is_empty() {
self.insert_selected_prompt().await; self.insert_selected_prompt().await;
} else { } else if next_msg.starts_with("/") {
if next_msg.starts_with("/") {
self.on_command(&next_msg).await; self.on_command(&next_msg).await;
} else { } else {
self.predictions.insert(PredictionAction::ConversationAppend(ConversationEntry::Spoken(Speaker::User, next_msg))).await; self.predictions.insert(PredictionAction::ConversationAppend(ConversationEntry::Spoken(Speaker::User, next_msg))).await;
} }
}
}, },
_ => {self.user_input.handle_event(&evt);}, _ => {self.user_input.handle_event(&evt);},
} }
+1 -1
View File
@@ -182,7 +182,7 @@ impl Widget for StatusBar<'_> {
let negative = time_remaining.abs() != time_remaining; let negative = time_remaining.abs() != time_remaining;
let time_style = if minutes_remaining <= 0 || negative { let time_style = if minutes_remaining <= 0 || negative {
Style::new().fg(ratatui::style::Color::LightRed).bold() Style::new().fg(ratatui::style::Color::LightRed).underlined()
} else if minutes_remaining < 5 { } else if minutes_remaining < 5 {
Style::new().fg(ratatui::style::Color::LightRed).bold() Style::new().fg(ratatui::style::Color::LightRed).bold()
} else if minutes_remaining < 10 { } else if minutes_remaining < 10 {