git_iris/
context.rs

1use crate::token_optimizer::TokenOptimizer;
2use colored::Colorize;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use textwrap::wrap;
7
8#[derive(Serialize, Debug, Clone)]
9pub struct CommitContext {
10    pub branch: String,
11    pub recent_commits: Vec<RecentCommit>,
12    pub staged_files: Vec<StagedFile>,
13    pub project_metadata: ProjectMetadata,
14    pub user_name: String,
15    pub user_email: String,
16}
17
18#[derive(Serialize, Debug, Clone)]
19pub struct RecentCommit {
20    pub hash: String,
21    pub message: String,
22    pub author: String,
23    pub timestamp: String,
24}
25
26#[derive(Serialize, Debug, Clone)]
27pub struct StagedFile {
28    pub path: String,
29    pub change_type: ChangeType,
30    pub diff: String,
31    pub analysis: Vec<String>,
32    pub content: Option<String>,
33    pub content_excluded: bool,
34}
35
36/// Model for commit message generation results
37#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
38pub struct GeneratedMessage {
39    /// Optional emoji for the commit message
40    pub emoji: Option<String>,
41    /// Commit message title/subject line
42    pub title: String,
43    /// Detailed commit message body
44    pub message: String,
45}
46
47/// Model for code review generation results
48#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
49pub struct GeneratedReview {
50    /// Brief summary of the code changes and overall review
51    pub summary: String,
52    /// Detailed assessment of the overall code quality
53    pub code_quality: String,
54    /// List of specific suggestions for improving the code
55    pub suggestions: Vec<String>,
56    /// List of identified issues or problems in the code
57    pub issues: Vec<String>,
58    /// List of positive aspects or good practices in the code
59    pub positive_aspects: Vec<String>,
60}
61
62impl From<String> for GeneratedMessage {
63    fn from(s: String) -> Self {
64        match serde_json::from_str(&s) {
65            Ok(message) => message,
66            Err(e) => {
67                eprintln!("Failed to parse JSON: {e}\nInput was: {s}");
68                Self {
69                    emoji: None,
70                    title: "Error parsing commit message".to_string(),
71                    message: "There was an error parsing the commit message from the AI. Please try again.".to_string(),
72                }
73            }
74        }
75    }
76}
77
78impl From<String> for GeneratedReview {
79    fn from(s: String) -> Self {
80        match serde_json::from_str(&s) {
81            Ok(review) => review,
82            Err(e) => {
83                crate::log_debug!("Failed to parse review JSON: {}", e);
84                crate::log_debug!("Input was: {}", s);
85                Self {
86                    summary: "Error parsing code review".to_string(),
87                    code_quality: "There was an error parsing the code review from the AI."
88                        .to_string(),
89                    suggestions: vec!["Please try again.".to_string()],
90                    issues: vec![],
91                    positive_aspects: vec![],
92                }
93            }
94        }
95    }
96}
97
98#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
99pub enum ChangeType {
100    Added,
101    Modified,
102    Deleted,
103}
104
105impl fmt::Display for ChangeType {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self {
108            Self::Added => write!(f, "Added"),
109            Self::Modified => write!(f, "Modified"),
110            Self::Deleted => write!(f, "Deleted"),
111        }
112    }
113}
114
115#[derive(Serialize, Debug, Clone, Default)]
116pub struct ProjectMetadata {
117    pub language: Option<String>,
118    pub framework: Option<String>,
119    pub dependencies: Vec<String>,
120    pub version: Option<String>,
121    pub build_system: Option<String>,
122    pub test_framework: Option<String>,
123    pub plugins: Vec<String>,
124}
125
126impl ProjectMetadata {
127    pub fn merge(&mut self, new: ProjectMetadata) {
128        if let Some(new_lang) = new.language {
129            match &mut self.language {
130                Some(lang) if !lang.contains(&new_lang) => {
131                    lang.push_str(", ");
132                    lang.push_str(&new_lang);
133                }
134                None => self.language = Some(new_lang),
135                _ => {}
136            }
137        }
138        self.dependencies.extend(new.dependencies.clone());
139        self.framework = self.framework.take().or(new.framework);
140        self.version = self.version.take().or(new.version);
141        self.build_system = self.build_system.take().or(new.build_system);
142        self.test_framework = self.test_framework.take().or(new.test_framework);
143        self.plugins.extend(new.plugins);
144        self.dependencies.sort();
145        self.dependencies.dedup();
146    }
147}
148
149impl CommitContext {
150    pub fn new(
151        branch: String,
152        recent_commits: Vec<RecentCommit>,
153        staged_files: Vec<StagedFile>,
154        project_metadata: ProjectMetadata,
155        user_name: String,
156        user_email: String,
157    ) -> Self {
158        Self {
159            branch,
160            recent_commits,
161            staged_files,
162            project_metadata,
163            user_name,
164            user_email,
165        }
166    }
167    pub fn optimize(&mut self, max_tokens: usize) {
168        let optimizer = TokenOptimizer::new(max_tokens);
169        optimizer.optimize_context(self);
170    }
171}
172
173/// Formats a commit message from a `GeneratedMessage`
174pub fn format_commit_message(response: &GeneratedMessage) -> String {
175    let mut message = String::new();
176
177    if let Some(emoji) = &response.emoji {
178        message.push_str(&format!("{emoji} "));
179    }
180
181    message.push_str(&response.title);
182    message.push_str("\n\n");
183
184    let wrapped_message = wrap(&response.message, 78);
185    for line in wrapped_message {
186        message.push_str(&line);
187        message.push('\n');
188    }
189
190    message
191}
192
193impl GeneratedReview {
194    /// Formats the review into a readable string with colors and emojis for terminal display
195    pub fn format(&self) -> String {
196        let mut formatted = String::new();
197
198        formatted.push_str(&format!(
199            "{}\n\n{}\n\n",
200            "✨ Code Review Summary ✨".bright_magenta().bold(),
201            self.summary.bright_white()
202        ));
203
204        formatted.push_str(&format!(
205            "{}\n\n{}\n\n",
206            "🔍 Code Quality Assessment".bright_cyan().bold(),
207            self.code_quality.bright_white()
208        ));
209
210        if !self.positive_aspects.is_empty() {
211            formatted.push_str(&format!("{}\n\n", "✅ Positive Aspects".green().bold()));
212            for (i, aspect) in self.positive_aspects.iter().enumerate() {
213                formatted.push_str(&format!("{}. {}\n", i + 1, aspect.green()));
214            }
215            formatted.push('\n');
216        }
217
218        if !self.issues.is_empty() {
219            formatted.push_str(&format!("{}\n\n", "❌ Issues Identified".yellow().bold()));
220            for (i, issue) in self.issues.iter().enumerate() {
221                formatted.push_str(&format!("{}. {}\n", i + 1, issue.yellow()));
222            }
223            formatted.push('\n');
224        }
225
226        if !self.suggestions.is_empty() {
227            formatted.push_str(&format!(
228                "{}\n\n",
229                "💡 Suggestions for Improvement".bright_blue().bold()
230            ));
231            for (i, suggestion) in self.suggestions.iter().enumerate() {
232                formatted.push_str(&format!("{}. {}\n", i + 1, suggestion.bright_blue()));
233            }
234        }
235
236        formatted
237    }
238}