Enhance state direction command with ship computer outputs, and report token burn on the UI

This commit is contained in:
2026-06-09 09:04:03 +02:00
parent 114f1ea4df
commit 88e1f2a62b
3 changed files with 105 additions and 100 deletions
+13 -53
View File
@@ -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::{BandcampResult, PossibleResponse}, scene::{ConversationEntry, Scene, StageActions, StageDirection}, transcription::TranscriptionControl, tts::{TtsControl, start_tts}};
use crate::{audio::{AudioInputControl, start_audio_input}, prediction::{BandcampResult, PossibleResponse}, scene::{ConversationEntry, Scene, Scenery, StageActions, StageDirection}, transcription::TranscriptionControl, tts::{TtsControl, start_tts}};
mod scene;
mod events;
@@ -94,24 +94,6 @@ enum FocusState {
UserInput
}
#[derive(Debug)]
enum BandcampError {
Json(serde_json::Error),
Api(bandcamp::Error)
}
impl From<serde_json::Error> for BandcampError {
fn from(value: serde_json::Error) -> Self {
BandcampError::Json(value)
}
}
impl From<bandcamp::Error> for BandcampError {
fn from(value: bandcamp::Error) -> Self {
BandcampError::Api(value)
}
}
impl App {
fn new(prediction_request_sink: watch::Sender<StageActions>, audio: AudioInputControl, transcription: TranscriptionControl, tts: TtsControl, sys_message_sink: mpsc::Sender<String>, initial_direction: StageDirection) -> Self {
Self {
@@ -292,6 +274,9 @@ impl App {
Span::from(" | ").style(ratatui::style::Color::DarkGray),
Span::from(format!("Time Remaining: {}", formatted_time)).style(time_style),
Span::from(" | ").style(ratatui::style::Color::DarkGray),
Span::from(format!("{} artifacts recorded", self.scene.scenery().artifacts.len())).style(ratatui::style::Color::LightBlue),
Span::from(" | ").style(ratatui::style::Color::DarkGray),
Span::from(format!("{} tokens sacrificed", self.scene.tokens_consumed)).style(ratatui::style::Color::LightCyan),
]);
frame.render_widget(status_line, area);
}
@@ -411,15 +396,7 @@ impl App {
let command = parts.next().unwrap();
let arg = parts.next().unwrap_or("");
match command {
"/bandcamp" => {
if let Err(err) = self.add_bandcamp_artifact(arg).await {
self.sys_message_sink.send(format!("Failed to fetch artifact: {:?}", err)).await.unwrap();
} else {
self.sys_message_sink.send(format!("Added Bandcamp artifact from {}", arg)).await.unwrap();
self.next_actions.push(ConversationEntry::ShipComputer(format!("Incoming transmission from {}", arg)));
self.regenerate_responses();
}
},
// FIXME: Need some new kind of /bandcamp command to force loading of specific urls
"/episode" => {
if let Ok(episode_number) = arg.trim().parse::<u32>() {
self.direction.episode_number = episode_number;
@@ -438,21 +415,10 @@ impl App {
self.sys_message_sink.send("Invalid timer format. Use /timer [minutes]".into()).await.unwrap();
}
},
"/clear" => {
match arg.trim() {
"artifacts" => {
self.direction.artifacts.clear();
self.sys_message_sink.send("Cleared artifacts.".into()).await.unwrap();
},
_ => {
self.sys_message_sink.send("Unknown clear command. Use /clear [artifacts]".into()).await.unwrap();
}
}
return;
},
"/narrative" => {
self.direction.narrative = arg.to_string();
self.sys_message_sink.send(format!("Updated stage direction: {}", self.direction.narrative)).await.unwrap();
self.regenerate_responses();
},
"/event" => {
self.next_actions.push(ConversationEntry::StageDirection(arg.to_string()));
@@ -463,7 +429,7 @@ impl App {
self.regenerate_responses();
},
_ => {
self.sys_message_sink.send("Unknown command. Available commands: /bandcamp [url], /episode [number], /narrative [text], /reset".into()).await.unwrap();
self.sys_message_sink.send("Unknown command. Available commands: /episode [number], /narrative [text], /event [text], /computer [text], /timer [minutes]".into()).await.unwrap();
}
}
}
@@ -476,6 +442,7 @@ impl App {
KeyCode::Tab => {
self.focus_state = FocusState::UserInput;
self.conversation_state.select(None);
self.reply_state.select_first();
},
KeyCode::PageUp => self.conversation_state.scroll_down_by(5),
KeyCode::PageDown => self.conversation_state.scroll_up_by(5),
@@ -489,6 +456,7 @@ impl App {
self.tts.speak(text.clone()).await;
self.focus_state = FocusState::UserInput;
self.conversation_state.select(None);
self.reply_state.select_first();
}
},
_ => ()
@@ -499,6 +467,7 @@ impl App {
KeyCode::Tab => {
self.focus_state = FocusState::Conversation;
self.conversation_state.select_first();
self.reply_state.select(None);
},
KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => self.regenerate_responses(),
KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
@@ -536,16 +505,6 @@ impl App {
}
}
async fn add_bandcamp_artifact(&mut self, url: &str) -> Result<(), BandcampError> {
let album = bandcamp::album_from_url(url).await?;
let result: BandcampResult = album.into();
let json = serde_json::to_string(&result)?;
self.direction.artifacts.push(json);
self.sys_message_sink.send("Added bandcamp album".into()).await.unwrap();
Ok(())
}
fn regenerate_responses(&mut self) {
let actions = StageActions {
direction: self.direction.clone(),
@@ -570,7 +529,8 @@ impl App {
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct SaveData {
pub direction: StageDirection,
pub messages: Vec<ChatCompletionRequestMessage>
pub messages: Vec<ChatCompletionRequestMessage>,
pub scenery: Scenery
}
impl SaveData {
@@ -619,7 +579,7 @@ async fn main() {
let (audio_ctrl, mic_stream, tts_output) = start_audio_input(&sys_message_sink).await;
let tts_ctrl = start_tts(tts_output).await;
let (prediction_request_in, mut prediction_out) = prediction::start_prediction(sys_message_src, saved_session.messages).await;
let (prediction_request_in, mut prediction_out) = prediction::start_prediction(sys_message_src, saved_session.messages, saved_session.scenery).await;
let transcription_ctrl = transcription::start_transcription(mic_stream).await;
let mut app = App::new(prediction_request_in, audio_ctrl, transcription_ctrl, tts_ctrl, sys_message_sink, saved_session.direction);