graphrag_cli/ui/components/
raw_results_viewer.rs1use crate::{action::Action, theme::Theme};
4use ratatui::{
5 layout::{Margin, Rect},
6 widgets::{Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap},
7 Frame,
8};
9
10pub struct RawResultsViewer {
12 content: Vec<String>,
14 scroll_offset: usize,
16 scrollbar_state: ScrollbarState,
18 focused: bool,
20 theme: Theme,
22}
23
24impl RawResultsViewer {
25 pub fn new() -> Self {
26 Self {
27 content: vec![
28 "Raw search results will appear here.".to_string(),
29 "".to_string(),
30 "These are the entities and relationships retrieved from".to_string(),
31 "the knowledge graph before LLM processing.".to_string(),
32 ],
33 scroll_offset: 0,
34 scrollbar_state: ScrollbarState::default(),
35 focused: false,
36 theme: Theme::default(),
37 }
38 }
39
40 pub fn set_content(&mut self, lines: Vec<String>) {
42 self.content = lines;
43 self.scroll_offset = 0;
44 self.update_scrollbar();
45 }
46
47 #[allow(dead_code)]
49 pub fn clear(&mut self) {
50 self.content.clear();
51 self.scroll_offset = 0;
52 self.update_scrollbar();
53 }
54
55 pub fn scroll_up(&mut self) {
57 self.scroll_offset = self.scroll_offset.saturating_sub(1);
58 self.update_scrollbar();
59 }
60
61 pub fn scroll_down(&mut self) {
63 if self.scroll_offset < self.content.len().saturating_sub(1) {
64 self.scroll_offset += 1;
65 }
66 self.update_scrollbar();
67 }
68
69 pub fn scroll_page_up(&mut self, page_size: usize) {
71 self.scroll_offset = self.scroll_offset.saturating_sub(page_size);
72 self.update_scrollbar();
73 }
74
75 pub fn scroll_page_down(&mut self, page_size: usize) {
77 let max_scroll = self.content.len().saturating_sub(1);
78 self.scroll_offset = (self.scroll_offset + page_size).min(max_scroll);
79 self.update_scrollbar();
80 }
81
82 pub fn scroll_to_top(&mut self) {
84 self.scroll_offset = 0;
85 self.update_scrollbar();
86 }
87
88 pub fn scroll_to_bottom(&mut self) {
90 self.scroll_offset = self.content.len().saturating_sub(1);
91 self.update_scrollbar();
92 }
93
94 pub fn set_focused(&mut self, focused: bool) {
96 self.focused = focused;
97 }
98
99 fn update_scrollbar(&mut self) {
101 self.scrollbar_state = self
102 .scrollbar_state
103 .content_length(self.content.len())
104 .position(self.scroll_offset);
105 }
106}
107
108impl super::Component for RawResultsViewer {
109 fn handle_action(&mut self, action: &Action) -> Option<Action> {
110 match action {
111 Action::ScrollUp => {
112 if self.focused {
113 self.scroll_up();
114 }
115 None
116 },
117 Action::ScrollDown => {
118 if self.focused {
119 self.scroll_down();
120 }
121 None
122 },
123 Action::ScrollPageUp => {
124 if self.focused {
125 self.scroll_page_up(10);
126 }
127 None
128 },
129 Action::ScrollPageDown => {
130 if self.focused {
131 self.scroll_page_down(10);
132 }
133 None
134 },
135 Action::ScrollToTop => {
136 if self.focused {
137 self.scroll_to_top();
138 }
139 None
140 },
141 Action::ScrollToBottom => {
142 if self.focused {
143 self.scroll_to_bottom();
144 }
145 None
146 },
147 Action::FocusRawResultsViewer => {
148 self.set_focused(true);
149 None
150 },
151 _ => None,
152 }
153 }
154
155 fn render(&mut self, f: &mut Frame, area: Rect) {
156 let border_style = if self.focused {
157 self.theme.border_focused()
158 } else {
159 self.theme.border()
160 };
161
162 let title = if self.focused {
163 " Raw Search Results [ACTIVE] (j/k or ↑↓ to scroll | Ctrl+N next panel) "
164 } else {
165 " Raw Search Results (Ctrl+3 or Ctrl+N to focus) "
166 };
167
168 let block = Block::default()
169 .title(title)
170 .borders(Borders::ALL)
171 .border_style(border_style);
172
173 let text = self.content.join("\n");
174
175 let paragraph = Paragraph::new(text)
176 .block(block)
177 .wrap(Wrap { trim: false })
178 .scroll((self.scroll_offset as u16, 0))
179 .style(self.theme.text_dim()); f.render_widget(paragraph, area);
182
183 if self.content.len() > area.height as usize {
185 let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
186 .begin_symbol(Some("↑"))
187 .end_symbol(Some("↓"));
188
189 let scrollbar_area = area.inner(Margin {
190 vertical: 1,
191 horizontal: 0,
192 });
193
194 f.render_stateful_widget(scrollbar, scrollbar_area, &mut self.scrollbar_state);
195 }
196 }
197}
198
199impl Default for RawResultsViewer {
200 fn default() -> Self {
201 Self::new()
202 }
203}