use crate::config::Config;
use crate::error::Result;
use crate::parser::parse_session;
use crate::storage::SessionIndex;
use crate::tui::app::App;
use crate::tui::dashboard_view::{DashboardAction, DashboardView};
use crate::tui::projects_view::{ProjectAction, ProjectsView};
use crate::tui::prompts_view::{PromptAction, PromptsView};
use crate::tui::search_modal::{SearchAction, SearchContext, SearchModal};
use crate::tui::sessions_view::{SessionAction, SessionsView};
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::Frame;
#[derive(Debug, Clone)]
pub enum ViewMode {
Projects,
Dashboard,
Prompts,
Sessions(String), #[allow(dead_code)]
SessionDetail(String), }
pub struct Router {
pub view_mode: ViewMode,
pub view_stack: Vec<ViewMode>,
pub projects_view: Option<ProjectsView>,
pub dashboard_view: Option<DashboardView>,
pub prompts_view: Option<PromptsView>,
pub sessions_view: Option<SessionsView>,
pub session_detail_view: Option<App>,
pub search_modal: SearchModal,
pub should_quit: bool,
pub config: Config,
session_watch_path: Option<std::path::PathBuf>,
session_watcher: Option<crate::watcher::SessionWatcher>,
}
impl Router {
pub fn new() -> Result<Self> {
let config = Config::load()?;
if let Err(e) = config.validate() {
eprintln!("Warning: Invalid configuration: {}", e);
eprintln!("Using default configuration.");
}
let projects_view = Some(ProjectsView::new(&config)?);
let search_modal = SearchModal::new(SearchContext::Global);
Ok(Router {
view_mode: ViewMode::Projects,
view_stack: vec![],
projects_view,
dashboard_view: None,
prompts_view: None,
sessions_view: None,
session_detail_view: None,
search_modal,
should_quit: false,
config,
session_watch_path: None,
session_watcher: None,
})
}
#[allow(dead_code)]
pub fn new_with_session(session_id: String) -> Result<Self> {
let index = SessionIndex::new()?;
let session_file = index
.find_by_id(&session_id)?
.ok_or_else(|| crate::error::HindsightError::SessionNotFound(session_id.clone()))?;
let session = parse_session(&session_file.path)?;
let session_detail_view = Some(App::new(session));
let config = Config::load()?;
let search_modal = SearchModal::new(SearchContext::Session(session_id.clone()));
Ok(Router {
view_mode: ViewMode::SessionDetail(session_id),
view_stack: vec![],
projects_view: None,
dashboard_view: None,
prompts_view: None,
sessions_view: None,
session_detail_view,
search_modal,
should_quit: false,
config,
session_watch_path: None,
session_watcher: None,
})
}
pub fn tick(&mut self) {
if let ViewMode::SessionDetail(_) = &self.view_mode {
if let Some(ref mut view) = self.session_detail_view {
view.tick();
}
let has_changes = if let Some(ref mut watcher) = self.session_watcher {
watcher.poll().map(|nodes| !nodes.is_empty()).unwrap_or(false)
} else {
false
};
if has_changes {
if let Some(ref path) = self.session_watch_path.clone() {
if let Ok(updated_session) = parse_session(path) {
self.session_detail_view = Some(App::new(updated_session));
}
}
}
}
}
pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
if self.search_modal.is_active {
return self.handle_search_key(key);
}
if matches!(key.code, KeyCode::Char('/')) {
self.activate_search();
return Ok(());
}
match &self.view_mode {
ViewMode::Projects => {
if key.code == KeyCode::Char('d') || key.code == KeyCode::Char('D') {
self.navigate_to_dashboard()?;
return Ok(());
}
if key.code == KeyCode::Char('p') || key.code == KeyCode::Char('P') {
self.navigate_to_prompts()?;
return Ok(());
}
if let Some(ref mut view) = self.projects_view {
match view.handle_key(key)? {
ProjectAction::None => {}
ProjectAction::SelectProject(project_name) => {
self.navigate_to_sessions(project_name)?;
}
ProjectAction::Quit => {
self.should_quit = true;
}
}
}
}
ViewMode::Dashboard => {
if let Some(ref mut view) = self.dashboard_view {
match view.handle_key(key)? {
DashboardAction::None => {}
DashboardAction::Back => {
self.navigate_back()?;
}
DashboardAction::Quit => {
self.should_quit = true;
}
}
}
}
ViewMode::Prompts => {
if let Some(ref mut view) = self.prompts_view {
match view.handle_key(key)? {
PromptAction::None => {}
PromptAction::SelectSession(session_id) => {
self.navigate_to_session_detail(session_id)?;
}
PromptAction::Back => {
self.navigate_back()?;
}
PromptAction::Quit => {
self.should_quit = true;
}
}
}
}
ViewMode::Sessions(_) => {
if let Some(ref mut view) = self.sessions_view {
match view.handle_key(key)? {
SessionAction::None => {}
SessionAction::SelectSession(session_id) => {
self.navigate_to_session_detail(session_id)?;
}
SessionAction::Back => {
self.navigate_back()?;
}
SessionAction::Quit => {
self.should_quit = true;
}
}
}
}
ViewMode::SessionDetail(_) => {
let (should_go_back, should_quit) =
if let Some(ref mut view) = self.session_detail_view {
view.handle_key(key)?;
(
view.should_quit && !self.view_stack.is_empty(),
view.should_quit && self.view_stack.is_empty(),
)
} else {
(false, false)
};
if should_go_back {
self.navigate_back()?;
if let Some(ref mut view) = self.session_detail_view {
view.should_quit = false;
}
} else if should_quit {
self.should_quit = true;
}
}
}
Ok(())
}
fn navigate_to_dashboard(&mut self) -> Result<()> {
self.view_stack.push(self.view_mode.clone());
self.dashboard_view = Some(DashboardView::new()?);
self.view_mode = ViewMode::Dashboard;
Ok(())
}
fn navigate_to_prompts(&mut self) -> Result<()> {
self.view_stack.push(self.view_mode.clone());
self.prompts_view = Some(PromptsView::new()?);
self.view_mode = ViewMode::Prompts;
Ok(())
}
fn navigate_to_sessions(&mut self, project_name: String) -> Result<()> {
self.view_stack.push(self.view_mode.clone());
self.sessions_view = Some(SessionsView::new(project_name.clone(), &self.config)?);
self.view_mode = ViewMode::Sessions(project_name);
Ok(())
}
fn navigate_to_session_detail(&mut self, session_id: String) -> Result<()> {
self.view_stack.push(self.view_mode.clone());
let index = SessionIndex::new()?;
let session_file = index
.find_by_id(&session_id)?
.ok_or_else(|| crate::error::HindsightError::SessionNotFound(session_id.clone()))?;
let session = parse_session(&session_file.path)?;
self.session_watch_path = Some(session_file.path.clone());
self.session_watcher = crate::watcher::SessionWatcher::new(&session_file.path).ok();
self.session_detail_view = Some(App::new(session));
self.view_mode = ViewMode::SessionDetail(session_id);
Ok(())
}
fn navigate_back(&mut self) -> Result<()> {
self.session_watcher = None;
self.session_watch_path = None;
if let Some(prev_view) = self.view_stack.pop() {
self.view_mode = prev_view;
match &self.view_mode {
ViewMode::Projects => {
if self.projects_view.is_none() {
self.projects_view = Some(ProjectsView::new(&self.config)?);
} else if let Some(ref mut view) = self.projects_view {
view.refresh()?;
}
}
ViewMode::Dashboard => {
if self.dashboard_view.is_none() {
self.dashboard_view = Some(DashboardView::new()?);
}
}
ViewMode::Prompts => {
if self.prompts_view.is_none() {
self.prompts_view = Some(PromptsView::new()?);
}
}
ViewMode::Sessions(project_name) => {
if self.sessions_view.is_none() {
self.sessions_view =
Some(SessionsView::new(project_name.clone(), &self.config)?);
} else if let Some(ref mut view) = self.sessions_view {
view.refresh()?;
}
}
ViewMode::SessionDetail(_) => {
}
}
}
Ok(())
}
fn activate_search(&mut self) {
let context = match &self.view_mode {
ViewMode::Projects | ViewMode::Dashboard | ViewMode::Prompts => SearchContext::Global,
ViewMode::Sessions(project) => SearchContext::Project(project.clone()),
ViewMode::SessionDetail(session_id) => SearchContext::Session(session_id.clone()),
};
self.search_modal.context = context;
self.search_modal.activate();
}
fn handle_search_key(&mut self, key: KeyEvent) -> Result<()> {
match self.search_modal.handle_key(key)? {
SearchAction::None => Ok(()),
SearchAction::Cancel => Ok(()),
SearchAction::SelectSession(session_id) => {
self.navigate_to_session_detail(session_id)?;
Ok(())
}
SearchAction::SelectNode(node_uuid) => {
if let Some(ref mut view) = self.session_detail_view {
view.select_node_by_uuid(&node_uuid);
}
Ok(())
}
}
}
pub fn render(&mut self, f: &mut Frame) {
match &self.view_mode {
ViewMode::Projects => {
if let Some(ref mut view) = self.projects_view {
view.render(f, f.area());
}
}
ViewMode::Dashboard => {
if let Some(ref view) = self.dashboard_view {
view.render(f, f.area());
}
}
ViewMode::Prompts => {
if let Some(ref mut view) = self.prompts_view {
view.render(f, f.area());
}
}
ViewMode::Sessions(_) => {
if let Some(ref mut view) = self.sessions_view {
view.render(f, f.area());
}
}
ViewMode::SessionDetail(_) => {
if let Some(ref mut view) = self.session_detail_view {
crate::tui::ui::draw(f, view);
}
}
}
self.search_modal.render(f, f.area());
}
}