doum_cli/llm/
prompt.rs

1use crate::system::SystemInfo;
2use handlebars::Handlebars;
3use rust_embed::RustEmbed;
4use serde_json::json;
5
6/// Embed prompt assets
7#[derive(RustEmbed)]
8#[folder = "static/prompts/"]
9struct PromptAssets;
10
11/// Prompt builder structure
12pub struct PromptBuilder {
13    system_info: SystemInfo,
14    handlebars: Handlebars<'static>,
15}
16
17impl PromptBuilder {
18    /// Create new PromptBuilder
19    pub fn new(system_info: SystemInfo) -> Self {
20        Self {
21            system_info,
22            handlebars: Handlebars::new(),
23        }
24    }
25
26    /// Load prompt file content
27    fn load_prompt(name: &str) -> String {
28        PromptAssets::get(name)
29            .and_then(|file| {
30                std::str::from_utf8(file.data.as_ref())
31                    .ok()
32                    .map(|s| s.to_string())
33            })
34            .unwrap_or_else(|| {
35                eprintln!("Warning: Failed to load prompt file: {}", name);
36                tracing::warn!("Failed to load prompt file: {}", name);
37                String::new()
38            })
39    }
40
41    /// Create common prompt section
42    fn build_common_prompt(&self) -> String {
43        let template = Self::load_prompt("common.md");
44
45        let data = json!({
46            "os": self.system_info.os.as_str(),
47            "shell": self.system_info.shell.as_str(),
48            "current_dir": self.system_info.current_dir.display().to_string(),
49            "username": self.system_info.username.as_deref().unwrap_or("unknown"),
50            "hostname": self.system_info.hostname.as_deref().unwrap_or("unknown"),
51        });
52
53        self.handlebars
54            .render_template(&template, &data)
55            .unwrap_or(template)
56    }
57
58    /// Create Mode Select message array
59    pub fn build_auto_mode(&self) -> String {
60        let common_prompt = self.build_common_prompt();
61        let mode_select_template = Self::load_prompt("mode_select.md");
62
63        Self::concat_prompts(vec![&common_prompt, &mode_select_template])
64    }
65
66    /// Create Ask mode message array
67    pub fn build_ask(&self) -> String {
68        let common_prompt = self.build_common_prompt();
69        let ask_prompt = Self::load_prompt("ask.md");
70
71        Self::concat_prompts(vec![&common_prompt, &ask_prompt])
72    }
73
74    /// Create Suggest mode message array
75    pub fn build_suggest(&self) -> String {
76        let common_prompt = self.build_common_prompt();
77        let suggest_template = Self::load_prompt("suggest.md");
78
79        let data = json!({
80            "os": self.system_info.os.as_str(),
81            "shell": self.system_info.shell.as_str(),
82        });
83
84        let suggest_prompt = self
85            .handlebars
86            .render_template(&suggest_template, &data)
87            .unwrap_or(suggest_template);
88
89        Self::concat_prompts(vec![&common_prompt, &suggest_prompt])
90    }
91
92    /// Concatenate multiple prompt sections
93    fn concat_prompts(prompts: Vec<&str>) -> String {
94        prompts.join("\n\n---\n\n")
95    }
96}