deepseek_rust_cli/tui/
app.rs1use std::time::Instant;
2
3use crate::api::types::TokenUsage;
4
5pub fn load_global_history() -> Vec<String> {
6 let path = std::path::PathBuf::from(".deep/input_history.json");
7 if let Ok(content) = std::fs::read_to_string(path) {
8 if let Ok(history) = serde_json::from_str::<Vec<String>>(&content) {
9 return history;
10 }
11 }
12 Vec::new()
13}
14
15pub fn save_global_history(history: &[String]) {
16 let path = std::path::PathBuf::from(".deep/input_history.json");
17 if let Some(parent) = path.parent() {
18 let _ = std::fs::create_dir_all(parent);
19 }
20 if let Ok(json) = serde_json::to_string_pretty(history) {
21 let _ = std::fs::write(path, json);
22 }
23}
24
25pub struct App {
26 pub input: String,
27 pub cursor_pos: usize,
29 pub awaiting_approval: bool,
30 pub spinner_frame: usize,
31 pub current_task: Option<String>,
32 pub task_start_time: Option<Instant>,
34 pub job_start_time: Option<Instant>,
36 pub cwd: String,
37 pub model: String,
38 pub history: Vec<String>,
39 pub history_index: Option<usize>,
40 pub footer_height: u16,
42 pub token_usage: TokenUsage,
43 pub aborted: bool,
45 pub queued_commands: Vec<String>,
48 pub log_x: u16,
49 pub log_y: u16,
50 pub reasoning_started: bool,
51 pub content_started: bool,
52 pub is_path_traversal_warning: bool,
53}
54
55impl Default for App {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl App {
62 pub fn new() -> Self {
63 let history = load_global_history();
64 Self {
65 input: String::new(),
66 cursor_pos: 0,
67 awaiting_approval: false,
68 spinner_frame: 0,
69 current_task: None,
70 task_start_time: None,
71 job_start_time: None,
72 cwd: std::env::current_dir()
73 .map(|p| p.display().to_string())
74 .unwrap_or_else(|_| ".".to_string()),
75 model: String::from("unknown"),
76 history,
77 history_index: None,
78 footer_height: 4, token_usage: TokenUsage::default(),
80 aborted: false,
81 queued_commands: Vec::new(),
82 log_x: 0,
83 log_y: 0,
84 reasoning_started: false,
85 content_started: false,
86 is_path_traversal_warning: false,
87 }
88 }
89
90 pub fn next_history(&mut self) {
91 if self.history.is_empty() {
92 return;
93 }
94 let next_index = match self.history_index {
95 Some(i) => {
96 if i > 0 {
97 Some(i - 1)
98 } else {
99 Some(0)
100 }
101 }
102 None => Some(self.history.len().saturating_sub(1)),
103 };
104 if let Some(idx) = next_index {
105 self.history_index = Some(idx);
106 self.input = self.history[idx].clone();
107 self.cursor_pos = self.input.len();
108 }
109 }
110
111 pub fn prev_history(&mut self) {
112 if self.history.is_empty() {
113 return;
114 }
115 let next_index = match self.history_index {
116 Some(i) => {
117 if i < self.history.len().saturating_sub(1) {
118 Some(i + 1)
119 } else {
120 self.input.clear();
121 self.cursor_pos = 0;
122 None
123 }
124 }
125 None => None,
126 };
127 self.history_index = next_index;
128 if let Some(idx) = self.history_index {
129 self.input = self.history[idx].clone();
130 self.cursor_pos = self.input.len();
131 }
132 }
133
134 pub fn start_task(&mut self, task: String) {
135 if self.job_start_time.is_none() {
137 self.job_start_time = Some(Instant::now());
138 }
139 if self.current_task.as_ref() != Some(&task) {
140 self.current_task = Some(task);
141 self.task_start_time = Some(Instant::now());
142 }
143 }
144
145 pub fn finish_task(&mut self) {
146 self.current_task = None;
147 self.task_start_time = None;
148 self.job_start_time = None;
149 self.awaiting_approval = false;
150 self.is_path_traversal_warning = false;
151 self.aborted = false;
152 if !self.queued_commands.is_empty() {
154 self.queued_commands.remove(0);
155 }
156 }
157
158 pub fn tick(&mut self) {
159 self.spinner_frame = self.spinner_frame.wrapping_add(1);
160 }
161
162 pub fn total_tokens(&self) -> u64 {
164 self.token_usage.prompt_tokens + self.token_usage.completion_tokens
165 }
166}