nihalaninihal commited on
Commit
e696760
Β·
verified Β·
1 Parent(s): 1c70005

added pdf download report analysis

Browse files
Files changed (1) hide show
  1. app.py +250 -20
app.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import gradio as gr
2
  import google.generativeai as genai
3
  import os
@@ -13,6 +15,9 @@ import tempfile
13
  from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
14
  import time
15
  import os
 
 
 
16
 
17
 
18
 
@@ -258,12 +263,175 @@ class RepositoryAnalyzer:
258
  "total_contributors": len(contributor_data),
259
  "contributors": contributor_data
260
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
  @retry(
263
  retry=retry_if_exception_type(Exception),
264
  stop=stop_after_attempt(3),
265
  wait=wait_exponential(multiplier=1, min=4, max=10)
266
  )
 
 
267
  def analyze_repository(repo_url: str, progress=gr.Progress()) -> Tuple[str, str, str]:
268
  """Analyze repository and generate LLM summary with rate limit handling"""
269
  try:
@@ -363,12 +531,31 @@ Please provide detailed analysis for each section while maintaining the formatti
363
  json.dump(analysis_data, f, indent=2)
364
  analysis_file = f.name
365
 
366
- progress(1.0, desc="Analysis complete!")
367
- return response.text, analysis_file, "βœ… Analysis completed successfully!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
  except Exception as e:
370
  error_message = f"❌ Error analyzing repository: {str(e)}"
371
- return "", "", error_message
372
 
373
  def create_chat_session() -> Any:
374
  """Create a new chat session for follow-up questions"""
@@ -426,6 +613,7 @@ def ask_question(question: str, analysis_file: str, chat_history: List[Tuple[str
426
 
427
 
428
 
 
429
  # Create Gradio interface
430
  with gr.Blocks(theme=gr.themes.Soft()) as app:
431
  gr.Markdown("""
@@ -435,8 +623,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
435
  1. πŸ“Š Analyze repository structure and patterns
436
  2. πŸ’‘ Generate insights about development practices
437
  3. πŸ’­ Allow you to ask follow-up questions about the analysis
438
-
439
- Enter a GitHub repository URL (e.g., `https://github.com/owner/repo`)
440
  """)
441
 
442
  with gr.Row():
@@ -445,16 +632,24 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
445
  placeholder="https://github.com/owner/repo",
446
  scale=4
447
  )
448
- analyze_btn = gr.Button("πŸ” Analyze", variant="primary", scale=1)
449
 
450
- # Add status message
 
 
 
 
 
451
  status_msg = gr.Markdown("", elem_id="status_message")
452
-
 
 
 
 
 
 
 
453
  with gr.Row():
454
- # Use Markdown instead of Textbox for better formatting
455
- summary = gr.Markdown(
456
- label="Analysis Summary",
457
- )
458
 
459
  with gr.Row():
460
  chatbot = gr.Chatbot(
@@ -472,13 +667,32 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
472
  ask_btn = gr.Button("πŸ’­ Ask", variant="primary", scale=1)
473
  clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", variant="secondary", scale=1)
474
 
475
- # Hidden state for analysis file
476
  analysis_file = gr.State("")
 
 
 
 
 
 
 
 
 
 
 
477
 
478
  def clear_outputs():
479
- return "", [], "", ""
 
 
 
 
 
 
 
 
480
 
481
- # Set up event handlers
482
  analyze_btn.click(
483
  fn=lambda: "⏳ Analysis in progress...",
484
  inputs=None,
@@ -487,15 +701,30 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
487
  ).then(
488
  analyze_repository,
489
  inputs=[repo_url],
490
- outputs=[summary, analysis_file, status_msg],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  )
492
 
493
  ask_btn.click(
494
  ask_question,
495
  inputs=[question, analysis_file, chatbot],
496
- outputs=[chatbot],
497
  ).then(
498
- lambda: "", # Clear the question input
499
  None,
500
  question,
501
  queue=False
@@ -504,10 +733,11 @@ with gr.Blocks(theme=gr.themes.Soft()) as app:
504
  clear_btn.click(
505
  clear_outputs,
506
  inputs=None,
507
- outputs=[summary, chatbot, question, status_msg],
508
- queue=False
509
  )
510
 
 
 
511
  # Launch the app
512
  if __name__ == "__main__":
513
  app.launch(
 
1
+ !pip install requests
2
+
3
  import gradio as gr
4
  import google.generativeai as genai
5
  import os
 
15
  from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
16
  import time
17
  import os
18
+ import re
19
+ from fpdf import FPDF
20
+ import requests
21
 
22
 
23
 
 
263
  "total_contributors": len(contributor_data),
264
  "contributors": contributor_data
265
  }
266
+ def create_pdf_from_markdown(markdown_text: str, filename: str) -> str:
267
+ """Convert markdown text to PDF"""
268
+ class PDF(FPDF):
269
+ def header(self):
270
+ self.set_font('Arial', 'B', 12)
271
+ self.cell(0, 10, 'Repository Analysis Report', 0, 1, 'C')
272
+ self.ln(10)
273
+
274
+ def footer(self):
275
+ self.set_y(-15)
276
+ self.set_font('Arial', 'I', 8)
277
+ self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
278
+
279
+ pdf = PDF()
280
+ pdf.add_page()
281
+ pdf.set_auto_page_break(auto=True, margin=15)
282
+
283
+ # Process markdown sections
284
+ sections = markdown_text.split('\n## ')
285
+
286
+ # Handle main title
287
+ if sections[0].startswith('# '):
288
+ title = sections[0].split('\n')[0].replace('# ', '')
289
+ pdf.set_font('Arial', 'B', 16)
290
+ pdf.cell(0, 10, title, 0, 1, 'C')
291
+ pdf.ln(5)
292
+ content = '\n'.join(sections[0].split('\n')[1:])
293
+ sections[0] = content
294
+
295
+ for section in sections:
296
+ if section:
297
+ # Extract section title and content
298
+ lines = section.split('\n')
299
+ if section == sections[0]: # First section (after main title)
300
+ section_title = ''
301
+ content = lines
302
+ else:
303
+ section_title = lines[0]
304
+ content = lines[1:]
305
+
306
+ # Add section title
307
+ if section_title:
308
+ pdf.set_font('Arial', 'B', 14)
309
+ # Remove emojis from section titles
310
+ clean_title = re.sub(r'[^\x00-\x7F]+', '', section_title)
311
+ pdf.cell(0, 10, clean_title.strip(), 0, 1, 'L')
312
+ pdf.ln(5)
313
+
314
+ # Add content
315
+ pdf.set_font('Arial', '', 11)
316
+ for line in content:
317
+ if line.strip():
318
+ # Remove markdown formatting and emojis
319
+ clean_line = re.sub(r'[\*\[\]]', '', line)
320
+ clean_line = re.sub(r'[^\x00-\x7F]+', '', clean_line)
321
+
322
+ if line.startswith('- '):
323
+ pdf.cell(10, 5, '', 0, 0)
324
+ pdf.multi_cell(0, 5, clean_line[2:])
325
+ else:
326
+ pdf.multi_cell(0, 5, clean_line)
327
+ pdf.ln(5)
328
+
329
+ # Save PDF
330
+ pdf_path = f"{filename}.pdf"
331
+ pdf.output(pdf_path)
332
+ return pdf_path
333
+
334
+ def download_noto_font():
335
+ """Download Google's Noto Color Emoji font if not already present"""
336
+ font_path = "NotoColorEmoji.ttf"
337
+ if not os.path.exists(font_path):
338
+ url = "https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf"
339
+ response = requests.get(url)
340
+ with open(font_path, "wb") as f:
341
+ f.write(response.content)
342
+ return font_path
343
+
344
+ class PDFWithEmoji(FPDF):
345
+ def __init__(self):
346
+ super().__init__()
347
+ self.add_font('DejaVu', '', 'DejaVuSansCondensed.ttf', uni=True)
348
+ self.add_font('Noto', '', 'NotoColorEmoji.ttf', uni=True)
349
+
350
+ def header(self):
351
+ self.set_font('DejaVu', '', 12)
352
+ self.cell(0, 10, 'πŸ“Š Repository Analysis Report', 0, 1, 'C')
353
+ self.ln(10)
354
+
355
+ def footer(self):
356
+ self.set_y(-15)
357
+ self.set_font('DejaVu', '', 8)
358
+ self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
359
+
360
+ def write_with_emoji(self, text):
361
+ """Write text with proper emoji support"""
362
+ self.set_font('DejaVu', '', 11)
363
+ self.multi_cell(0, 5, text)
364
+
365
+ def create_pdf_report(markdown_text: str) -> str:
366
+ """Create a PDF report with full emoji and Unicode support"""
367
+ # Ensure required fonts are available
368
+ if not os.path.exists("DejaVuSansCondensed.ttf"):
369
+ url = "https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSansCondensed.ttf"
370
+ response = requests.get(url)
371
+ with open("DejaVuSansCondensed.ttf", "wb") as f:
372
+ f.write(response.content)
373
+
374
+ download_noto_font()
375
+
376
+ # Create PDF
377
+ pdf = PDFWithEmoji()
378
+ pdf.add_page()
379
+ pdf.set_auto_page_break(auto=True, margin=15)
380
+
381
+ # Process markdown text
382
+ sections = markdown_text.split('\n## ')
383
+
384
+ # Handle main title
385
+ if sections[0].startswith('# '):
386
+ title = sections[0].split('\n')[0].replace('# ', '')
387
+ pdf.set_font('DejaVu', '', 16)
388
+ pdf.cell(0, 10, title, 0, 1, 'C')
389
+ pdf.ln(5)
390
+ content = '\n'.join(sections[0].split('\n')[1:])
391
+ sections[0] = content
392
+
393
+ # Process each section
394
+ for section in sections:
395
+ if section:
396
+ lines = section.split('\n')
397
+ if section == sections[0]: # First section
398
+ section_title = ''
399
+ content = lines
400
+ else:
401
+ section_title = lines[0]
402
+ content = lines[1:]
403
+
404
+ # Add section title
405
+ if section_title:
406
+ pdf.set_font('DejaVu', '', 14)
407
+ pdf.cell(0, 10, section_title, 0, 1, 'L')
408
+ pdf.ln(5)
409
+
410
+ # Add content
411
+ pdf.set_font('DejaVu', '', 11)
412
+ for line in content:
413
+ if line.strip():
414
+ if line.strip().startswith('- '):
415
+ pdf.cell(10, 5, 'β€’', 0, 0)
416
+ pdf.write_with_emoji(line.strip()[2:])
417
+ pdf.ln()
418
+ else:
419
+ pdf.write_with_emoji(line.strip())
420
+ pdf.ln()
421
+
422
+ # Save PDF
423
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
424
+ pdf_path = f"repo_analysis_{timestamp}.pdf"
425
+ pdf.output(pdf_path)
426
+ return pdf_path
427
 
428
  @retry(
429
  retry=retry_if_exception_type(Exception),
430
  stop=stop_after_attempt(3),
431
  wait=wait_exponential(multiplier=1, min=4, max=10)
432
  )
433
+
434
+
435
  def analyze_repository(repo_url: str, progress=gr.Progress()) -> Tuple[str, str, str]:
436
  """Analyze repository and generate LLM summary with rate limit handling"""
437
  try:
 
531
  json.dump(analysis_data, f, indent=2)
532
  analysis_file = f.name
533
 
534
+ progress(0.9, desc="Generating PDF report...")
535
+ try:
536
+ pdf_path = create_pdf_report(response.text)
537
+ except Exception as pdf_error:
538
+ print(f"PDF generation error: {str(pdf_error)}")
539
+ pdf_path = ""
540
+
541
+
542
+
543
+ # Generate PDF
544
+ pdf_path = create_pdf_from_markdown(response.text, f"analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
545
+
546
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as f:
547
+ json.dump(analysis_data, f, indent=2)
548
+ analysis_file = f.name
549
+
550
+
551
+
552
+
553
+ progress(1.0, desc="✨ Analysis complete!")
554
+ return response.text, analysis_file, pdf_path, "βœ… Analysis completed successfully!"
555
 
556
  except Exception as e:
557
  error_message = f"❌ Error analyzing repository: {str(e)}"
558
+ return "", "", "", error_message # Return 4 empty values when there's an error
559
 
560
  def create_chat_session() -> Any:
561
  """Create a new chat session for follow-up questions"""
 
613
 
614
 
615
 
616
+ # Create Gradio interface
617
  # Create Gradio interface
618
  with gr.Blocks(theme=gr.themes.Soft()) as app:
619
  gr.Markdown("""
 
623
  1. πŸ“Š Analyze repository structure and patterns
624
  2. πŸ’‘ Generate insights about development practices
625
  3. πŸ’­ Allow you to ask follow-up questions about the analysis
626
+ 4. πŸ“‘ Generate a downloadable PDF report
 
627
  """)
628
 
629
  with gr.Row():
 
632
  placeholder="https://github.com/owner/repo",
633
  scale=4
634
  )
 
635
 
636
+ with gr.Row():
637
+ with gr.Column(scale=1):
638
+ analyze_btn = gr.Button("πŸ” Analyze", variant="primary")
639
+ download_pdf_btn = gr.Button("πŸ“„ Download PDF", variant="secondary")
640
+
641
+ # Status message
642
  status_msg = gr.Markdown("", elem_id="status_message")
643
+
644
+ # File output for PDF
645
+ pdf_output = gr.File(
646
+ label="Analysis Report",
647
+ visible=False,
648
+ interactive=True
649
+ )
650
+
651
  with gr.Row():
652
+ summary = gr.Markdown(label="Analysis Summary")
 
 
 
653
 
654
  with gr.Row():
655
  chatbot = gr.Chatbot(
 
667
  ask_btn = gr.Button("πŸ’­ Ask", variant="primary", scale=1)
668
  clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", variant="secondary", scale=1)
669
 
670
+ # Hidden states
671
  analysis_file = gr.State("")
672
+ current_pdf_path = gr.State("")
673
+
674
+ def handle_pdf_download(pdf_path):
675
+ """Handle PDF download when button is clicked"""
676
+ if pdf_path and os.path.exists(pdf_path):
677
+ return {
678
+ pdf_output: pdf_path
679
+ }
680
+ return {
681
+ pdf_output: None
682
+ }
683
 
684
  def clear_outputs():
685
+ """Clear all outputs"""
686
+ return {
687
+ summary: "",
688
+ chatbot: [],
689
+ question: "",
690
+ status_msg: "",
691
+ pdf_output: None,
692
+ current_pdf_path: ""
693
+ }
694
 
695
+ # Event handlers
696
  analyze_btn.click(
697
  fn=lambda: "⏳ Analysis in progress...",
698
  inputs=None,
 
701
  ).then(
702
  analyze_repository,
703
  inputs=[repo_url],
704
+ outputs=[summary, analysis_file, current_pdf_path, status_msg]
705
+ ).then(
706
+ lambda: gr.update(visible=True),
707
+ None,
708
+ download_pdf_btn
709
+ )
710
+
711
+ # PDF download handler
712
+ download_pdf_btn.click(
713
+ handle_pdf_download,
714
+ inputs=[current_pdf_path],
715
+ outputs=pdf_output
716
+ ).then(
717
+ lambda: gr.update(visible=True),
718
+ None,
719
+ pdf_output
720
  )
721
 
722
  ask_btn.click(
723
  ask_question,
724
  inputs=[question, analysis_file, chatbot],
725
+ outputs=[chatbot]
726
  ).then(
727
+ lambda: "",
728
  None,
729
  question,
730
  queue=False
 
733
  clear_btn.click(
734
  clear_outputs,
735
  inputs=None,
736
+ outputs=[summary, chatbot, question, status_msg, pdf_output, current_pdf_path]
 
737
  )
738
 
739
+
740
+
741
  # Launch the app
742
  if __name__ == "__main__":
743
  app.launch(