Codex Handbook
tui/src/app/pets.rs 197 lines
//! App-level handlers for ambient terminal pet events.use super::*;impl App {    pub(super) fn disable_ambient_pet_before_shutdown(&mut self, tui: &mut tui::Tui) -> Result<()> {        self.chat_widget.disable_ambient_pet_for_session();        if let Err(clear_err) = tui.clear_ambient_pet_image() {            match clear_err {                crate::pets::PetImageRenderError::Terminal(err) => return Err(err.into()),                crate::pets::PetImageRenderError::Asset(err) => {                    tracing::warn!(                        error = %err,                        "failed to clear ambient pet image before shutdown feedback"                    );                }            }        }        Ok(())    }    pub(super) fn handle_ambient_pet_image_render_error(        &mut self,        tui: &mut tui::Tui,        err: crate::pets::PetImageRenderError,    ) -> Result<()> {        match err {            crate::pets::PetImageRenderError::Terminal(err) => Err(err.into()),            crate::pets::PetImageRenderError::Asset(err) => {                tracing::warn!(                    error = %err,                    "failed to render ambient pet image; disabling pet for session"                );                self.chat_widget.disable_ambient_pet_for_session();                if let Err(clear_err) = tui.clear_ambient_pet_image() {                    match clear_err {                        crate::pets::PetImageRenderError::Terminal(err) => return Err(err.into()),                        crate::pets::PetImageRenderError::Asset(err) => {                            tracing::warn!(                                error = %err,                                "failed to clear ambient pet image after render failure"                            );                        }                    }                }                Ok(())            }        }    }    pub(super) fn handle_pet_picker_preview_image_render_error(        &mut self,        tui: &mut tui::Tui,        err: crate::pets::PetImageRenderError,    ) -> Result<()> {        match err {            crate::pets::PetImageRenderError::Terminal(err) => Err(err.into()),            crate::pets::PetImageRenderError::Asset(err) => {                tracing::warn!(error = %err, "failed to render pet picker preview image");                self.chat_widget                    .fail_pet_picker_preview_render(err.to_string());                if let Err(clear_err) = tui.draw_pet_picker_preview_image(/*request*/ None) {                    match clear_err {                        crate::pets::PetImageRenderError::Terminal(err) => return Err(err.into()),                        crate::pets::PetImageRenderError::Asset(err) => {                            tracing::warn!(                                error = %err,                                "failed to clear pet picker preview image after render failure"                            );                        }                    }                }                Ok(())            }        }    }    pub(super) fn handle_pet_selected(&mut self, tui: &mut tui::Tui, pet_id: String) {        let request_id = self.chat_widget.show_pet_selection_loading_popup();        tui.frame_requester().schedule_frame();        let codex_home = self.config.codex_home.clone();        let frame_requester = tui.frame_requester();        let animations_enabled = self.config.animations;        let tx = self.app_event_tx.clone();        std::mem::drop(tokio::task::spawn_blocking(move || {            let result = crate::pets::ensure_builtin_pack_for_pet(&pet_id, &codex_home)                .and_then(|()| {                    crate::pets::AmbientPet::load(                        Some(&pet_id),                        &codex_home,                        frame_requester,                        animations_enabled,                    )                })                .map(Some)                .map_err(|err| err.to_string());            tx.send(AppEvent::PetSelectionLoaded {                request_id,                pet_id,                result,            });        }));    }    pub(super) async fn handle_pet_disabled(&mut self, tui: &mut tui::Tui) {        let edit = crate::legacy_core::config::edit::tui_pet_edit(crate::pets::DISABLED_PET_ID);        let apply_result = ConfigEditsBuilder::new(&self.config.codex_home)            .with_edits([edit])            .apply()            .await;        match apply_result {            Ok(()) => {                self.sync_tui_pet_disabled();                tui.frame_requester().schedule_frame();            }            Err(err) => {                self.chat_widget                    .add_error_message(format!("Failed to disable pets: {err}"));            }        }    }    pub(super) fn handle_pet_preview_loaded(        &mut self,        tui: &mut tui::Tui,        request_id: u64,        result: Result<crate::pets::AmbientPet, String>,    ) {        self.chat_widget            .finish_pet_picker_preview_load(request_id, result);        tui.frame_requester().schedule_frame();    }    pub(super) async fn handle_pet_selection_loaded(        &mut self,        tui: &mut tui::Tui,        request_id: u64,        pet_id: String,        result: Result<Option<crate::pets::AmbientPet>, String>,    ) -> Result<AppRunControl> {        if !self            .chat_widget            .finish_pet_selection_loading_popup(request_id)        {            return Ok(AppRunControl::Continue);        }        match result {            Ok(ambient_pet) => {                let edit = crate::legacy_core::config::edit::tui_pet_edit(&pet_id);                match ConfigEditsBuilder::new(&self.config.codex_home)                    .with_edits([edit])                    .apply()                    .await                {                    Ok(()) => {                        self.config.tui_pet = Some(pet_id.clone());                        self.chat_widget                            .set_tui_pet_loaded(Some(pet_id), ambient_pet);                    }                    Err(err) => {                        self.chat_widget                            .add_error_message(format!("Failed to save pet selection: {err}"));                    }                }            }            Err(err) => {                self.chat_widget                    .add_error_message(format!("Failed to load pet: {err}"));            }        }        tui.frame_requester().schedule_frame();        Ok(AppRunControl::Continue)    }    pub(super) fn handle_configured_pet_loaded(        &mut self,        tui: &mut tui::Tui,        pet_id: String,        result: Result<Option<crate::pets::AmbientPet>, String>,    ) {        if self.config.tui_pet.as_deref() != Some(pet_id.as_str()) {            return;        }        match result {            Ok(ambient_pet) => {                self.chat_widget                    .set_tui_pet_loaded(Some(pet_id), ambient_pet);                tui.frame_requester().schedule_frame();            }            Err(err) => {                self.chat_widget                    .add_warning_message(format!("Failed to load configured pet: {err}"));            }        }    }}