返回 DeepSeek-TUI-2026
lib.rs
根目录 / crates / tui-core / src / lib.rs
1 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2 pub enum Pane {
3 Chat,
4 Diff,
5 Tasks,
6 Agents,
7 Status,
8 Jobs,
9 }
10
11 #[derive(Debug, Clone, PartialEq, Eq)]
12 pub enum UiEvent {
13 KeyPressed(char),
14 PromptSubmitted(String),
15 ResponseDelta(String),
16 ToolStarted(String),
17 ToolFinished(String),
18 JobQueued(String),
19 JobProgress { job_id: String, progress: u8 },
20 JobCompleted(String),
21 ApprovalRequested(String),
22 ApprovalResolved(String),
23 PauseRequested,
24 ResumeRequested,
25 Tick,
26 }
27
28 #[derive(Debug, Clone, PartialEq, Eq)]
29 pub enum UiEffect {
30 Render,
31 PersistCheckpoint,
32 ScheduleBackgroundRefresh,
33 EmitStatusLine(String),
34 }
35
36 #[derive(Debug, Clone)]
37 pub struct UiState {
38 pub active_pane: Pane,
39 pub paused: bool,
40 pub last_response_delta: Option<String>,
41 pub active_tool: Option<String>,
42 pub pending_tasks: usize,
43 pub active_jobs: usize,
44 pub pending_approvals: usize,
45 pub status_line: String,
46 }
47
48 impl Default for UiState {
49 fn default() -> Self {
50 Self {
51 active_pane: Pane::Chat,
52 paused: false,
53 last_response_delta: None,
54 active_tool: None,
55 pending_tasks: 0,
56 active_jobs: 0,
57 pending_approvals: 0,
58 status_line: "ready".to_string(),
59 }
60 }
61 }
62
63 impl UiState {
64 pub fn reduce(&mut self, event: UiEvent) -> Vec<UiEffect> {
65 match event {
66 UiEvent::KeyPressed('1') => {
67 self.active_pane = Pane::Chat;
68 vec![UiEffect::Render]
69 }
70 UiEvent::KeyPressed('2') => {
71 self.active_pane = Pane::Diff;
72 vec![UiEffect::Render]
73 }
74 UiEvent::KeyPressed('3') => {
75 self.active_pane = Pane::Tasks;
76 vec![UiEffect::Render]
77 }
78 UiEvent::KeyPressed('4') => {
79 self.active_pane = Pane::Agents;
80 vec![UiEffect::Render]
81 }
82 UiEvent::KeyPressed('5') => {
83 self.active_pane = Pane::Jobs;
84 vec![UiEffect::Render]
85 }
86 UiEvent::PromptSubmitted(_) => {
87 self.pending_tasks = self.pending_tasks.saturating_add(1);
88 self.status_line = "prompt submitted".to_string();
89 vec![
90 UiEffect::Render,
91 UiEffect::PersistCheckpoint,
92 UiEffect::EmitStatusLine(self.status_line.clone()),
93 ]
94 }
95 UiEvent::ResponseDelta(delta) => {
96 self.last_response_delta = Some(delta);
97 self.status_line = "streaming response".to_string();
98 vec![
99 UiEffect::Render,
100 UiEffect::EmitStatusLine(self.status_line.clone()),
101 ]
102 }
103 UiEvent::ToolStarted(name) => {
104 self.active_tool = Some(name.clone());
105 self.status_line = format!("tool running: {name}");
106 vec![
107 UiEffect::Render,
108 UiEffect::EmitStatusLine(self.status_line.clone()),
109 ]
110 }
111 UiEvent::ToolFinished(name) => {
112 self.active_tool = None;
113 self.pending_tasks = self.pending_tasks.saturating_sub(1);
114 self.status_line = format!("tool finished: {name}");
115 vec![
116 UiEffect::Render,
117 UiEffect::PersistCheckpoint,
118 UiEffect::EmitStatusLine(self.status_line.clone()),
119 ]
120 }
121 UiEvent::JobQueued(_) => {
122 self.active_jobs = self.active_jobs.saturating_add(1);
123 self.status_line = "job queued".to_string();
124 vec![UiEffect::Render, UiEffect::PersistCheckpoint]
125 }
126 UiEvent::JobProgress { progress, .. } => {
127 self.status_line = format!("job progress: {}%", progress.min(100));
128 vec![
129 UiEffect::Render,
130 UiEffect::EmitStatusLine(self.status_line.clone()),
131 ]
132 }
133 UiEvent::JobCompleted(_) => {
134 self.active_jobs = self.active_jobs.saturating_sub(1);
135 self.status_line = "job completed".to_string();
136 vec![
137 UiEffect::Render,
138 UiEffect::PersistCheckpoint,
139 UiEffect::EmitStatusLine(self.status_line.clone()),
140 ]
141 }
142 UiEvent::ApprovalRequested(_) => {
143 self.pending_approvals = self.pending_approvals.saturating_add(1);
144 self.status_line = "approval requested".to_string();
145 vec![
146 UiEffect::Render,
147 UiEffect::EmitStatusLine(self.status_line.clone()),
148 ]
149 }
150 UiEvent::ApprovalResolved(_) => {
151 self.pending_approvals = self.pending_approvals.saturating_sub(1);
152 self.status_line = "approval resolved".to_string();
153 vec![
154 UiEffect::Render,
155 UiEffect::PersistCheckpoint,
156 UiEffect::EmitStatusLine(self.status_line.clone()),
157 ]
158 }
159 UiEvent::PauseRequested => {
160 self.paused = true;
161 self.status_line = "paused".to_string();
162 vec![
163 UiEffect::Render,
164 UiEffect::EmitStatusLine(self.status_line.clone()),
165 ]
166 }
167 UiEvent::ResumeRequested => {
168 self.paused = false;
169 self.status_line = "resumed".to_string();
170 vec![
171 UiEffect::Render,
172 UiEffect::EmitStatusLine(self.status_line.clone()),
173 ]
174 }
175 UiEvent::Tick => vec![UiEffect::ScheduleBackgroundRefresh],
176 UiEvent::KeyPressed(_) => Vec::new(),
177 }
178 }
179
180 pub fn snapshot(&self) -> String {
181 format!(
182 "pane={:?};paused={};pending_tasks={};active_jobs={};pending_approvals={};active_tool={};status={}",
183 self.active_pane,
184 self.paused,
185 self.pending_tasks,
186 self.active_jobs,
187 self.pending_approvals,
188 self.active_tool.clone().unwrap_or_default(),
189 self.status_line
190 )
191 }
192 }
193
193 lines RUST