use dkp_core::{
search::{SearchIndex, SearchResult},
types::{
chunks::RetrievalChunk, eval::EvalCase, manifest::McpConfig, mcp_manifest::McpManifest,
},
};
use super::event::InputEvent;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Panel {
Assets = 0,
Search = 1,
Chunks = 2,
Eval = 3,
Mcp = 4,
}
impl Panel {
const COUNT: usize = 5;
pub fn next(self) -> Self {
Self::from_index((self as usize + 1) % Self::COUNT)
}
pub fn prev(self) -> Self {
Self::from_index((self as usize + Self::COUNT - 1) % Self::COUNT)
}
pub fn index(self) -> usize {
self as usize
}
fn from_index(i: usize) -> Self {
match i {
0 => Self::Assets,
1 => Self::Search,
2 => Self::Chunks,
3 => Self::Eval,
4 => Self::Mcp,
_ => Self::Assets,
}
}
pub fn label(self) -> &'static str {
match self {
Self::Assets => "Assets",
Self::Search => "Search",
Self::Chunks => "Chunks",
Self::Eval => "Eval",
Self::Mcp => "MCP",
}
}
}
pub struct AssetSummary {
pub glossary_count: usize,
pub chunk_count: usize,
pub rule_count: usize,
pub eval_count: usize,
pub has_graph: bool,
pub has_skills: bool,
pub has_l10n: bool,
pub has_mcp_manifest: bool,
pub has_assets_file: bool,
pub has_cross_refs: bool,
pub has_okf: bool,
}
pub enum DetailItem {
Chunk(RetrievalChunk),
SearchResult(SearchResult),
EvalCase(EvalCase),
}
pub struct AppState {
pub pack_name: String,
pub pack_version: String,
pub pack_domain: String,
pub pack_update_date: String,
pub active_panel: Panel,
pub detail_view: Option<DetailItem>,
pub asset_summary: AssetSummary,
pub assets_selected: usize,
pub search_input: String,
pub search_input_focused: bool,
pub search_results: Vec<SearchResult>,
pub search_selected: usize,
pub search_index: SearchIndex,
pub chunks: Vec<RetrievalChunk>,
pub chunks_selected: usize,
pub chunks_offset: usize,
pub eval_cases: Vec<EvalCase>,
pub eval_selected: usize,
pub eval_offset: usize,
pub mcp_config: Option<McpConfig>,
pub mcp_manifest: Option<McpManifest>,
pub mcp_selected: usize,
pub should_quit: bool,
}
impl AppState {
pub fn handle_event(&mut self, ev: InputEvent) {
if self.detail_view.is_some() {
match ev {
InputEvent::Escape | InputEvent::Quit => {
self.detail_view = None;
}
_ => {}
}
return;
}
match ev {
InputEvent::Quit => self.should_quit = true,
InputEvent::Tab => {
self.active_panel = self.active_panel.next();
self.search_input_focused = false;
}
InputEvent::BackTab => {
self.active_panel = self.active_panel.prev();
self.search_input_focused = false;
}
InputEvent::SearchFocus => {
self.active_panel = Panel::Search;
self.search_input_focused = true;
}
InputEvent::Escape => {
self.search_input_focused = false;
}
InputEvent::Enter => self.open_detail(),
InputEvent::Up => self.scroll_up(),
InputEvent::Down => self.scroll_down(),
InputEvent::Char(c) => {
if self.active_panel == Panel::Search && self.search_input_focused {
self.search_input.push(c);
self.run_search();
}
}
InputEvent::Backspace
if self.active_panel == Panel::Search && self.search_input_focused =>
{
self.search_input.pop();
self.run_search();
}
_ => {}
}
}
fn run_search(&mut self) {
if self.search_input.trim().is_empty() {
self.search_results.clear();
self.search_selected = 0;
return;
}
self.search_results = self
.search_index
.search(&self.search_input, 20)
.unwrap_or_default();
self.search_selected = 0;
}
fn scroll_up(&mut self) {
match self.active_panel {
Panel::Assets => {
if self.assets_selected > 0 {
self.assets_selected -= 1;
}
}
Panel::Search => {
if self.search_selected > 0 {
self.search_selected -= 1;
}
}
Panel::Chunks => {
if self.chunks_selected > 0 {
self.chunks_selected -= 1;
if self.chunks_selected < self.chunks_offset {
self.chunks_offset = self.chunks_selected;
}
}
}
Panel::Eval => {
if self.eval_selected > 0 {
self.eval_selected -= 1;
if self.eval_selected < self.eval_offset {
self.eval_offset = self.eval_selected;
}
}
}
Panel::Mcp => {
if self.mcp_selected > 0 {
self.mcp_selected -= 1;
}
}
}
}
fn scroll_down(&mut self) {
match self.active_panel {
Panel::Assets => {
if self.assets_selected + 1 < 9 {
self.assets_selected += 1;
}
}
Panel::Search => {
if self.search_selected + 1 < self.search_results.len() {
self.search_selected += 1;
}
}
Panel::Chunks => {
if self.chunks_selected + 1 < self.chunks.len() {
self.chunks_selected += 1;
}
}
Panel::Eval => {
if self.eval_selected + 1 < self.eval_cases.len() {
self.eval_selected += 1;
}
}
Panel::Mcp => {
let max = self
.mcp_manifest
.as_ref()
.map(|m| m.resources.len() + m.tools.len())
.unwrap_or(0);
if self.mcp_selected + 1 < max {
self.mcp_selected += 1;
}
}
}
}
fn open_detail(&mut self) {
self.detail_view = match self.active_panel {
Panel::Chunks => self
.chunks
.get(self.chunks_selected)
.cloned()
.map(DetailItem::Chunk),
Panel::Search => self
.search_results
.get(self.search_selected)
.cloned()
.map(DetailItem::SearchResult),
Panel::Eval => self
.eval_cases
.get(self.eval_selected)
.cloned()
.map(DetailItem::EvalCase),
_ => None,
};
}
}