Skip to main content

llm_manager/tui/app/
types.rs

1pub mod sub;
2use crate::config::Config;
3use crate::config::Profile;
4use crate::models::Backend;
5use crate::models::{
6    BenchTuneConfig, DiscoveredModel, ListSort, ModelSettings, ModelState, SearchResult,
7    SearchSort, ServerMetrics,
8};
9use ratatui::layout::Rect;
10use ratatui::text::Line;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::sync::atomic::AtomicBool;
14use std::sync::{Arc, Mutex};
15
16// Re-export sub-structs
17pub use sub::{
18    BenchTuneState, DownloadState, EditState, LoadingState, LogState, PendingOperations,
19    PickerState, SearchState, ServerState, SettingsState, UIState,
20};
21
22/// Static cell for caching the API port string in help text.
23pub static API_PORT_CACHE: Mutex<(u16, String)> = Mutex::new((0, String::new()));
24
25/// State for an in-progress panel resize drag.
26pub struct ResizeState {
27    /// Starting X position of the mouse when drag began.
28    pub start_x: u16,
29    /// Starting left_pct value when drag began.
30    pub start_pct: u16,
31    /// The area of the top panels container (for border detection).
32    pub container: Rect,
33}
34
35/// Cache for the settings panel render output.
36pub struct SettingsRenderCache {
37    pub hash: u64,
38    pub selected: usize,
39    pub lines: Vec<Line<'static>>,
40    pub selected_content_line: usize,
41}
42
43/// Which panel has focus.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
45pub enum ActivePanel {
46    #[default]
47    Models,
48    Log,
49    ServerSettings,
50    LlmSettings,
51    Profiles,
52    SystemPromptPresets,
53    SearchReadme,
54    ActiveModel,
55    ModelInfo,
56    Downloads,
57}
58
59/// Mode for the models panel.
60#[derive(Debug, Clone)]
61pub enum ModelsMode {
62    /// Normal mode: list of local models.
63    List { sort_by: ListSort },
64    /// Search mode: searching HuggingFace.
65    Search {
66        query: String,
67        results: Vec<SearchResult>,
68        sort_by: SearchSort,
69        show_readme: bool,
70        page: usize,
71        /// Whether results are currently being loaded.
72        loading: bool,
73        /// Whether more results are available.
74        has_more: bool,
75    },
76    /// Files mode: listing available GGUF files for a model.
77    Files {
78        model_id: String,
79        files: Vec<(String, u64, String)>, // (filename, size, url)
80        selected_idx: Option<usize>,
81        previous_query: String,
82        previous_results: Vec<SearchResult>,
83        selected_result: Option<SearchResult>,
84    },
85    /// Benchmark tuning mode: running bench_tune on a model.
86    BenchTune,
87}
88
89/// Global mode that overlays all panels.
90#[derive(Debug, Clone, PartialEq)]
91pub enum GlobalMode {
92    Normal,
93    CmdLine {
94        cmd_line: String,
95    },
96    HostPicker {
97        entries: Vec<(String, String)>, // (ip, interface_name)
98        selected: usize,
99    },
100    BackendPicker {
101        entries: Vec<(Backend, Option<String>)>,
102        selected: usize,
103    },
104    Confirmation {
105        selected: bool,
106        kind: ConfirmationKind,
107        display_name: String,
108        detail: Option<String>,
109    },
110    RpcManager,
111    About,
112    MaxConcurrentPicker {
113        value: String,
114    },
115    SpecTypePicker {
116        entries: Vec<String>,
117        selected: usize,
118    },
119    YarnRoPESettings {
120        scale: String,
121        freq_base: String,
122        freq_scale: String,
123        selected_field: i32, // -1=enabled, 0=scale, 1=freq_base, 2=freq_scale
124        editing: bool,
125        edit_buffer: String,
126        edit_cursor_pos: usize,
127    },
128    BenchTuneSetup {
129        config: BenchTuneConfig,
130        selected_idx: usize,
131        editing_param: bool,
132        editing_param_field: i32,
133        param_edit_buffer: String,
134        param_edit_cursor_pos: usize,
135        bench_mode_selection: usize,
136        editing_prompt: bool,
137        editing_kwargs: bool,
138    },
139    PromptPicker {
140        entries: Vec<(String, String)>, // (name, description)
141        selected: usize,
142        editing: bool,
143        edit_buffer: String,
144        edit_cursor_pos: usize,
145        confirm_delete: bool,
146    },
147    ProfilePicker {
148        entries: Vec<(String, String)>, // (name, description)
149        selected: usize,
150        profiles: Vec<Profile>,
151    },
152    DashboardPicker {
153        enabled: bool,
154        port: String,
155        auth_key: String,
156        tls_enabled: bool,
157        tls_cert: String,
158        tls_key: String,
159        selected_field: i32, // -1=enabled, 0=port, 1=auth_key, 2=tls_enabled, 3=tls_cert, 4=tls_key
160        editing: bool,
161        edit_buffer: String,
162        edit_cursor_pos: usize,
163    },
164    ApiEndpointPicker {
165        enabled: bool,
166        port: String,
167        api_key: String,
168        tls_enabled: bool,
169        tls_cert: String,
170        tls_key: String,
171        selected_field: i32, // -1=enabled, 0=port, 1=api_key, 2=tls_enabled, 3=tls_cert, 4=tls_key
172        editing: bool,
173        edit_buffer: String,
174        edit_cursor_pos: usize,
175    },
176    DashboardUrl {
177        host: String,
178        ws_port: String,
179        api_port: u16,
180        llm_port: u16,
181        auth_key: String,
182        ws_enabled: bool,
183        tls_enabled: bool,
184    },
185    SearchInput {
186        buffer: String,
187        cursor_pos: usize,
188    },
189    GgufNaming {
190        explanation: crate::tui::gguf_naming::GgufExplanation,
191        filename: String,
192    },
193    Onboarding {
194        step: usize,
195    },
196    ChatTemplatePicker {
197        entries: Vec<String>,
198        selected: usize,
199    },
200    ChatTemplateFilePicker {
201        entries: Vec<(String, String)>, // (display_name, full_path)
202        selected: usize,
203    },
204    WebSearchPicker {
205        enabled: bool,
206        engine: String,
207        engine_url: String,
208        api_key: Option<String>,
209        selected_field: i32, // -1=enabled, 0=engine, 1=url, 2=api_key
210        engine_picker_selected: usize,
211        editing: bool,
212        edit_buffer: String,
213        edit_cursor_pos: usize,
214        check_status: Option<WebSearchCheckStatus>,
215    },
216    LlamaServerOptionsPicker {
217        port: String,
218        threads: u32,
219        threads_batch: u32,
220        log_level: String,
221        webui: bool,
222        selected_field: i32, // -1=port, 0=threads, 1=threads_batch, 2=mode, 3=log_level, 4=webui
223        mode_picker_selected: usize,
224        editing: bool,
225        edit_buffer: String,
226        edit_cursor_pos: usize,
227    },
228}
229
230#[derive(Debug, Clone, PartialEq)]
231pub enum WebSearchCheckStatus {
232    Checking,
233    Ok,
234    Error(String),
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub enum ConfirmationKind {
239    Exit,
240    Reset,
241    Delete,
242    Unload,
243    DeleteBackend,
244}
245
246/// Scroll state for text that exceeds display width.
247#[derive(Debug, Clone)]
248pub struct TextScrollState {
249    pub offset: usize,
250    pub last_tick: std::time::Instant,
251    pub direction: i8,
252    pub hold_count: u8,
253    pub max_offset: usize,
254    pub visible: bool,
255    /// Cached scrolled text output.
256    pub cached_output: Option<String>,
257    /// Width used for the cached output.
258    pub cached_width: u16,
259    /// Offset used for the cached output.
260    pub cached_offset: usize,
261}
262
263/// Phase of model loading.
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
265pub enum LoadingPhase {
266    ServerStarting,
267    LoadingModel,
268    LoadingMeta,
269    LoadingTensors,
270    ServerListening,
271    Complete,
272}
273
274impl LoadingPhase {
275    pub fn label(&self) -> &'static str {
276        match self {
277            LoadingPhase::ServerStarting => "Server starting...",
278            LoadingPhase::LoadingModel => "Loading model weights...",
279            LoadingPhase::LoadingMeta => "Loading metadata...",
280            LoadingPhase::LoadingTensors => "Loading tensors...",
281            LoadingPhase::ServerListening => "Server listening...",
282            LoadingPhase::Complete => "Ready",
283        }
284    }
285}
286
287/// The main application state.
288pub struct App {
289    // Core state
290    pub running: bool,
291    pub config: Config,
292    pub models: Vec<DiscoveredModel>,
293    pub selected_model_idx: Option<usize>,
294    pub models_mode: ModelsMode,
295    pub settings: ModelSettings,
296    pub model_settings_cache: ModelSettings,
297    pub model_states: HashMap<String, ModelState>,
298    pub metrics: ServerMetrics,
299    pub max_threads: u32,
300    pub cancelled: Option<Arc<AtomicBool>>,
301    pub server_mode: crate::models::ServerMode,
302    pub router_max_models: u32,
303    pub ws_server_handle: Option<tokio::task::JoinHandle<()>>,
304    pub ws_shutdown_tx: Option<tokio::sync::watch::Sender<bool>>,
305    pub background_tasks: HashMap<String, tokio::task::JoinHandle<()>>,
306
307    // Sub-structs
308    pub settings_state: SettingsState,
309    pub picker: PickerState,
310    pub download: DownloadState,
311    pub server: ServerState,
312    pub bench_tune: BenchTuneState,
313    pub log: LogState,
314    pub loading: LoadingState,
315    pub pending: PendingOperations,
316    pub search: SearchState,
317    pub ui: UIState,
318    pub edit: EditState,
319
320    // ── Performance hints ────────────────────────────────────
321    /// Cached first non-Available model state to avoid scanning model_states every render.
322    pub active_model_hint: Option<(String, crate::models::ModelState)>,
323
324    // ── Scheduler ────────────────────────────────────────────
325    pub pending_tx: tokio::sync::mpsc::Sender<super::pending_events::PendingEvent>,
326    pub pending_rx: tokio::sync::mpsc::Receiver<super::pending_events::PendingEvent>,
327    pub server_ready: bool,
328}