Skip to main content

rustyclaw_tui/components/dialogs/
mod.rs

1// ── Dialog state types ──────────────────────────────────────────────────────
2//
3// State structs for modal overlays. These are kept minimal — the actual
4// dialog iocraft components will be added as needed.
5
6pub use rustyclaw_core::user_prompt_types::{
7    FormField, PromptOption, PromptResponseValue, PromptType, UserPrompt, UserPromptResponse,
8};
9
10// ── Tool approval ───────────────────────────────────────────────────────────
11
12#[derive(Debug, Clone)]
13pub struct ToolApprovalState {
14    pub id: String,
15    pub name: String,
16    pub arguments: String,
17    pub selected_allow: bool,
18}
19
20impl ToolApprovalState {
21    pub fn new(id: String, name: String, arguments: String) -> Self {
22        Self {
23            id,
24            name,
25            arguments,
26            selected_allow: true,
27        }
28    }
29}
30
31// ── Provider / model selection ──────────────────────────────────────────────
32
33#[derive(Debug, Clone)]
34pub struct ProviderSelectorState {
35    pub providers: Vec<String>,
36    pub cursor: usize,
37}
38
39#[derive(Debug, Clone)]
40pub struct ApiKeyDialogState {
41    pub provider: String,
42    pub input: String,
43}
44
45#[derive(Debug, Clone)]
46pub struct ModelSelectorState {
47    pub provider: String,
48    pub models: Vec<String>,
49    pub cursor: usize,
50}
51
52#[derive(Debug, Clone)]
53pub struct FetchModelsLoading {
54    pub provider: String,
55    pub tick: usize,
56}
57
58// ── Credentials ─────────────────────────────────────────────────────────────
59
60#[derive(Debug, Clone)]
61pub struct CredentialDialogState {
62    pub credentials: Vec<CredDialogOption>,
63    pub cursor: usize,
64}
65
66#[derive(Debug, Clone)]
67pub struct CredDialogOption {
68    pub name: String,
69    pub provider: String,
70    pub has_totp: bool,
71}
72
73#[derive(Debug, Clone)]
74pub struct PolicyPickerState {
75    pub credential: String,
76    pub cursor: usize,
77}
78
79// ── TOTP ────────────────────────────────────────────────────────────────────
80
81#[derive(Debug, Clone)]
82pub struct TotpDialogState {
83    pub credential: String,
84    pub phase: TotpDialogPhase,
85}
86
87#[derive(Debug, Clone)]
88pub enum TotpDialogPhase {
89    Setup {
90        qr_lines: Vec<String>,
91        secret: String,
92    },
93    Verify {
94        input: String,
95        error: Option<String>,
96    },
97    Remove {
98        confirmed: bool,
99    },
100}
101
102// ── Auth / vault ────────────────────────────────────────────────────────────
103
104#[derive(Debug, Clone)]
105pub struct AuthPromptState {
106    pub input: String,
107}
108
109#[derive(Debug, Clone)]
110pub struct VaultUnlockPromptState {
111    pub input: String,
112    pub error: Option<String>,
113}
114
115// ── Secret viewer ───────────────────────────────────────────────────────────
116
117#[derive(Debug, Clone)]
118pub struct SecretViewerState {
119    pub key: String,
120    pub value: String,
121    pub revealed: bool,
122}
123
124// ── Tool permissions ────────────────────────────────────────────────────────
125
126#[derive(Debug, Clone)]
127pub struct ToolPermissionsState {
128    pub tools: Vec<ToolPermissionEntry>,
129    pub cursor: usize,
130}
131
132#[derive(Debug, Clone)]
133pub struct ToolPermissionEntry {
134    pub name: String,
135    pub permission: String,
136}
137
138// ── User prompt ─────────────────────────────────────────────────────────────
139
140#[derive(Debug, Clone)]
141pub struct UserPromptState {
142    pub prompt: UserPrompt,
143    pub phase: UserPromptPhase,
144}
145
146#[derive(Debug, Clone)]
147pub enum UserPromptPhase {
148    Select { cursor: usize },
149    MultiSelect { cursor: usize, selected: Vec<bool> },
150    Confirm { yes: bool },
151    TextInput { input: String },
152    Form { cursor: usize, inputs: Vec<String> },
153}
154
155impl UserPromptState {
156    pub fn new(prompt: UserPrompt) -> Self {
157        let phase = match &prompt.prompt_type {
158            PromptType::Select { default, options } => UserPromptPhase::Select {
159                cursor: default.unwrap_or(0).min(options.len().saturating_sub(1)),
160            },
161            PromptType::MultiSelect { defaults, options } => {
162                let mut selected = vec![false; options.len()];
163                for &i in defaults {
164                    if i < selected.len() {
165                        selected[i] = true;
166                    }
167                }
168                UserPromptPhase::MultiSelect {
169                    cursor: 0,
170                    selected,
171                }
172            }
173            PromptType::Confirm { default } => UserPromptPhase::Confirm { yes: *default },
174            PromptType::TextInput { default, .. } => UserPromptPhase::TextInput {
175                input: default.clone().unwrap_or_default(),
176            },
177            PromptType::Form { fields } => UserPromptPhase::Form {
178                cursor: 0,
179                inputs: fields
180                    .iter()
181                    .map(|f| f.default.clone().unwrap_or_default())
182                    .collect(),
183            },
184        };
185        Self { prompt, phase }
186    }
187}
188
189pub const SPINNER_FRAMES: &[char] = &['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];