@@ -18,6 +18,7 @@ impl Default for SummaryOptions {
1818}
1919
2020impl PRSummaryGenerator {
21+ #[ allow( dead_code) ]
2122 pub async fn generate_summary (
2223 diffs : & [ UnifiedDiff ] ,
2324 git : & GitIntegration ,
@@ -54,6 +55,37 @@ impl PRSummaryGenerator {
5455 Self :: parse_summary_response ( & response. content , stats)
5556 }
5657
58+ pub async fn generate_change_diagram (
59+ diffs : & [ UnifiedDiff ] ,
60+ adapter : & Box < dyn LLMAdapter > ,
61+ ) -> Result < Option < String > > {
62+ let stats = Self :: calculate_stats ( diffs) ;
63+ let prompt = Self :: build_diagram_prompt ( diffs, & stats) ;
64+ let request = LLMRequest {
65+ system_prompt : "You create concise Mermaid diagrams for code changes. Respond with a single mermaid diagram or 'none'." . to_string ( ) ,
66+ user_prompt : prompt,
67+ temperature : Some ( 0.2 ) ,
68+ max_tokens : Some ( 800 ) ,
69+ } ;
70+
71+ let response = adapter. complete ( request) . await ?;
72+ Ok ( extract_mermaid_block ( & response. content ) )
73+ }
74+
75+ pub fn build_diagram_only_summary ( diffs : & [ UnifiedDiff ] , diagram : String ) -> PRSummary {
76+ let stats = Self :: calculate_stats ( diffs) ;
77+ PRSummary {
78+ title : "Change Diagram" . to_string ( ) ,
79+ description : String :: new ( ) ,
80+ change_type : ChangeType :: Chore ,
81+ key_changes : Vec :: new ( ) ,
82+ breaking_changes : None ,
83+ testing_notes : String :: new ( ) ,
84+ stats,
85+ visual_diff : Some ( diagram) ,
86+ }
87+ }
88+
5789 fn calculate_stats ( diffs : & [ UnifiedDiff ] ) -> ChangeStats {
5890 let mut stats = ChangeStats :: default ( ) ;
5991
@@ -154,6 +186,50 @@ impl PRSummaryGenerator {
154186 prompt
155187 }
156188
189+ fn build_diagram_prompt ( diffs : & [ UnifiedDiff ] , stats : & ChangeStats ) -> String {
190+ let mut prompt = String :: new ( ) ;
191+ prompt. push_str (
192+ "Create a single Mermaid flowchart or sequence diagram that summarizes the change.\n " ,
193+ ) ;
194+ prompt. push_str (
195+ "Use only one mermaid code block. If a diagram isn't useful, reply with 'none'.\n \n " ,
196+ ) ;
197+ prompt. push_str ( "## Statistics\n " ) ;
198+ prompt. push_str ( & format ! ( "- Files changed: {}\n " , stats. files_changed) ) ;
199+ prompt. push_str ( & format ! ( "- Lines added: {}\n " , stats. lines_added) ) ;
200+ prompt. push_str ( & format ! ( "- Lines removed: {}\n " , stats. lines_removed) ) ;
201+
202+ prompt. push_str ( "\n ## Files Changed\n " ) ;
203+ for diff in diffs. iter ( ) . take ( 20 ) {
204+ let path = diff. file_path . display ( ) ;
205+ let added = diff
206+ . hunks
207+ . iter ( )
208+ . flat_map ( |h| & h. changes )
209+ . filter ( |c| matches ! ( c. change_type, crate :: core:: diff_parser:: ChangeType :: Added ) )
210+ . count ( ) ;
211+ let removed = diff
212+ . hunks
213+ . iter ( )
214+ . flat_map ( |h| & h. changes )
215+ . filter ( |c| matches ! ( c. change_type, crate :: core:: diff_parser:: ChangeType :: Removed ) )
216+ . count ( ) ;
217+ let status = if diff. is_deleted {
218+ "deleted"
219+ } else if diff. is_new {
220+ "new"
221+ } else {
222+ "modified"
223+ } ;
224+ prompt. push_str ( & format ! (
225+ "- {} ({}; +{}, -{})\n " ,
226+ path, status, added, removed
227+ ) ) ;
228+ }
229+
230+ prompt
231+ }
232+
157233 fn get_system_prompt ( ) -> String {
158234 r#"You are an AI assistant that generates clear, concise PR summaries.
159235
@@ -394,3 +470,48 @@ fn extract_mermaid_diagram(content: &str) -> Option<String> {
394470
395471 None
396472}
473+
474+ fn extract_mermaid_block ( content : & str ) -> Option < String > {
475+ if content. to_lowercase ( ) . contains ( "none" ) {
476+ return None ;
477+ }
478+ if let Some ( diagram) = extract_mermaid_diagram ( content) {
479+ return Some ( diagram) ;
480+ }
481+
482+ let mut in_block = false ;
483+ let mut lines = Vec :: new ( ) ;
484+ for line in content. lines ( ) {
485+ let trimmed = line. trim ( ) ;
486+ if trimmed. starts_with ( "```" ) && trimmed. contains ( "mermaid" ) {
487+ in_block = true ;
488+ continue ;
489+ }
490+ if trimmed. starts_with ( "```" ) && in_block {
491+ break ;
492+ }
493+ if in_block {
494+ lines. push ( line) ;
495+ }
496+ }
497+ let diagram = lines. join ( "\n " ) . trim ( ) . to_string ( ) ;
498+ if !diagram. is_empty ( ) {
499+ return Some ( diagram) ;
500+ }
501+
502+ let fallback = content
503+ . lines ( )
504+ . filter ( |line| {
505+ let trimmed = line. trim ( ) ;
506+ trimmed. starts_with ( "flowchart" )
507+ || trimmed. starts_with ( "graph" )
508+ || trimmed. starts_with ( "sequenceDiagram" )
509+ } )
510+ . collect :: < Vec < _ > > ( )
511+ . join ( "\n " ) ;
512+ if fallback. trim ( ) . is_empty ( ) {
513+ None
514+ } else {
515+ Some ( fallback)
516+ }
517+ }
0 commit comments