1use iocraft::prelude::*;
7
8use crate::components::auth_dialog::AuthDialog;
9use crate::components::command_menu::CommandMenu;
10use crate::components::hatching_dialog::HatchingDialog;
11use crate::components::input_bar::InputBar;
12use crate::components::messages::Messages;
13use crate::components::secrets_dialog::{SecretInfo, SecretsDialog};
14use crate::components::sidebar::Sidebar;
15use crate::components::skills_dialog::{SkillInfo, SkillsDialog};
16use crate::components::status_bar::StatusBar;
17use crate::components::tool_approval_dialog::ToolApprovalDialog;
18use crate::components::tool_perms_dialog::{ToolPermInfo, ToolPermsDialog};
19use crate::components::user_prompt_dialog::UserPromptDialog;
20use crate::components::vault_unlock_dialog::VaultUnlockDialog;
21use crate::theme;
22use crate::types::DisplayMessage;
23
24#[derive(Default, Props)]
25pub struct RootProps {
26 pub width: u16,
28 pub height: u16,
29
30 pub soul_name: String,
32 pub model_label: String,
33
34 pub gateway_icon: String,
36 pub gateway_label: String,
37 pub gateway_color: Option<Color>,
38
39 pub messages: Vec<DisplayMessage>,
41 pub scroll_offset: i32,
42
43 pub command_completions: Vec<String>,
45 pub command_selected: Option<usize>,
46
47 pub input_value: String,
49 pub on_change: HandlerMut<'static, String>,
50 pub on_submit: HandlerMut<'static, String>,
51 pub input_has_focus: bool,
52
53 pub task_text: String,
55 pub streaming: bool,
56 pub elapsed: String,
57 pub threads: Vec<crate::action::ThreadInfo>,
58 pub sidebar_focused: bool,
59 pub sidebar_selected: usize,
60
61 pub hint: String,
63 pub spinner_tick: usize,
64
65 pub show_auth_dialog: bool,
67 pub auth_code: String,
68 pub auth_error: String,
69
70 pub show_tool_approval: bool,
72 pub tool_approval_name: String,
73 pub tool_approval_args: String,
74 pub tool_approval_selected: bool,
75
76 pub show_vault_unlock: bool,
78 pub vault_password_len: usize,
79 pub vault_error: String,
80
81 pub show_user_prompt: bool,
83 pub user_prompt_title: String,
84 pub user_prompt_desc: String,
85 pub user_prompt_input: String,
86 pub user_prompt_type: Option<rustyclaw_core::user_prompt_types::PromptType>,
87 pub user_prompt_selected: usize,
88
89 pub show_secrets_dialog: bool,
91 pub secrets_data: Vec<SecretInfo>,
92 pub secrets_agent_access: bool,
93 pub secrets_has_totp: bool,
94 pub secrets_selected: Option<usize>,
95 pub secrets_scroll_offset: usize,
96 pub secrets_add_step: u8,
97 pub secrets_add_name: String,
98 pub secrets_add_value: String,
99
100 pub show_skills_dialog: bool,
102 pub skills_data: Vec<SkillInfo>,
103 pub skills_selected: Option<usize>,
104 pub skills_scroll_offset: usize,
105
106 pub show_tool_perms_dialog: bool,
108 pub tool_perms_data: Vec<ToolPermInfo>,
109 pub tool_perms_selected: Option<usize>,
110 pub tool_perms_scroll_offset: usize,
111
112 pub show_hatching: bool,
114 pub hatching_state: crate::components::hatching_dialog::HatchState,
115 pub hatching_agent_name: String,
116}
117
118#[component]
119pub fn Root(props: &mut RootProps) -> impl Into<AnyElement<'static>> {
120 let show_auth = props.show_auth_dialog;
121 let show_approval = props.show_tool_approval;
122 let show_vault = props.show_vault_unlock;
123 let show_prompt = props.show_user_prompt;
124
125 let secrets_data = std::mem::take(&mut props.secrets_data);
126 let secrets_agent = props.secrets_agent_access;
127 let secrets_totp = props.secrets_has_totp;
128 let secrets_selected = props.secrets_selected;
129 let secrets_scroll = props.secrets_scroll_offset;
130 let secrets_add_step = props.secrets_add_step;
131 let secrets_add_name = std::mem::take(&mut props.secrets_add_name);
132 let secrets_add_value = std::mem::take(&mut props.secrets_add_value);
133 #[allow(unused_variables)]
134 let show_secrets = props.show_secrets_dialog;
135 let skills_data = std::mem::take(&mut props.skills_data);
136 let skills_selected = props.skills_selected;
137 let skills_scroll = props.skills_scroll_offset;
138 #[allow(unused_variables)]
139 let show_skills = props.show_skills_dialog;
140 let tool_perms_data = std::mem::take(&mut props.tool_perms_data);
141 let tool_perms_selected = props.tool_perms_selected;
142 let tool_perms_scroll = props.tool_perms_scroll_offset;
143 #[allow(unused_variables)]
144 let show_tool_perms = props.show_tool_perms_dialog;
145
146 let show_hatching = props.show_hatching;
147 let hatching_state = props.hatching_state.clone();
148 let hatching_agent_name = props.hatching_agent_name.clone();
149
150 element! {
151 View(
152 width: props.width,
153 height: props.height,
154 flex_direction: FlexDirection::Column,
155 background_color: theme::BG_MAIN,
156 ) {
157 View(
159 flex_grow: 1.0,
160 flex_direction: FlexDirection::Row,
161 width: 100pct,
162 ) {
163 View(
165 flex_grow: 1.0,
166 flex_direction: FlexDirection::Column,
167 ) {
168 Messages(
169 messages: props.messages.clone(),
170 scroll_offset: props.scroll_offset,
171 streaming: props.streaming,
172 spinner_tick: props.spinner_tick,
173 elapsed: props.elapsed.clone(),
174 assistant_name: if props.soul_name.is_empty() {
175 None
176 } else {
177 Some(props.soul_name.clone())
178 },
179 )
180 CommandMenu(
181 completions: props.command_completions.clone(),
182 selected: props.command_selected,
183 )
184 InputBar(
185 value: props.input_value.clone(),
186 on_change: props.on_change.take(),
187 on_submit: props.on_submit.take(),
188 gateway_icon: props.gateway_icon.clone(),
189 gateway_label: props.gateway_label.clone(),
190 gateway_color: props.gateway_color,
191 has_focus: props.input_has_focus,
192 )
193 }
194 Sidebar(
196 gateway_label: props.gateway_label.clone(),
197 task_text: props.task_text.clone(),
198 streaming: props.streaming,
199 elapsed: props.elapsed.clone(),
200 spinner_tick: props.spinner_tick,
201 threads: props.threads.clone(),
202 focused: props.sidebar_focused,
203 selected: props.sidebar_selected,
204 )
205 }
206
207 StatusBar(
209 hint: props.hint.clone(),
210 streaming: props.streaming,
211 elapsed: props.elapsed.clone(),
212 spinner_tick: props.spinner_tick,
213 soul_name: props.soul_name.clone(),
214 model_label: props.model_label.clone(),
215 )
216
217 #(if show_auth {
219 element! {
220 View(
221 width: props.width,
222 height: props.height,
223 position: Position::Absolute,
224 top: 0,
225 left: 0,
226 ) {
227 AuthDialog(
228 code: props.auth_code.clone(),
229 error: props.auth_error.clone(),
230 )
231 }
232 }.into_any()
233 } else {
234 element! { View() }.into_any()
235 })
236
237 #(if show_approval {
239 element! {
240 View(
241 width: props.width,
242 height: props.height,
243 position: Position::Absolute,
244 top: 0,
245 left: 0,
246 ) {
247 ToolApprovalDialog(
248 tool_name: props.tool_approval_name.clone(),
249 arguments: props.tool_approval_args.clone(),
250 selected_allow: props.tool_approval_selected,
251 )
252 }
253 }.into_any()
254 } else {
255 element! { View() }.into_any()
256 })
257
258 #(if show_vault {
260 element! {
261 View(
262 width: props.width,
263 height: props.height,
264 position: Position::Absolute,
265 top: 0,
266 left: 0,
267 ) {
268 VaultUnlockDialog(
269 password_len: props.vault_password_len,
270 error: props.vault_error.clone(),
271 )
272 }
273 }.into_any()
274 } else {
275 element! { View() }.into_any()
276 })
277
278 #(if show_prompt {
280 element! {
281 View(
282 width: props.width,
283 height: props.height,
284 position: Position::Absolute,
285 top: 0,
286 left: 0,
287 ) {
288 UserPromptDialog(
289 title: props.user_prompt_title.clone(),
290 description: props.user_prompt_desc.clone(),
291 input: props.user_prompt_input.clone(),
292 prompt_type: props.user_prompt_type.clone(),
293 selected: props.user_prompt_selected,
294 )
295 }
296 }.into_any()
297 } else {
298 element! { View() }.into_any()
299 })
300
301 #(if show_secrets {
303 element! {
304 View(
305 width: props.width,
306 height: props.height,
307 position: Position::Absolute,
308 top: 0,
309 left: 0,
310 ) {
311 SecretsDialog(
312 secrets: secrets_data,
313 agent_access: secrets_agent,
314 has_totp: secrets_totp,
315 selected: secrets_selected,
316 scroll_offset: secrets_scroll,
317 add_step: secrets_add_step,
318 add_name: secrets_add_name,
319 add_value: secrets_add_value,
320 )
321 }
322 }.into_any()
323 } else {
324 element! { View() }.into_any()
325 })
326
327 #(if show_skills {
329 element! {
330 View(
331 width: props.width,
332 height: props.height,
333 position: Position::Absolute,
334 top: 0,
335 left: 0,
336 ) {
337 SkillsDialog(
338 skills: skills_data,
339 selected: skills_selected,
340 scroll_offset: skills_scroll,
341 )
342 }
343 }.into_any()
344 } else {
345 element! { View() }.into_any()
346 })
347
348 #(if show_tool_perms {
350 element! {
351 View(
352 width: props.width,
353 height: props.height,
354 position: Position::Absolute,
355 top: 0,
356 left: 0,
357 ) {
358 ToolPermsDialog(
359 tools: tool_perms_data,
360 selected: tool_perms_selected,
361 scroll_offset: tool_perms_scroll,
362 )
363 }
364 }.into_any()
365 } else {
366 element! { View() }.into_any()
367 })
368
369 #(if show_hatching {
371 element! {
372 View(
373 width: props.width,
374 height: props.height,
375 position: Position::Absolute,
376 top: 0,
377 left: 0,
378 ) {
379 HatchingDialog(
380 state: hatching_state,
381 agent_name: hatching_agent_name,
382 )
383 }
384 }.into_any()
385 } else {
386 element! { View() }.into_any()
387 })
388 }
389 }
390}