src: split out conversation and archive bits into separate modules

This commit is contained in:
2026-06-09 19:30:40 +02:00
parent 7f2dd6f8b2
commit eeb27656c7
6 changed files with 105 additions and 82 deletions
+45
View File
@@ -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<ChatCompletionRequestMessage> for ConversationEntry {
fn try_into(self) -> Result<ChatCompletionRequestMessage, Self::Error> {
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<ConversationEntry> for ChatCompletionRequestMessage {
fn try_into(self) -> Result<ConversationEntry, Self::Error> {
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 = ();
}
+128
View File
@@ -0,0 +1,128 @@
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use sqlite::OpenFlags;
use crate::{artifacts::Artifact, prediction::{GeneratedResponses, PossibleResponse}, scene::conversation::ConversationEntry};
pub mod conversation;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct StageDirection {
pub episode_number: u32,
#[serde(skip)]
pub end_time: DateTime<Utc>,
pub narrative: String,
pub current_playlist: Vec<PlaylistEntry>
}
impl StageDirection {
pub fn time_remaining(&self) -> Duration {
self.end_time.signed_duration_since(Utc::now())
}
}
impl Default for StageDirection {
fn default() -> Self {
Self {
episode_number: 0,
end_time: Utc::now() + Duration::hours(2),
narrative: Default::default(),
current_playlist: Default::default(),
}
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct Scenery {
pub artifacts: Vec<Artifact>
}
#[derive(Debug)]
pub enum MixxxError {
Sql(sqlite::Error)
}
impl From<sqlite::Error> for MixxxError {
fn from(value: sqlite::Error) -> Self {
Self::Sql(value)
}
}
impl StageDirection {
pub fn reload_mixxx_playlist(&mut self) -> Result<(), MixxxError> {
self.current_playlist.clear();
let playlist_name = format!("BFF.fm - Episode {}", self.episode_number);
let connection = sqlite::Connection::open_thread_safe_with_flags("mixxxdb.sqlite", OpenFlags::new().with_read_only())?;
let query = "SELECT id FROM Playlists WHERE name = ? ORDER BY id DESC LIMIT 1";
let mut statement = connection.prepare(query)?;
statement.bind((1, playlist_name.as_str()))?;
statement.next()?;
let latest_id = statement.read::<i64, _>("id").unwrap();
let query = "SELECT title, artist, album, comment, url, bpm FROM library LEFT JOIN PlaylistTracks ON PlaylistTracks.track_id = library.id WHERE PlaylistTracks.playlist_id = ? ORDER BY position";
for track in connection.prepare(query).unwrap().into_iter().bind((1, latest_id)).unwrap().map(|row| row.unwrap()) {
let title = track.try_read::<&str, _>("title").unwrap_or("Untitled Track");
let artist = track.try_read::<&str, _>("artist").unwrap_or("Unknown Artist");
let album = track.try_read::<&str, _>("album").unwrap_or("Unknown Album");
let bpm = track.try_read::<f64, _>("bpm").unwrap_or(0.);
self.current_playlist.push(PlaylistEntry {
artist: artist.into(),
album: album.into(),
title: title.into(),
bpm
});
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum PredictionAction {
ConversationAppend(ConversationEntry),
SetEpisodeNumber(u32),
GeneratePredictions,
SetNarrative(String),
SetShowEndTime(DateTime<Utc>)
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct PlaylistEntry {
pub artist: String,
pub album: String,
pub title: String,
pub bpm: f64
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Scene {
reply_options: GeneratedResponses,
conversation: Vec<ConversationEntry>,
pub direction: StageDirection,
pub tokens_consumed: usize,
scenery: Scenery
}
impl Scene {
pub fn new(reply_options: GeneratedResponses, conversation: Vec<ConversationEntry>, scenery: Scenery, tokens_consumed: usize, direction: StageDirection) -> Self {
Self {
reply_options,
conversation,
scenery,
tokens_consumed,
direction
}
}
pub fn scenery(&self) -> &Scenery {
&self.scenery
}
pub fn conversation(&self) -> &Vec<ConversationEntry> {
&self.conversation
}
pub fn reply_options(&self) -> &Vec<PossibleResponse> {
&self.reply_options.responses
}
}