1use crate::i18n::Locale;
9use orbok_models::SearchCapability;
10use orbok_search::SearchMode;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ViewId {
15 Search,
16 Sources,
17 Indexing,
18 Storage,
19 Models,
20 Settings,
21}
22
23impl ViewId {
24 pub const ALL: &'static [ViewId] = &[
25 ViewId::Search,
26 ViewId::Sources,
27 ViewId::Indexing,
28 ViewId::Storage,
29 ViewId::Models,
30 ViewId::Settings,
31 ];
32}
33
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
36pub struct IndexHealth {
37 pub indexed: u64,
38 pub stale: u64,
39 pub failed: u64,
40 pub queued: u64,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct SourceCard {
46 pub display_name: String,
47 pub display_path: String,
48 pub indexed: u64,
49 pub stale: u64,
50 pub failed: u64,
51 pub active: bool,
52}
53
54#[derive(Debug, Clone, PartialEq)]
57pub struct SearchResultDisplay {
58 pub display_path: String,
59 pub title: Option<String>,
60 pub heading_path: Option<String>,
61 pub snippet: Option<String>,
62 pub keyword_rank: u32,
63 pub badges: Vec<String>,
64}
65
66
67#[derive(Debug, Clone, PartialEq)]
69pub struct WizardFileCheck {
70 pub relative_path: String,
71 pub found: bool,
72 pub size_mb: Option<f64>,
73}
74
75#[derive(Debug, Clone, PartialEq)]
77pub enum WizardState {
78 NotConfigured,
80 FileMissing { previous_dir: String },
82 Checked { model_dir: String, checks: Vec<WizardFileCheck>, all_ok: bool },
84 Ready { model_dir: String },
86}
87
88#[derive(Debug, Clone)]
90pub struct AppState {
91 pub active_view: ViewId,
92 pub locale: Locale,
93 pub query: String,
94 pub last_query: Option<String>,
95 pub search_mode: SearchMode,
96 pub search_results: Vec<SearchResultDisplay>,
97 pub search_running: bool,
98 pub selected_result: Option<usize>,
99 pub storage_rows: Vec<(String, u64, u64)>,
100 pub health: IndexHealth,
101 pub sources: Vec<SourceCard>,
102 pub capability: SearchCapability,
103 pub storage_total_bytes: u64,
104 pub wizard: Option<WizardState>,
106 pub wizard_path_input: String,
108}
109
110impl Default for AppState {
111 fn default() -> Self {
112 Self {
113 active_view: ViewId::Search,
114 locale: Locale::default(),
115 query: String::new(),
116 last_query: None,
117 search_mode: SearchMode::Auto,
118 search_results: Vec::new(),
119 search_running: false,
120 selected_result: None,
121 storage_rows: Vec::new(),
122 health: IndexHealth::default(),
123 sources: Vec::new(),
124 capability: SearchCapability::KeywordOnly,
125 storage_total_bytes: 0,
126 wizard: None,
127 wizard_path_input: String::new(),
128 }
129 }
130}
131
132#[derive(Debug, Clone)]
134pub enum Message {
135 Switch(ViewId),
136 QueryChanged(String),
137 SubmitSearch,
138 SearchResultsReady(Vec<SearchResultDisplay>),
139 SearchError(String),
140 SelectResult(usize),
141 OpenSourceFile(String),
142 SetSearchMode(SearchMode),
143 PersistLocale(Locale),
144 SetLocale(Locale),
145 StorageDataReady(Vec<(String, u64, u64)>),
146 WizardPathChanged(String),
148 WizardValidate,
149 WizardChecked { model_dir: String, checks: Vec<WizardFileCheck>, all_ok: bool },
150 WizardAccept,
151 WizardSkip,
152}
153
154impl AppState {
155 pub fn update(&mut self, message: &Message) {
156 match message {
157 Message::Switch(view) => self.active_view = *view,
158 Message::QueryChanged(query) => self.query = query.clone(),
159 Message::SubmitSearch => {
160 let trimmed = self.query.trim();
161 if !trimmed.is_empty() {
162 self.last_query = Some(trimmed.to_string());
163 self.search_running = true;
164 self.search_results.clear();
165 self.selected_result = None;
166 }
167 }
168 Message::SearchResultsReady(results) => {
169 self.search_results = results.clone();
170 self.search_running = false;
171 self.selected_result = None;
172 }
173 Message::SearchError(_) => {
174 self.search_running = false;
175 }
176 Message::SelectResult(idx) => self.selected_result = Some(*idx),
177 Message::OpenSourceFile(_) => {} Message::SetSearchMode(mode) => self.search_mode = *mode,
179 Message::PersistLocale(locale) | Message::SetLocale(locale) => self.locale = *locale,
180 Message::StorageDataReady(rows) => self.storage_rows = rows.clone(),
181 Message::WizardPathChanged(p) => self.wizard_path_input = p.clone(),
182 Message::WizardValidate => {} Message::WizardChecked { model_dir, checks, all_ok } => {
184 self.wizard = Some(if *all_ok {
185 WizardState::Ready { model_dir: model_dir.clone() }
186 } else {
187 WizardState::Checked {
188 model_dir: model_dir.clone(),
189 checks: checks.clone(),
190 all_ok: false,
191 }
192 });
193 }
194 Message::WizardAccept => {
195 self.capability = SearchCapability::Hybrid;
198 self.wizard = None;
199 self.wizard_path_input = String::new();
200 }
201 Message::WizardSkip => {
202 self.capability = SearchCapability::KeywordOnly;
203 self.wizard = None;
204 self.wizard_path_input = String::new();
205 }
206 }
207 }
208}