1use pi_tui::component::Component;
9use pi_tui::components::{Markdown, Spacer, Text};
10
11use super::state::{LoadedResource, ShortcutHint, StartupDiagnostics};
12use super::theme::{self, MarkdownOptions, MarkdownTheme, ResolvedTheme, ThemeColor};
13
14#[must_use]
20pub fn build_resources(resources: &[LoadedResource], th: &ResolvedTheme) -> Box<dyn Component> {
21 if resources.is_empty() {
22 return Box::new(Spacer::new(0));
23 }
24 let mut stack = super::messages::ColumnStack::new();
25 for r in resources {
26 let line = format!(
27 "{} {}",
28 th.fg(ThemeColor::Accent, &format!("[{}]", r.kind)),
29 th.fg(ThemeColor::Muted, &r.label),
30 );
31 stack.push(Box::new(Text::with_padding(line, 1, 0)));
32 }
33 Box::new(stack)
34}
35
36#[must_use]
42pub fn build_diagnostics(
43 diagnostics: &StartupDiagnostics,
44 th: &ResolvedTheme,
45) -> Box<dyn Component> {
46 if diagnostics.entries.is_empty() {
47 return Box::new(Spacer::new(0));
48 }
49 let mut stack = super::messages::ColumnStack::new();
50 for d in &diagnostics.entries {
51 let (color, glyph) = match d.severity {
52 super::state::DiagnosticSeverity::Warning => (ThemeColor::Warning, "⚠"),
53 super::state::DiagnosticSeverity::Error => (ThemeColor::Error, "✗"),
54 };
55 let line = format!(
56 "{} {}{}",
57 th.fg(color, glyph),
58 th.fg(ThemeColor::Dim, &format!("{}: ", d.source)),
59 d.message,
60 );
61 stack.push(Box::new(Text::with_padding(line, 1, 0)));
62 }
63 Box::new(stack)
64}
65
66#[must_use]
72pub fn build_first_time_setup(
73 step: usize,
74 md_theme: MarkdownTheme,
75 th: &ResolvedTheme,
76) -> Box<dyn Component> {
77 let mut stack = super::messages::ColumnStack::new();
78 stack.push(Box::new(Text::with_padding(
79 theme::bold(&th.fg(ThemeColor::Accent, "Welcome to pi")),
80 1,
81 0,
82 )));
83 let body = match step {
84 0 => "Choose a theme: **dark** or **light**?\nUse `/theme` later to change.",
85 1 => "Enable anonymous usage analytics?\n(you can change this in settings)",
86 _ => "Setup complete. Type a message to begin.",
87 };
88 stack.push(Box::new(Markdown::new(
89 body,
90 1,
91 0,
92 md_theme,
93 theme::default_text_style(),
94 MarkdownOptions::default(),
95 )));
96 Box::new(stack)
97}
98
99#[must_use]
105pub fn build_shortcut_overlay(
106 hints: &[ShortcutHint],
107 extension_hints: &[ShortcutHint],
108 th: &ResolvedTheme,
109) -> Box<dyn Component> {
110 let mut stack = super::messages::ColumnStack::new();
111 stack.push(Box::new(Text::with_padding(
112 theme::bold(&th.fg(ThemeColor::Accent, "Keyboard shortcuts")),
113 1,
114 0,
115 )));
116 push_shortcut_hints(&mut stack, hints, th);
117 if !extension_hints.is_empty() {
118 stack.push(Box::new(Spacer::new(1)));
119 stack.push(Box::new(Text::with_padding(
120 theme::bold(&th.fg(ThemeColor::Accent, "Extensions")),
121 1,
122 0,
123 )));
124 push_shortcut_hints(&mut stack, extension_hints, th);
125 }
126 Box::new(stack)
127}
128
129fn push_shortcut_hints(
130 stack: &mut super::messages::ColumnStack,
131 hints: &[ShortcutHint],
132 th: &ResolvedTheme,
133) {
134 for hint in hints {
135 let line = format!(
136 " {} {}",
137 th.fg(ThemeColor::Accent, &hint.key),
138 th.fg(ThemeColor::Muted, &hint.action),
139 );
140 stack.push(Box::new(Text::with_padding(line, 0, 0)));
141 }
142}
143
144#[must_use]
146pub fn default_shortcut_hints() -> Vec<ShortcutHint> {
147 use super::state::ShortcutHint;
148 [
149 ("Esc", "Interrupt / abort"),
150 ("Ctrl+C", "Clear editor (×2 to exit)"),
151 ("Ctrl+D", "Exit"),
152 ("Shift+Tab", "Cycle thinking"),
153 ("Ctrl+L", "Model selector"),
154 ("Ctrl+P", "Cycle models"),
155 ("Ctrl+O", "Toggle tool output"),
156 ("Ctrl+T", "Toggle thinking"),
157 ("Ctrl+G", "External editor"),
158 ("Ctrl+X", "Copy last assistant"),
159 ("Alt+Enter", "Queue follow-up"),
160 ("Alt+Up", "Restore follow-up"),
161 ("Ctrl+V", "Paste image/text"),
162 ("/help", "Slash commands"),
163 ("? , /hotkeys", "This overlay"),
164 ]
165 .into_iter()
166 .map(|(key, action)| ShortcutHint {
167 key: key.to_owned(),
168 action: action.to_owned(),
169 })
170 .collect()
171}
172
173#[must_use]
179pub fn build_changelog(
180 markdown: &str,
181 md_theme: MarkdownTheme,
182 _th: &ResolvedTheme,
183) -> Box<dyn Component> {
184 let mut stack = super::messages::ColumnStack::new();
185 stack.push(Box::new(Markdown::new(
186 markdown,
187 1,
188 0,
189 md_theme,
190 theme::default_text_style(),
191 MarkdownOptions::default(),
192 )));
193 Box::new(stack)
194}