feat: implement interactive lesson management and update command handling

This commit is contained in:
asepharyana
2026-07-13 02:53:30 +07:00
parent ab8b098a57
commit dc0f1c7647
10 changed files with 288 additions and 209 deletions
+22 -65
View File
@@ -20,7 +20,6 @@ use std::collections::VecDeque;
use crate::app::harness::Verdict;
use sha2::Digest;
use crate::app::mode::ModeKind;
use crate::app::review::{should_trigger_review, trigger_review};
use crate::app::state::rest::{AppStateRest, ChatMessageDisplay};
use crate::app::state::runtime::TurnEvent;
@@ -38,7 +37,6 @@ use crate::dto::chat::message::{ChatMessage, Role};
#[derive(Debug, Clone)]
pub enum Action {
ForceQuit,
SwitchMode(ModeKind),
SubmitInput(String),
DeleteChar,
DeleteCharRight,
@@ -57,18 +55,16 @@ pub enum Action {
QuitConfirm,
Resize(u16, u16),
Tick,
LessonExport {
path: String,
},
LessonImport {
path: String,
},
LessonAccept {
name: String,
},
LessonReject {
name: String,
},
LessonDelete {
name: String,
},
StartOAuth {
provider: String,
},
@@ -106,25 +102,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
state.shutdown_lsp();
state.quit = true;
}
Action::SwitchMode(mode) => {
state.misc.overlay = match mode {
ModeKind::Chat
| ModeKind::Bash => Overlay::None,
ModeKind::Workflow => Overlay::Workflow,
ModeKind::Help => Overlay::Help,
ModeKind::Settings => Overlay::Settings,
ModeKind::QuitConfirm => Overlay::QuitConfirm,
ModeKind::KeyInput => Overlay::KeyInput,
ModeKind::Editor => Overlay::Editor,
ModeKind::Effort => Overlay::Effort,
ModeKind::Mcp => Overlay::Mcp,
ModeKind::Todo => Overlay::Todo,
ModeKind::Rewind => Overlay::Rewind,
ModeKind::Loading => Overlay::Loading,
};
state.dirty = true;
}
Action::SubmitInput(text) => {
state.input.submit();
let text = text.trim().to_string();
@@ -175,6 +153,9 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
}
Action::OpenOverlay(overlay) => {
state.misc.overlay = overlay;
if overlay == Overlay::Learning || overlay == Overlay::Rewind || overlay == Overlay::ModelSelector {
state.misc.selected_index = 0;
}
state.dirty = true;
}
Action::OpenEditor { path } => {
@@ -245,45 +226,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
state.scroll.set_max_visible(w as usize);
state.dirty = true;
}
Action::LessonExport { path } => {
let dest = std::path::Path::new(&path);
if let Some(parent) = dest.parent() {
let _ = std::fs::create_dir_all(parent);
}
match crate::model::memory::export_lessons(&state.memory_dir, dest) {
Ok(_) => {
state.push_toast(crate::app::state::types::Toast::new(
crate::app::state::types::ToastKind::Success,
format!("lessons exported to {}", path),
));
}
Err(e) => {
state.push_toast(crate::app::state::types::Toast::new(
crate::app::state::types::ToastKind::Error,
format!("export failed: {}", e),
));
}
}
state.dirty = true;
}
Action::LessonImport { path } => {
let src = std::path::Path::new(&path);
match crate::model::memory::import_lessons(&state.memory_dir, src) {
Ok(count) => {
state.push_toast(crate::app::state::types::Toast::new(
crate::app::state::types::ToastKind::Success,
format!("imported {} lessons from {}", count, path),
));
}
Err(e) => {
state.push_toast(crate::app::state::types::Toast::new(
crate::app::state::types::ToastKind::Error,
format!("import failed: {}", e),
));
}
}
state.dirty = true;
}
Action::StartOAuth { provider } => {
let turn_events = state.turn_events.clone();
let provider_clone = provider.clone();
@@ -583,6 +526,9 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
&rt.session_dir, &state.memory_dir, &name, true,
);
}
if let Some(ref mut rt) = state.session_runtime {
refresh_lesson_counters(&state.memory_dir, rt);
}
state.push_toast(Toast::new(ToastKind::Success,
format!("accepted lesson: {}", name)));
state.dirty = true;
@@ -593,10 +539,21 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
&rt.session_dir, &state.memory_dir, &name, false,
);
}
if let Some(ref mut rt) = state.session_runtime {
refresh_lesson_counters(&state.memory_dir, rt);
}
state.push_toast(Toast::new(ToastKind::Info,
format!("rejected lesson: {}", name)));
state.dirty = true;
}
Action::LessonDelete { name } => {
let _ = crate::model::memory::Memory::remove(&state.memory_dir, &name);
if let Some(ref mut rt) = state.session_runtime {
refresh_lesson_counters(&state.memory_dir, rt);
}
state.push_toast(Toast::new(ToastKind::Info, format!("deleted lesson: {}", name)));
state.dirty = true;
}
Action::RunWorkflow { script } => {
// Open the Workflow overlay so the user can see progress.
state.misc.overlay = Overlay::Workflow;