diff --git a/src/archive.rs b/src/archive.rs new file mode 100644 index 0000000..edbf8f1 --- /dev/null +++ b/src/archive.rs @@ -0,0 +1,46 @@ +use std::process::{Command, Stdio}; + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::artifacts::Artifact; + + +#[derive(Debug, Default, Serialize, Deserialize, Clone, JsonSchema)] +pub struct BeatsQueryArgs { + artist: Option, + album: Option, + genre: Option, + title: Option, + year: Option +} + +impl BeatsQueryArgs { + pub fn execute(self) -> Result { + let mut beets_cmd = Command::new("beet"); + beets_cmd.args(["export", "-f", "json", "-i", "title,label,year,genres,album,artist"]); + if let Some(artist) = self.artist { + beets_cmd.arg(format!("artist:{}", artist)); + } + if let Some(genre) = self.genre { + beets_cmd.arg(format!("genre:{}", genre)); + } + if let Some(album) = self.album { + beets_cmd.arg(format!("album:{}", album)); + } + if let Some(title) = self.title { + beets_cmd.arg(format!("title:{}", title)); + } + if let Some(year) = self.year { + beets_cmd.arg(format!("year:{}", year)); + } + + if let Ok(output) = beets_cmd.stdout(Stdio::piped()).spawn().unwrap().wait_with_output() { + //messages.push(ConversationEntry::ShipComputer(format!("Executing archive query {:?}", beets_cmd))); + Ok(Artifact::BeetsTrack(serde_json::from_str(str::from_utf8(&output.stdout).unwrap()).unwrap())) + } else { + Err(()) + //messages.push(ConversationEntry::ShipComputer("Unable to execute query!".into())); + } + } +} \ No newline at end of file diff --git a/src/artifacts.rs b/src/artifacts.rs index dd75e7c..755dd49 100644 --- a/src/artifacts.rs +++ b/src/artifacts.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -42,4 +43,9 @@ impl Into for bandcamp::Artist { fn into(self) -> Artifact { Artifact::Bandcamp(self.into()) } +} + +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +pub struct BandcampQueryArgs { + pub query: String } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c984fd0..8d64328 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use futures::{StreamExt, future::FutureExt}; use ratatui::prelude::*; use tui_skeleton::{AnimationMode, SkeletonText}; -use crate::{audio::{AudioInputControl, start_audio_input}, prediction::{SessionControl, SessionUpdate}, scene::{ConversationEntry, PredictionAction, Scene, Scenery, StageDirection}, transcription::TranscriptionControl, tts::{TtsControl, start_tts}}; +use crate::{audio::{AudioInputControl, start_audio_input}, prediction::{SessionControl, SessionUpdate}, scene::{PredictionAction, Scene, Scenery, StageDirection, conversation::ConversationEntry}, transcription::TranscriptionControl, tts::{TtsControl, start_tts}}; mod scene; mod events; @@ -22,6 +22,7 @@ mod tts; mod prediction; mod audio; mod artifacts; +mod archive; // TODO: We should be able to delete entries from the conversation, or at least go back and edit something I said. // TODO: I want a "mark" command or keyboard shortcut, that inserts a marker into the log, so I know where to come back for the next speaking segment. diff --git a/src/prediction.rs b/src/prediction.rs index 49775e2..c43a7ab 100644 --- a/src/prediction.rs +++ b/src/prediction.rs @@ -1,5 +1,3 @@ -use std::process::{Command, Stdio}; - use async_openai::{Client, config::OpenAIConfig, types::chat::{ChatCompletionMessageToolCalls, ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestToolMessageArgs, ChatCompletionTool, ChatCompletionTools, CreateChatCompletionRequestArgs, FinishReason, FunctionObjectArgs, ResponseFormat, ResponseFormatJsonSchema}}; use bandcamp::SearchResultItem; use schemars::{JsonSchema, schema_for}; @@ -7,7 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Serializer, ser::CompactFormatter}; use tokio::sync::{mpsc, watch}; -use crate::{SaveData, artifacts::{Artifact, BandcampResult}, scene::{ConversationEntry, PredictionAction, Scene, Scenery, StageDirection}}; +use crate::{SaveData, archive::BeatsQueryArgs, artifacts::BandcampQueryArgs, scene::{PredictionAction, Scene, Scenery, StageDirection, conversation::ConversationEntry}}; const SYSTEM_PROMPT: &str = include_str!("system-prompt.txt"); @@ -47,20 +45,6 @@ struct StageEventArgs { event: StageEvent } -#[derive(Debug, Default, Serialize, Deserialize, Clone, JsonSchema)] -struct BeatsQueryArgs { - artist: Option, - album: Option, - genre: Option, - title: Option, - year: Option -} - -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] -struct BandcampQueryArgs { - query: String -} - #[derive(Default, Debug)] struct ToolResults { result: Option, @@ -132,26 +116,9 @@ impl Session { async fn tool_artifact_query(&mut self, args: BeatsQueryArgs) -> ToolResults { let mut messages = vec![]; - let mut beets_cmd = Command::new("beet"); - beets_cmd.args(["export", "-f", "json", "-i", "title,label,year,genres,album,artist"]); - if let Some(artist) = args.artist { - beets_cmd.arg(format!("artist:{}", artist)); - } - if let Some(genre) = args.genre { - beets_cmd.arg(format!("genre:{}", genre)); - } - if let Some(album) = args.album { - beets_cmd.arg(format!("album:{}", album)); - } - if let Some(title) = args.title { - beets_cmd.arg(format!("title:{}", title)); - } - if let Some(year) = args.year { - beets_cmd.arg(format!("year:{}", year)); - } - if let Ok(output) = beets_cmd.stdout(Stdio::piped()).spawn().unwrap().wait_with_output() { - messages.push(ConversationEntry::ShipComputer(format!("Executing archive query {:?}", beets_cmd))); - self.scenery.artifacts.push(Artifact::BeetsTrack(serde_json::from_str(str::from_utf8(&output.stdout).unwrap()).unwrap())); + messages.push(ConversationEntry::ShipComputer(format!("Executing archive query {:?}", args))); + if let Ok(output) = args.execute() { + self.scenery.artifacts.push(output); } else { messages.push(ConversationEntry::ShipComputer("Unable to execute query!".into())); }; diff --git a/src/scene/conversation.rs b/src/scene/conversation.rs new file mode 100644 index 0000000..f03633a --- /dev/null +++ b/src/scene/conversation.rs @@ -0,0 +1,45 @@ +use async_openai::types::chat::{ChatCompletionRequestAssistantMessage, ChatCompletionRequestAssistantMessageContent, ChatCompletionRequestMessage, ChatCompletionRequestSystemMessage, ChatCompletionRequestSystemMessageContent, ChatCompletionRequestUserMessage, ChatCompletionRequestUserMessageContent}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ConversationEntry { + User(String), + Eva(String), + ShipComputer(String), + StageDirection(String), + SystemMessage(String) +} + +impl TryInto for ConversationEntry { + fn try_into(self) -> Result { + match self { + ConversationEntry::User(text) => Ok(ChatCompletionRequestMessage::User(ChatCompletionRequestUserMessage { content: text.into(), ..Default::default()})), + ConversationEntry::Eva(text) => Ok(ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { content: Some(text.into()), ..Default::default()})), + ConversationEntry::ShipComputer(text) => Ok(ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: text.into(), name: Some("ship-computer".into()), ..Default::default() })), + ConversationEntry::StageDirection(text) => Ok(ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: text.into(), name: Some("stage-direction".into()), ..Default::default() })), + _ => Err(()) + } + } + + type Error = (); +} + + +impl TryInto for ChatCompletionRequestMessage { + fn try_into(self) -> Result { + match self { + ChatCompletionRequestMessage::User(ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text(msg), ..}) => Ok(ConversationEntry::User(msg)), + ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { content: Some(ChatCompletionRequestAssistantMessageContent::Text(msg)), ..}) => Ok(ConversationEntry::Eva(msg)), + ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: ChatCompletionRequestSystemMessageContent::Text(msg), name: Some(name), ..}) => { + match name.as_str() { + "ship-computer" => Ok(ConversationEntry::ShipComputer(msg)), + "stage-direction" => Ok(ConversationEntry::StageDirection(msg)), + _ => Err(()) + } + }, + _ => Err(()) + } + } + + type Error = (); +} \ No newline at end of file diff --git a/src/scene.rs b/src/scene/mod.rs similarity index 62% rename from src/scene.rs rename to src/scene/mod.rs index 53a9615..016fb57 100644 --- a/src/scene.rs +++ b/src/scene/mod.rs @@ -1,52 +1,10 @@ -use async_openai::types::chat::*; use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; use sqlite::OpenFlags; -use crate::{artifacts::Artifact, prediction::{GeneratedResponses, PossibleResponse}}; +use crate::{artifacts::Artifact, prediction::{GeneratedResponses, PossibleResponse}, scene::conversation::ConversationEntry}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum ConversationEntry { - User(String), - Eva(String), - ShipComputer(String), - StageDirection(String), - SystemMessage(String) -} - -impl TryInto for ConversationEntry { - fn try_into(self) -> Result { - match self { - ConversationEntry::User(text) => Ok(ChatCompletionRequestMessage::User(ChatCompletionRequestUserMessage { content: text.into(), ..Default::default()})), - ConversationEntry::Eva(text) => Ok(ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { content: Some(text.into()), ..Default::default()})), - ConversationEntry::ShipComputer(text) => Ok(ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: text.into(), name: Some("ship-computer".into()), ..Default::default() })), - ConversationEntry::StageDirection(text) => Ok(ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: text.into(), name: Some("stage-direction".into()), ..Default::default() })), - _ => Err(()) - } - } - - type Error = (); -} - - -impl TryInto for ChatCompletionRequestMessage { - fn try_into(self) -> Result { - match self { - ChatCompletionRequestMessage::User(ChatCompletionRequestUserMessage { content: ChatCompletionRequestUserMessageContent::Text(msg), ..}) => Ok(ConversationEntry::User(msg)), - ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { content: Some(ChatCompletionRequestAssistantMessageContent::Text(msg)), ..}) => Ok(ConversationEntry::Eva(msg)), - ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { content: ChatCompletionRequestSystemMessageContent::Text(msg), name: Some(name), ..}) => { - match name.as_str() { - "ship-computer" => Ok(ConversationEntry::ShipComputer(msg)), - "stage-direction" => Ok(ConversationEntry::StageDirection(msg)), - _ => Err(()) - } - }, - _ => Err(()) - } - } - - type Error = (); -} +pub mod conversation; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct StageDirection {