intelli_shell/process/
new.rs

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