feat: add lesson export and import functionality

- Implemented `LessonExport` and `LessonImport` actions in the action module.
- Added corresponding command parsing for lesson export and import.
- Created functions to handle lesson export and import in the memory module.
- Updated state management to reflect changes after lesson operations.
- Introduced deferred operations for handling asynchronous tasks in the event loop.
- Enhanced the tool execution context to include graduated checks for file operations.
- Added OAuth support with PKCE for secure authorization flows.
- Implemented a loopback server for handling OAuth redirects.
- Refactored various modules to improve code organization and maintainability.
This commit is contained in:
asepharyana
2026-07-11 18:23:01 +07:00
parent cc03bd79b6
commit c1ad206a00
49 changed files with 1088 additions and 12 deletions
+45
View File
@@ -38,6 +38,12 @@ pub enum Action {
QuitConfirm,
Resize(u16, u16),
Tick,
LessonExport {
path: String,
},
LessonImport {
path: String,
},
RecordUsage {
tokens_in: u64,
tokens_out: u64,
@@ -224,6 +230,45 @@ 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::Tick => {
let now_ms = chrono::Utc::now().timestamp_millis();
state.misc.drain_expired_toasts(now_ms);