intelli_shell/process/
new.rs

1use std::time::Duration;
2
3use indicatif::{ProgressBar, ProgressStyle};
4use tracing::instrument;
5
6use super::{InteractiveProcess, Process, ProcessOutput};
7use crate::{
8    cli::BookmarkCommandProcess,
9    component::{
10        Component,
11        edit::{EditCommandComponent, EditCommandComponentMode},
12    },
13    config::Config,
14    errors::{AppError, UserFacingError},
15    format_error, format_msg,
16    model::{CATEGORY_USER, Command, SOURCE_USER},
17    service::IntelliShellService,
18    widgets::SPINNER_CHARS,
19};
20
21impl Process for BookmarkCommandProcess {
22    #[instrument(skip_all)]
23    async fn execute(self, config: Config, service: IntelliShellService) -> color_eyre::Result<ProcessOutput> {
24        let BookmarkCommandProcess {
25            mut command,
26            alias,
27            mut description,
28            ai,
29        } = self;
30
31        // If AI is enabled, we expect a command or description to be provided
32        if ai {
33            let cmd = command.clone().unwrap_or_default();
34            let desc = description.clone().unwrap_or_default();
35
36            if cmd.trim().is_empty() && desc.trim().is_empty() {
37                return Ok(ProcessOutput::fail().stderr(format_error!(
38                    config.theme,
39                    "{}",
40                    UserFacingError::AiEmptyCommand
41                )));
42            }
43
44            // Setup the progress bar
45            let pb = ProgressBar::new_spinner();
46            pb.set_style(
47                ProgressStyle::with_template("{spinner:.blue} {wide_msg}")
48                    .unwrap()
49                    .tick_strings(&SPINNER_CHARS),
50            );
51            pb.enable_steady_tick(Duration::from_millis(100));
52            pb.set_message("Thinking ...");
53
54            // Suggest commands using AI
55            let res = service.suggest_command(cmd, desc).await;
56
57            // Clear the spinner
58            pb.finish_and_clear();
59
60            // Handle the result
61            match res {
62                Ok(Some(suggestion)) => {
63                    command = Some(suggestion.cmd);
64                    description = suggestion.description;
65                }
66                Ok(None) => {
67                    return Ok(
68                        ProcessOutput::fail().stderr(format_error!(config.theme, "AI didn't generate any command"))
69                    );
70                }
71                Err(AppError::UserFacing(err)) => {
72                    return Ok(ProcessOutput::fail().stderr(format_error!(config.theme, "{err}")));
73                }
74                Err(AppError::Unexpected(report)) => return Err(report),
75            }
76        }
77
78        let command = Command::new(CATEGORY_USER, SOURCE_USER, command.unwrap_or_default())
79            .with_alias(alias)
80            .with_description(description);
81
82        match service.insert_command(command).await {
83            Ok(command) => Ok(ProcessOutput::success()
84                .stderr(format_msg!(
85                    config.theme,
86                    "Command stored: {}",
87                    config.theme.secondary.apply(&command.cmd)
88                ))
89                .fileout(command.cmd)),
90            Err(AppError::UserFacing(err)) => Ok(ProcessOutput::fail().stderr(format_error!(config.theme, "{err}"))),
91            Err(AppError::Unexpected(report)) => Err(report),
92        }
93    }
94}
95
96impl InteractiveProcess for BookmarkCommandProcess {
97    #[instrument(skip_all)]
98    fn into_component(
99        self,
100        config: Config,
101        service: IntelliShellService,
102        inline: bool,
103    ) -> color_eyre::Result<Box<dyn Component>> {
104        let BookmarkCommandProcess {
105            command,
106            alias,
107            description,
108            ai,
109        } = self;
110
111        let command = Command::new(CATEGORY_USER, SOURCE_USER, command.unwrap_or_default())
112            .with_alias(alias)
113            .with_description(description);
114
115        Ok(Box::new(EditCommandComponent::new(
116            service,
117            config.theme,
118            inline,
119            command,
120            EditCommandComponentMode::New { ai },
121        )))
122    }
123}