ErNewdev0 commited on
Commit
7406fa3
·
verified ·
1 Parent(s): 1c3b695

chore: ganti interface

Browse files
Files changed (1) hide show
  1. app.py +239 -606
app.py CHANGED
@@ -16,16 +16,15 @@ import dotenv
16
  # Load environment variables
17
  dotenv.load_dotenv()
18
 
19
- # Metadata# Metadata
20
- CURRENT_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
21
- CURRENT_USER = os.getenv("USER", "ErRickow")
22
 
23
  # Default API Keys (fallback if user doesn't provide their own)
24
  DEFAULT_XAI_KEY = os.getenv(
25
  "XAI_API_KEY"
26
  )
27
  DEFAULT_GEMINI_KEY = os.getenv("GEMINI_API_KEY")
28
- GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
29
 
30
  # API settings
31
  OLLAMA_API = os.environ.get("OLLAMA_API", "http://localhost:11434")
@@ -113,7 +112,6 @@ Note:
113
  - Masukkan API key Anda sendiri jika default mencapai limit
114
  """
115
 
116
-
117
  async def get_available_models(provider: str, api_key: str = None) -> List[str]:
118
  """Mendapatkan daftar model yang tersedia dari API."""
119
  try:
@@ -282,7 +280,9 @@ class RepoAnalyzer:
282
  except Exception as e:
283
  yield f"⚠️ Error dalam X.AI API: {str(e)}\nPastikan:\n1. API Key valid\n2. Model {model} tersedia\n3. Anda memiliki akses ke model ini"
284
 
285
- def clone_repository(self, repo_url: str, github_token: str, branch: str = None) -> tuple[bool, str]:
 
 
286
  """Clone repository GitHub dengan autentikasi"""
287
  if not repo_url:
288
  return False, "⚠️ URL repository diperlukan"
@@ -297,7 +297,9 @@ class RepoAnalyzer:
297
 
298
  # Cek apakah repository private
299
  headers = {"Authorization": f"token {github_token}"} if github_token else {}
300
- repo_check = requests.get(f"https://api.github.com/repos/{owner_repo}", headers=headers)
 
 
301
 
302
  if repo_check.status_code == 404:
303
  return False, "⚠️ Repository tidak ditemukan. Periksa URL repository."
@@ -306,7 +308,9 @@ class RepoAnalyzer:
306
  False,
307
  "⚠️ Token GitHub tidak valid. Klik icon bantuan (?) untuk panduan mendapatkan token.",
308
  )
309
- elif repo_check.status_code == 403 and repo_check.json().get("private", False):
 
 
310
  return (
311
  False,
312
  "⚠️ Ini adalah repository private. Token GitHub dengan akses 'repo' diperlukan.",
@@ -363,10 +367,8 @@ class RepoAnalyzer:
363
  continue
364
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
365
 
366
-
367
  analyzer = RepoAnalyzer()
368
 
369
-
370
  async def handle_chat(
371
  message,
372
  history,
@@ -378,420 +380,173 @@ async def handle_chat(
378
  analyzer=analyzer,
379
  ):
380
  """Menangani interaksi chat dengan model AI"""
381
- try:
382
- if not analyzer.current_repo:
383
- new_message = {
384
- "role": "assistant",
385
- "content": "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan.",
386
- }
387
- history = history or []
388
- history.append({"role": "user", "content": message})
389
- history.append(new_message)
390
- yield history
391
- return
392
-
393
  history = history or []
394
- user_message = f'<div class="user-message">{message}</div>'
395
- history.append({"role": "user", "content": user_message})
396
- history.append({"role": "assistant", "content": '<div class="assistant-message">'})
 
397
 
398
- # Add context about selected files
 
 
 
 
 
399
  file_context = ""
400
  if selected_files:
401
  file_context = "\n\nFile yang dipilih:\n"
402
  for file in selected_files:
403
  content = analyzer.repo_content.get(file, "")
404
- if content:
405
  file_context += f"\n{file}:\n```\n{content}\n```\n"
406
 
407
  enhanced_message = f"{message}\n{file_context}"
408
- full_response = ""
409
-
410
- try:
411
- if provider_choice == AIProvider.XAI:
412
- async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
413
- full_response += chunk
414
- formatted_response = format_response(full_response)
415
- history[-1]["content"] = f'<div class="assistant-message">{formatted_response}</div>'
416
- await asyncio.sleep(0.05)
417
- yield history
418
-
419
- elif provider_choice == AIProvider.GEMINI:
420
- if not gemini_key and not DEFAULT_GEMINI_KEY:
421
- error_msg = "⚠️ API Key Gemini diperlukan"
422
- history[-1]["content"] = f'<div class="assistant-message">{error_msg}</div>'
423
- yield history
424
- return
425
 
426
- async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
427
- full_response += chunk
428
- formatted_response = format_response(full_response)
429
- history[-1]["content"] = f'<div class="assistant-message">{formatted_response}</div>'
430
- await asyncio.sleep(0.05)
431
- yield history
432
-
433
- else: # OLLAMA
434
- try:
435
- response = analyze_with_ollama(model_name, enhanced_message)
436
- words = response.split()
437
- for i in range(len(words)):
438
- full_response = " ".join(words[: i + 1])
439
- formatted_response = format_response(full_response)
440
- history[-1]["content"] = f'<div class="assistant-message">{formatted_response}</div>'
441
- await asyncio.sleep(0.05)
442
- yield history
443
- except Exception as e:
444
- error_msg = f"⚠️ Error dengan Ollama: {str(e)}\nPastikan:\n1. Ollama berjalan di {OLLAMA_API}\n2. Model {model_name} sudah diinstall"
445
- history[-1]["content"] = f'<div class="assistant-message">{error_msg}</div>'
446
- yield history
447
 
448
- except Exception as e:
449
- error_msg = f"⚠️ Error saat streaming response: {str(e)}"
450
- history[-1]["content"] = f'<div class="assistant-message">{error_msg}</div>'
451
- yield history
 
 
 
 
 
452
 
453
  except Exception as e:
454
- error_msg = f"⚠️ Error umum: {str(e)}"
455
- history[-1]["content"] = f'<div class="assistant-message">{error_msg}</div>'
456
  yield history
457
 
458
 
459
- def format_response(text):
460
- """Format response text with code blocks"""
461
- import re
462
- import uuid
463
-
464
- # Replace code blocks with styled versions
465
- def replace_code_block(match):
466
- language = match.group(1) or ""
467
- code = match.group(2)
468
- block_id = f"code-block-{uuid.uuid4().hex[:8]}"
469
-
470
- return f'''
471
- <div class="code-block">
472
- <div class="code-header">
473
- <span class="code-title">{language}</span>
474
- <button class="code-copy-btn" id="{block_id}-btn" onclick="copyCode('{block_id}')">Copy</button>
475
- </div>
476
- <pre class="code-content" id="{block_id}">{code}</pre>
477
- </div>
478
- '''
479
-
480
- # Replace ```language\ncode``` blocks
481
- text = re.sub(r"```(\w+)?\n(.*?)```", replace_code_block, text, flags=re.DOTALL)
482
-
483
- # Replace inline code
484
- text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
485
-
486
- return text
487
-
488
-
489
- def clear_all():
490
- """Clear semua input dan status"""
491
- return (
492
- "", # repo_url
493
- "", # github_token
494
- "", # branch
495
- "", # clone_status
496
- [], # chat_history
497
- [], # file_selector
498
- "<div class='file-list'>Belum ada file yang dipilih</div>", # file_list
499
- "", # xai_key
500
- "", # gemini_key
501
- "grok-2-latest", # model_dropdown default value
502
- )
503
-
504
-
505
  def create_ui():
 
506
  global analyzer
507
- current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
508
 
509
  with gr.Blocks(title="Open Repo AI", theme=gr.themes.Soft()) as app:
510
  # CSS Styling
511
  gr.Markdown("""
512
  <style>
513
- /* General Styles */
514
  .container { max-width: 100% !important; padding: 1rem; }
515
  .mobile-full { width: 100% !important; }
516
-
517
- /* Header Styles */
518
- .header-info {
519
- display: flex;
520
- justify-content: space-between;
521
- padding: 10px;
522
  background: #f8f9fa;
523
- border-radius: 4px;
524
- margin: 10px 0;
525
- font-family: monospace;
526
- }
527
- .timestamp, .user-info {
528
- padding: 5px 10px;
529
- background: #fff;
530
- border-radius: 4px;
531
  border: 1px solid #e9ecef;
532
- }
533
-
534
- /* File List Styles */
535
- .file-list {
536
- margin: 10px 0;
537
- padding: 10px;
538
- border: 1px solid #ddd;
539
- border-radius: 4px;
540
- }
541
- .file-item {
542
- display: flex;
543
- justify-content: space-between;
544
- padding: 5px 0;
545
- }
546
-
547
- /* Collapsible Sections */
548
- details {
549
  margin: 10px 0;
550
- border: 1px solid #e9ecef;
551
- border-radius: 8px;
552
- overflow: hidden;
553
  }
554
- summary {
555
- padding: 15px;
556
- background: #f8f9fa;
557
- cursor: pointer;
558
- font-weight: 600;
559
- display: flex;
560
- align-items: center;
561
- transition: background-color 0.3s;
562
  }
563
- summary:hover {
564
- background: #e9ecef;
565
- }
566
- .help-content {
567
- padding: 15px;
568
- background: white;
569
  }
570
-
571
- /* Example Sections */
572
- .examples-wrapper {
573
- margin: 20px 0;
574
- }
575
- .example-section {
576
  margin: 10px 0;
 
577
  }
578
- .example-content {
579
- padding: 15px;
580
- background: white;
581
- }
582
- .example-btn {
583
- width: 100%;
584
- text-align: left;
585
- padding: 8px 12px;
586
- background: #f8f9fa;
587
- border: 1px solid #e9ecef;
588
- border-radius: 4px;
589
- cursor: pointer;
590
- transition: all 0.3s;
591
  }
592
- .example-btn:hover {
593
  background: #e9ecef;
594
- border-color: #dee2e6;
595
- }
596
-
597
- /* Chat Message Styling */
598
- .message-wrap {
599
- display: flex;
600
- flex-direction: column;
601
- gap: 1rem;
602
- }
603
- .user-message, .assistant-message {
604
- padding: 1rem;
605
- border-radius: 8px;
606
- max-width: 90%;
607
- }
608
- .user-message {
609
- align-self: flex-end;
610
- background: #007AFF;
611
- color: white;
612
- }
613
- .assistant-message {
614
- align-self: flex-start;
615
- background: #f8f9fa;
616
- border: 1px solid #e9ecef;
617
- }
618
-
619
- /* Code Block Styling */
620
- .code-block {
621
- position: relative;
622
- margin: 1rem 0;
623
- border-radius: 8px;
624
- overflow: hidden;
625
- border: 1px solid #e9ecef;
626
- }
627
- .code-header {
628
- display: flex;
629
- justify-content: space-between;
630
- align-items: center;
631
- padding: 0.5rem 1rem;
632
- background: #f8f9fa;
633
- border-bottom: 1px solid #e9ecef;
634
- }
635
- .code-title {
636
- font-family: monospace;
637
- font-weight: bold;
638
- color: #495057;
639
- }
640
- .code-copy-btn {
641
- padding: 4px 8px;
642
- font-size: 12px;
643
- color: #6c757d;
644
- background: white;
645
- border: 1px solid #ced4da;
646
  border-radius: 4px;
647
- cursor: pointer;
648
- transition: all 0.2s;
649
  }
650
- .code-copy-btn:hover {
651
- background: #e9ecef;
652
- border-color: #adb5bd;
653
  }
654
- .code-content {
655
- padding: 1rem;
656
- background: #282a36;
657
- color: #f8f8f2;
658
- overflow-x: auto;
659
- font-family: monospace;
660
  }
661
-
662
- /* Media Queries */
663
  @media (max-width: 768px) {
664
  .gr-form { flex-direction: column !important; }
665
  .gr-group { margin: 0.5rem 0 !important; }
666
  }
667
- .clear-button {
668
- background: #dc3545 !important;
669
- color: white !important;
670
- border: none !important;
671
- padding: 0.5rem 1rem !important;
672
- border-radius: 4px !important;
673
- cursor: pointer !important;
674
- transition: background-color 0.2s !important;
675
- }
676
-
677
- .clear-button:hover {
678
- background: #c82333 !important;
679
- }
680
-
681
- /* Button Container */
682
- .button-container {
683
- display: flex;
684
- gap: 10px;
685
- margin-top: 10px;
686
- }
687
-
688
- .button-container button {
689
- flex: 1;
690
- }
691
  </style>
692
-
693
- <script>
694
- function copyCode(blockId) {
695
- const codeBlock = document.getElementById(blockId);
696
- const code = codeBlock.innerText;
697
- navigator.clipboard.writeText(code).then(() => {
698
- const btn = document.querySelector(`#${blockId}-btn`);
699
- btn.innerText = 'Copied!';
700
- setTimeout(() => {
701
- btn.innerText = 'Copy';
702
- }, 2000);
703
- });
704
- }
705
-
706
- function setQuestion(text) {
707
- const chatInput = document.querySelector('textarea[data-testid="textbox"]');
708
- if (chatInput) {
709
- chatInput.value = text;
710
- chatInput.focus();
711
- }
712
- }
713
- </script>
714
  """)
715
 
716
  # Header
717
  with gr.Row(elem_classes="container"):
718
  gr.Markdown(f"""
719
- # AI Github Repository Chat
720
 
721
- <div class="header-info">
722
- <span class="timestamp">Current Date and Time (UTC - YYYY-MM-DD HH:MM:SS formatted): {current_time}</span>
723
- </div>
724
  """)
725
 
726
  # Main Tabs Container
727
  with gr.Tabs() as tabs:
728
  # Configuration Tab
729
- with gr.Tab("🛠️ Konfigurasi"):
730
  provider = gr.Radio(
731
  choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
732
- label="Penyedia AI",
733
  value=AIProvider.XAI,
734
  )
735
 
736
  with gr.Group() as api_settings:
737
- # X.AI API Key section
738
  with gr.Row():
739
- with gr.Column(scale=3):
740
- xai_key = gr.Textbox(
741
- label="X.AI (Grok) API Key",
742
- type="password",
743
- placeholder="Opsional - Klik (?) untuk info",
744
- show_label=True,
745
- )
746
  with gr.Column(scale=1):
747
- gr.Markdown("""
748
- <details>
749
- <summary>❓ Cara Mendapatkan X.AI API Key</summary>
750
- <div class="help-content">
751
- <ol>
752
- <li>Kunjungi <a href="https://x.ai" target="_blank">X.AI Developer Portal</a></li>
753
- <li>Daftar/Login ke akun Anda</li>
754
- <li>Buat API Key baru</li>
755
- <li>Salin API Key</li>
756
- </ol>
757
- <p><strong>Catatan:</strong></p>
758
- <ul>
759
- <li>Jika tidak diisi, akan menggunakan API key default</li>
760
- <li>Masukkan API key Anda sendiri jika default mencapai limit</li>
761
- </ul>
762
- </div>
763
- </details>
764
- """)
765
-
766
- # Gemini API Key section
767
  with gr.Row():
768
- with gr.Column(scale=3):
769
- gemini_key = gr.Textbox(
770
- label="Gemini API Key",
771
- type="password",
772
- placeholder="Opsional - Klik (?) untuk info",
773
- show_label=True,
774
- )
775
  with gr.Column(scale=1):
776
- gr.Markdown("""
777
- <details>
778
- <summary>❓ Cara Mendapatkan Gemini API Key</summary>
779
- <div class="help-content">
780
- <ol>
781
- <li>Kunjungi <a href="https://makersuite.google.com/app/apikey" target="_blank">Google AI Studio</a></li>
782
- <li>Login dengan akun Google Anda</li>
783
- <li>Klik "Create API Key"</li>
784
- <li>Salin API Key yang dihasilkan</li>
785
- </ol>
786
- <p><strong>Catatan:</strong></p>
787
- <ul>
788
- <li>Gemini memberikan kuota gratis setiap bulan</li>
789
- <li>Key bisa dibuat ulang jika diperlukan</li>
790
- <li>Monitor penggunaan di <a href="https://console.cloud.google.com/" target="_blank">Google Cloud Console</a></li>
791
- </ul>
792
- </div>
793
- </details>
794
- """)
795
 
796
  with gr.Row():
797
  model_dropdown = gr.Dropdown(
@@ -802,9 +557,9 @@ def create_ui():
802
  )
803
 
804
  # Repository Analysis Tab
805
- with gr.Tab("📊 Analisis Repository"):
806
  with gr.Group():
807
- # Repository URL input
808
  with gr.Row():
809
  repo_url = gr.Textbox(
810
  label="URL Repository GitHub",
@@ -812,35 +567,15 @@ def create_ui():
812
  elem_classes="mobile-full",
813
  )
814
 
815
- # GitHub Token section
816
  with gr.Row():
817
  with gr.Column(scale=2):
818
  github_token = gr.Textbox(
819
- label="Token GitHub",
820
  type="password",
821
- placeholder="Klik (?) untuk panduan",
822
  elem_classes="mobile-full",
823
  )
824
- gr.Markdown("""
825
- <details>
826
- <summary>❓ Cara Mendapatkan GitHub Token</summary>
827
- <div class="help-content">
828
- <ol>
829
- <li>Kunjungi <a href="https://github.com/settings/tokens" target="_blank">GitHub Token Settings</a></li>
830
- <li>Klik "Generate new token" > "Generate new token (classic)"</li>
831
- <li>Beri nama token Anda di "Note"</li>
832
- <li>Pilih scope:
833
- <ul>
834
- <li><code>repo</code> (untuk akses repository private)</li>
835
- <li><code>read:packages</code> (opsional, untuk akses package)</li>
836
- </ul>
837
- </li>
838
- <li>Klik "Generate token"</li>
839
- <li><strong>PENTING:</strong> Salin token segera! Token hanya ditampilkan sekali</li>
840
- </ol>
841
- </div>
842
- </details>
843
- """)
844
  with gr.Column(scale=1):
845
  branch = gr.Textbox(
846
  label="Branch (opsional)",
@@ -848,18 +583,20 @@ def create_ui():
848
  elem_classes="mobile-full",
849
  )
850
 
851
- # Clone Button and Status
852
  clone_button = gr.Button(
853
- "🔄 Clone Repository",
854
  variant="primary",
855
  elem_classes="mobile-full",
856
  )
857
 
858
- clone_status = gr.Markdown(value="", label="Status Repository", elem_classes="mobile-full")
 
 
859
 
860
  # File Selection
861
  with gr.Group():
862
- gr.Markdown("### 📎 File yang Dipilih")
863
  with gr.Row():
864
  file_selector = gr.Dropdown(
865
  label="Pilih File dari Repository",
@@ -877,200 +614,17 @@ def create_ui():
877
  )
878
 
879
  # Examples Tab
880
- with gr.Tab("💡 Examples"):
881
- gr.Markdown("""
882
- <style>
883
- /* Styling untuk collapse */
884
- .examples-container {
885
- padding: 20px;
886
- background: #fff;
887
- border-radius: 8px;
888
- }
889
-
890
- .category-collapse {
891
- margin-bottom: 15px;
892
- border: 1px solid #e9ecef;
893
- border-radius: 8px;
894
- overflow: hidden;
895
- }
896
-
897
- .category-header {
898
- padding: 15px;
899
- background: #f8f9fa;
900
- cursor: pointer;
901
- user-select: none;
902
- display: flex;
903
- align-items: center;
904
- justify-content: space-between;
905
- transition: background-color 0.2s;
906
- }
907
-
908
- .category-header:hover {
909
- background: #e9ecef;
910
- }
911
-
912
- .category-content {
913
- padding: 15px;
914
- display: none;
915
- border-top: 1px solid #e9ecef;
916
- }
917
-
918
- .category-collapse.active .category-content {
919
- display: block;
920
- animation: slideDown 0.3s ease-out;
921
- }
922
-
923
- .category-header::after {
924
- content: '▼';
925
- font-size: 12px;
926
- transition: transform 0.3s;
927
- }
928
-
929
- .category-collapse.active .category-header::after {
930
- transform: rotate(180deg);
931
- }
932
-
933
- .example-button {
934
- display: block;
935
- width: 100%;
936
- padding: 10px;
937
- margin: 5px 0;
938
- text-align: left;
939
- background: #fff;
940
- border: 1px solid #e9ecef;
941
- border-radius: 4px;
942
- cursor: pointer;
943
- transition: all 0.2s;
944
- }
945
-
946
- .example-button:hover {
947
- background: #f8f9fa;
948
- border-color: #dee2e6;
949
- }
950
-
951
- @keyframes slideDown {
952
- from {
953
- opacity: 0;
954
- transform: translateY(-10px);
955
- }
956
- to {
957
- opacity: 1;
958
- transform: translateY(0);
959
- }
960
- }
961
- </style>
962
-
963
- <div class="examples-container">
964
- <h3>🎯 Contoh Pertanyaan</h3>
965
- <p>Klik pada kategori untuk melihat contoh pertanyaan yang bisa diajukan</p>
966
-
967
- <div class="category-collapse">
968
- <div class="category-header">
969
- 🔍 Analisis Kode
970
- </div>
971
- <div class="category-content">
972
- <button class="example-button" onclick="setQuestion('Jelaskan logika dan alur dari kode ini')">
973
- Jelaskan logika dari kode
974
- </button>
975
- <button class="example-button" onclick="setQuestion('Bagaimana cara mengoptimalkan performa kode ini?')">
976
- Optimasi performa
977
- </button>
978
- <button class="example-button" onclick="setQuestion('Apakah ada potential bugs yang perlu diperbaiki?')">
979
- Cek potential bugs
980
- </button>
981
- </div>
982
- </div>
983
-
984
- <div class="category-collapse">
985
- <div class="category-header">
986
- 📚 Best Practices
987
- </div>
988
- <div class="category-content">
989
- <button class="example-button" onclick="setQuestion('Apa saja best practices yang bisa diterapkan di kode ini?')">
990
- Saran best practices
991
- </button>
992
- <button class="example-button" onclick="setQuestion('Bagaimana cara meningkatkan maintainability kode ini?')">
993
- Improve maintainability
994
- </button>
995
- <button class="example-button" onclick="setQuestion('Berikan saran untuk meningkatkan code quality')">
996
- Improve code quality
997
- </button>
998
- </div>
999
- </div>
1000
-
1001
- <div class="category-collapse">
1002
- <div class="category-header">
1003
- 🧪 Testing
1004
- </div>
1005
- <div class="category-content">
1006
- <button class="example-button" onclick="setQuestion('Buatkan unit test untuk fungsi ini')">
1007
- Generate unit tests
1008
- </button>
1009
- <button class="example-button" onclick="setQuestion('Bagaimana cara test error handling yang baik?')">
1010
- Test error handling
1011
- </button>
1012
- <button class="example-button" onclick="setQuestion('Berikan saran untuk meningkatkan test coverage')">
1013
- Improve test coverage
1014
- </button>
1015
- </div>
1016
- </div>
1017
-
1018
- <div class="category-collapse">
1019
- <div class="category-header">
1020
- 📝 Dokumentasi
1021
- </div>
1022
- <div class="category-content">
1023
- <button class="example-button" onclick="setQuestion('Buatkan dokumentasi untuk fungsi-fungsi utama')">
1024
- Generate documentation
1025
- </button>
1026
- <button class="example-button" onclick="setQuestion('Buat README.md untuk project ini')">
1027
- Create README.md
1028
- </button>
1029
- <button class="example-button" onclick="setQuestion('Jelaskan cara penggunaan library/framework')">
1030
- Usage guide
1031
- </button>
1032
- </div>
1033
- </div>
1034
- </div>
1035
-
1036
- <script>
1037
- document.addEventListener('DOMContentLoaded', function() {
1038
- // Handle collapse functionality
1039
- const categories = document.querySelectorAll('.category-collapse');
1040
-
1041
- categories.forEach(category => {
1042
- const header = category.querySelector('.category-header');
1043
-
1044
- header.addEventListener('click', () => {
1045
- const isActive = category.classList.contains('active');
1046
-
1047
- // Close all categories
1048
- categories.forEach(c => c.classList.remove('active'));
1049
-
1050
- // If clicked category wasn't active, open it
1051
- if (!isActive) {
1052
- category.classList.add('active');
1053
- }
1054
- });
1055
- });
1056
- });
1057
-
1058
- // Function to set question in chat input
1059
- function setQuestion(text) {
1060
- const chatInput = document.querySelector('textarea[data-testid="textbox"]');
1061
- if (chatInput) {
1062
- chatInput.value = text;
1063
- chatInput.focus();
1064
- }
1065
- }
1066
- </script>
1067
- """)
1068
 
1069
  # Chat Interface (outside tabs)
1070
  with gr.Group():
1071
  chat_history = gr.Chatbot(
1072
- label="📝 Riwayat Chat",
1073
- height=500,
1074
  show_label=True,
1075
  type="messages",
1076
  elem_classes="mobile-full",
@@ -1078,20 +632,19 @@ def create_ui():
1078
 
1079
  with gr.Row():
1080
  chat_input = gr.Textbox(
1081
- label="💭 Tanyakan tentang Repository",
1082
- placeholder="Ketik pertanyaan Anda di sini...",
1083
  lines=3,
1084
  elem_classes="mobile-full",
1085
  )
1086
- with gr.Column(scale=1):
1087
- send_button = gr.Button("📤 Kirim", variant="primary")
1088
- clear_button = gr.Button("🧹 Bersihkan Semua", variant="secondary")
1089
 
1090
  # Event Handlers
1091
  def handle_clone(repo_url, github_token, branch):
1092
  if not repo_url:
1093
  return (
1094
- "⚠️ URL repository diperlukan!",
1095
  gr.Dropdown(choices=[]),
1096
  "<div class='file-list'>Belum ada file yang dipilih</div>",
1097
  )
@@ -1122,6 +675,88 @@ def create_ui():
1122
  html += "</div>"
1123
  return html
1124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1125
  def clear_chat_history():
1126
  return []
1127
 
@@ -1141,8 +776,8 @@ def create_ui():
1141
  )
1142
  except Exception as e:
1143
  return gr.Dropdown(
1144
- choices=["grok-2-latest"] if provider_choice == AIProvider.XAI else ["gemini-pro"],
1145
- value="grok-2-latest" if provider_choice == AIProvider.XAI else "gemini-pro",
1146
  label=f"Model AI (Error: {str(e)})",
1147
  )
1148
 
@@ -1167,27 +802,26 @@ def create_ui():
1167
  outputs=[clone_status, file_selector, file_list],
1168
  )
1169
 
1170
- file_selector.change(fn=update_file_list, inputs=[file_selector], outputs=[file_list])
 
 
1171
 
1172
- clear_button = gr.Button("🧹 Bersihkan Semua", variant="secondary")
 
 
 
 
 
 
1173
 
1174
- # Chat events
1175
- clear_button.click(
1176
- fn=clear_all,
1177
- outputs=[
1178
- repo_url,
1179
- github_token,
1180
- branch,
1181
- clone_status,
1182
- chat_history,
1183
- file_selector,
1184
- file_list,
1185
- xai_key,
1186
- gemini_key,
1187
- model_dropdown,
1188
- ],
1189
  )
1190
 
 
 
 
1191
  send_button.click(
1192
  fn=handle_chat,
1193
  inputs=[
@@ -1222,10 +856,9 @@ def create_ui():
1222
 
1223
 
1224
  if __name__ == "__main__":
1225
- print("""
1226
- 🚀 Starting Repository Chat Analysis
1227
- ====================================
1228
  """)
1229
 
1230
  app = create_ui()
1231
- app.launch(share=True, server_name="0.0.0.0", server_port=7860, debug=True)
 
16
  # Load environment variables
17
  dotenv.load_dotenv()
18
 
19
+ # Metadata
20
+ CURRENT_TIME = "2025-05-23 12:57:22"
21
+ CURRENT_USER = "ErRickow"
22
 
23
  # Default API Keys (fallback if user doesn't provide their own)
24
  DEFAULT_XAI_KEY = os.getenv(
25
  "XAI_API_KEY"
26
  )
27
  DEFAULT_GEMINI_KEY = os.getenv("GEMINI_API_KEY")
 
28
 
29
  # API settings
30
  OLLAMA_API = os.environ.get("OLLAMA_API", "http://localhost:11434")
 
112
  - Masukkan API key Anda sendiri jika default mencapai limit
113
  """
114
 
 
115
  async def get_available_models(provider: str, api_key: str = None) -> List[str]:
116
  """Mendapatkan daftar model yang tersedia dari API."""
117
  try:
 
280
  except Exception as e:
281
  yield f"⚠️ Error dalam X.AI API: {str(e)}\nPastikan:\n1. API Key valid\n2. Model {model} tersedia\n3. Anda memiliki akses ke model ini"
282
 
283
+ def clone_repository(
284
+ self, repo_url: str, github_token: str, branch: str = None
285
+ ) -> tuple[bool, str]:
286
  """Clone repository GitHub dengan autentikasi"""
287
  if not repo_url:
288
  return False, "⚠️ URL repository diperlukan"
 
297
 
298
  # Cek apakah repository private
299
  headers = {"Authorization": f"token {github_token}"} if github_token else {}
300
+ repo_check = requests.get(
301
+ f"https://api.github.com/repos/{owner_repo}", headers=headers
302
+ )
303
 
304
  if repo_check.status_code == 404:
305
  return False, "⚠️ Repository tidak ditemukan. Periksa URL repository."
 
308
  False,
309
  "⚠️ Token GitHub tidak valid. Klik icon bantuan (?) untuk panduan mendapatkan token.",
310
  )
311
+ elif repo_check.status_code == 403 and repo_check.json().get(
312
+ "private", False
313
+ ):
314
  return (
315
  False,
316
  "⚠️ Ini adalah repository private. Token GitHub dengan akses 'repo' diperlukan.",
 
367
  continue
368
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
369
 
 
370
  analyzer = RepoAnalyzer()
371
 
 
372
  async def handle_chat(
373
  message,
374
  history,
 
380
  analyzer=analyzer,
381
  ):
382
  """Menangani interaksi chat dengan model AI"""
383
+ if not analyzer.current_repo:
384
+ new_message = {
385
+ "role": "assistant",
386
+ "content": "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan.",
387
+ }
 
 
 
 
 
 
 
388
  history = history or []
389
+ history.append({"role": "user", "content": message})
390
+ history.append(new_message)
391
+ yield history
392
+ return
393
 
394
+ history = history or []
395
+ history.append({"role": "user", "content": message})
396
+ history.append({"role": "assistant", "content": ""})
397
+
398
+ try:
399
+ # Add context about selected files to the prompt
400
  file_context = ""
401
  if selected_files:
402
  file_context = "\n\nFile yang dipilih:\n"
403
  for file in selected_files:
404
  content = analyzer.repo_content.get(file, "")
405
+ if content: # Only include files that exist
406
  file_context += f"\n{file}:\n```\n{content}\n```\n"
407
 
408
  enhanced_message = f"{message}\n{file_context}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
+ full_response = ""
411
+ if provider_choice == AIProvider.XAI:
412
+ async for chunk in analyzer.stream_xai_response(
413
+ enhanced_message, xai_key, model_name
414
+ ):
415
+ full_response += chunk
416
+ # Add delay between chunks for readability
417
+ await asyncio.sleep(1)
418
+ history[-1]["content"] = full_response
419
+ yield history
420
+
421
+ elif provider_choice == AIProvider.GEMINI:
422
+ async for chunk in analyzer.stream_gemini_response(
423
+ enhanced_message, gemini_key or DEFAULT_GEMINI_KEY
424
+ ):
425
+ full_response += chunk
426
+ # Add delay between chunks for readability
427
+ await asyncio.sleep(1)
428
+ history[-1]["content"] = full_response
429
+ yield history
 
430
 
431
+ else: # OLLAMA
432
+ response = analyze_with_ollama(model_name, enhanced_message)
433
+ # Simulate streaming for OLLAMA with delay
434
+ words = response.split()
435
+ for i in range(len(words)):
436
+ full_response = " ".join(words[: i + 1])
437
+ await asyncio.sleep(1)
438
+ history[-1]["content"] = full_response
439
+ yield history
440
 
441
  except Exception as e:
442
+ history[-1]["content"] = f"⚠️ Error: {str(e)}"
 
443
  yield history
444
 
445
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  def create_ui():
447
+ # Gunakan analyzer global
448
  global analyzer
449
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
450
 
451
  with gr.Blocks(title="Open Repo AI", theme=gr.themes.Soft()) as app:
452
  # CSS Styling
453
  gr.Markdown("""
454
  <style>
 
455
  .container { max-width: 100% !important; padding: 1rem; }
456
  .mobile-full { width: 100% !important; }
457
+ .file-list { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
458
+ .file-item { display: flex; justify-content: space-between; padding: 5px 0; }
459
+ .file-remove { color: red; cursor: pointer; }
460
+ .example-list {
461
+ padding: 20px;
 
462
  background: #f8f9fa;
463
+ border-radius: 8px;
 
 
 
 
 
 
 
464
  border: 1px solid #e9ecef;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  margin: 10px 0;
 
 
 
466
  }
467
+ .example-list h3 {
468
+ color: #2c3e50;
469
+ margin-top: 20px;
470
+ margin-bottom: 10px;
471
+ font-size: 1.2em;
 
 
 
472
  }
473
+ .example-list h4 {
474
+ color: #34495e;
475
+ margin-top: 15px;
476
+ margin-bottom: 5px;
477
+ font-size: 1.1em;
 
478
  }
479
+ .example-list ul {
 
 
 
 
 
480
  margin: 10px 0;
481
+ padding-left: 20px;
482
  }
483
+ .example-list li {
484
+ margin: 8px 0;
485
+ line-height: 1.5;
486
+ list-style-type: disc;
 
 
 
 
 
 
 
 
 
487
  }
488
+ .example-list code {
489
  background: #e9ecef;
490
+ padding: 2px 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
  border-radius: 4px;
492
+ font-family: monospace;
493
+ font-size: 0.9em;
494
  }
495
+ .example-list strong {
496
+ color: #2c3e50;
497
+ font-weight: bold;
498
  }
499
+ .example-list p {
500
+ margin: 10px 0;
501
+ line-height: 1.5;
 
 
 
502
  }
 
 
503
  @media (max-width: 768px) {
504
  .gr-form { flex-direction: column !important; }
505
  .gr-group { margin: 0.5rem 0 !important; }
506
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  """)
509
 
510
  # Header
511
  with gr.Row(elem_classes="container"):
512
  gr.Markdown(f"""
513
+ # AI Github Repository Chat
514
 
515
+ - Current Date and Time (UTC): {current_time}
 
 
516
  """)
517
 
518
  # Main Tabs Container
519
  with gr.Tabs() as tabs:
520
  # Configuration Tab
521
+ with gr.Tab("Konfigurasi"):
522
  provider = gr.Radio(
523
  choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
524
+ label="AI Providers",
525
  value=AIProvider.XAI,
526
  )
527
 
528
  with gr.Group() as api_settings:
 
529
  with gr.Row():
530
+ xai_key = gr.Textbox(
531
+ label="X.AI (Grok) API Key (opsional)",
532
+ type="password",
533
+ placeholder="Memakai Apikey Kamu Sendiri",
534
+ show_label=True,
535
+ scale=3,
536
+ )
537
  with gr.Column(scale=1):
538
+ gr.Markdown(XAI_API_HELP)
539
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  with gr.Row():
541
+ gemini_key = gr.Textbox(
542
+ label="Gemini API Key",
543
+ type="password",
544
+ placeholder="Opsional - Kosongkan untuk gunakan key default",
545
+ show_label=True,
546
+ scale=3,
547
+ )
548
  with gr.Column(scale=1):
549
+ gr.Markdown(GEMINI_API_HELP)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
 
551
  with gr.Row():
552
  model_dropdown = gr.Dropdown(
 
557
  )
558
 
559
  # Repository Analysis Tab
560
+ with gr.Tab("Analisis Repository"):
561
  with gr.Group():
562
+ # Repository URL and Token inputs
563
  with gr.Row():
564
  repo_url = gr.Textbox(
565
  label="URL Repository GitHub",
 
567
  elem_classes="mobile-full",
568
  )
569
 
 
570
  with gr.Row():
571
  with gr.Column(scale=2):
572
  github_token = gr.Textbox(
573
+ label="Token GitHub (opsional)",
574
  type="password",
575
+ placeholder="Masukkan github token jika repo private",
576
  elem_classes="mobile-full",
577
  )
578
+ gr.Markdown(GITHUB_TOKEN_HELP)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  with gr.Column(scale=1):
580
  branch = gr.Textbox(
581
  label="Branch (opsional)",
 
583
  elem_classes="mobile-full",
584
  )
585
 
586
+
587
  clone_button = gr.Button(
588
+ "Analisa Repo",
589
  variant="primary",
590
  elem_classes="mobile-full",
591
  )
592
 
593
+ clone_status = gr.Markdown(
594
+ value="", label="Status Repository", elem_classes="mobile-full"
595
+ )
596
 
597
  # File Selection
598
  with gr.Group():
599
+ gr.Markdown("### File yang Dipilih")
600
  with gr.Row():
601
  file_selector = gr.Dropdown(
602
  label="Pilih File dari Repository",
 
614
  )
615
 
616
  # Examples Tab
617
+ with gr.Tab("Ide Cepat"):
618
+ example_output = gr.HTML(
619
+ value="Pilih file di tab Analisis Repository untuk melihat contoh pertanyaan.",
620
+ label="Contoh Pertanyaan",
621
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
 
623
  # Chat Interface (outside tabs)
624
  with gr.Group():
625
  chat_history = gr.Chatbot(
626
+ label="Open Repo AI Assistant",
627
+ height=300,
628
  show_label=True,
629
  type="messages",
630
  elem_classes="mobile-full",
 
632
 
633
  with gr.Row():
634
  chat_input = gr.Textbox(
635
+ label="Chat Dengan Repository",
636
+ placeholder="Ketik di sini...",
637
  lines=3,
638
  elem_classes="mobile-full",
639
  )
640
+ send_button = gr.Button("Kirim", variant="primary")
641
+ clear_button = gr.Button("Bersihkan", variant="secondary")
 
642
 
643
  # Event Handlers
644
  def handle_clone(repo_url, github_token, branch):
645
  if not repo_url:
646
  return (
647
+ "URL repository diperlukan!",
648
  gr.Dropdown(choices=[]),
649
  "<div class='file-list'>Belum ada file yang dipilih</div>",
650
  )
 
675
  html += "</div>"
676
  return html
677
 
678
+ def generate_examples(selected_files):
679
+ if not selected_files:
680
+ return """
681
+ <div class='example-list'>
682
+ <h3>Pilih File Terlebih Dahulu</h3>
683
+ <p>Silakan pilih file di tab Analisis Repository untuk melihat contoh pertanyaan yang relevan.</p>
684
+ </div>
685
+ """
686
+
687
+ examples = "<div class='example-list'>"
688
+
689
+ # General examples for any file
690
+ examples += """
691
+ <h3>Contoh Pertanyaan Umum:</h3>
692
+ <ul>
693
+ """
694
+
695
+ file_names = ", ".join(
696
+ [f"<code>{f.split('/')[-1]}</code>" for f in selected_files]
697
+ )
698
+ examples += f"""
699
+ <li><strong>Analisis Kode:</strong> "Jelaskan logika dan fungsi utama dari {file_names}"</li>
700
+ <li><strong>Deteksi Bug:</strong> "Apakah ada potensi bug atau masalah keamanan di file-file ini?"</li>
701
+ <li><strong>Best Practices:</strong> "Bagaimana cara mengoptimalkan kode di file-file ini?"</li>
702
+ """
703
+
704
+ # Specific examples based on file types
705
+ for file in selected_files:
706
+ filename = file.split("/")[-1]
707
+ ext = filename.split(".")[-1].lower() if "." in filename else ""
708
+ examples += f"<h4>Contoh untuk {filename}:</h4><ul>"
709
+
710
+ if ext in ["py", "js", "java", "cpp", "c", "go"]:
711
+ examples += f"""
712
+ <li>"Jelaskan fungsi-fungsi utama di {filename}"</li>
713
+ <li>"Bagaimana cara mengoptimalkan performa di {filename}?"</li>
714
+ <li>"Buat unit test untuk fungsi-fungsi di {filename}"</li>
715
+ """
716
+ elif ext in ["html", "css"]:
717
+ examples += f"""
718
+ <li>"Analisis struktur dan layout dari {filename}"</li>
719
+ <li>"Bagaimana cara membuat {filename} lebih responsif?"</li>
720
+ <li>"Optimasi untuk mobile view di {filename}"</li>
721
+ """
722
+ elif ext == "md":
723
+ examples += f"""
724
+ <li>"Ringkas isi dokumentasi dari {filename}"</li>
725
+ <li>"Buat tabel konten untuk {filename}"</li>
726
+ <li>"Perbaiki formatting di {filename}"</li>
727
+ """
728
+ elif ext in ["json", "yaml", "yml"]:
729
+ examples += f"""
730
+ <li>"Validasi struktur data di {filename}"</li>
731
+ <li>"Jelaskan konfigurasi di {filename}"</li>
732
+ <li>"Optimasi format di {filename}"</li>
733
+ """
734
+ elif ext == "dockerfile":
735
+ examples += f"""
736
+ <li>"Analisis keamanan dari {filename}"</li>
737
+ <li>"Optimasi multi-stage build di {filename}"</li>
738
+ <li>"Best practices untuk {filename}"</li>
739
+ """
740
+ else:
741
+ examples += f"""
742
+ <li>"Analisis isi dari {filename}"</li>
743
+ <li>"Jelaskan struktur dan tujuan {filename}"</li>
744
+ <li>"Saran perbaikan untuk {filename}"</li>
745
+ """
746
+ examples += "</ul>"
747
+
748
+ examples += """
749
+ <h3>Tips:</h3>
750
+ <ul>
751
+ <li>Gunakan pertanyaan yang spesifik dan fokus pada bagian tertentu</li>
752
+ <li>Sebutkan nama file jika bertanya tentang file tertentu</li>
753
+ <li>Jelaskan konteks atau masalah yang ingin diselesaikan</li>
754
+ </ul>
755
+ """
756
+
757
+ examples += "</div>"
758
+ return examples
759
+
760
  def clear_chat_history():
761
  return []
762
 
 
776
  )
777
  except Exception as e:
778
  return gr.Dropdown(
779
+ choices=["grok-2-latest"] if provider_choice == AIProvider.XAI else ["gemini-1.5-flash"],
780
+ value="grok-2-latest" if provider_choice == AIProvider.XAI else "gemini-1.5-flash",
781
  label=f"Model AI (Error: {str(e)})",
782
  )
783
 
 
802
  outputs=[clone_status, file_selector, file_list],
803
  )
804
 
805
+ file_selector.change(
806
+ fn=update_file_list, inputs=[file_selector], outputs=[file_list]
807
+ )
808
 
809
+ # Example tab updates
810
+ tabs.select(
811
+ fn=generate_examples,
812
+ inputs=[file_selector],
813
+ outputs=[example_output],
814
+ api_name=False,
815
+ )
816
 
817
+ # Also update when file selection changes
818
+ file_selector.change(
819
+ fn=generate_examples, inputs=[file_selector], outputs=[example_output]
 
 
 
 
 
 
 
 
 
 
 
 
820
  )
821
 
822
+ # Chat events
823
+ clear_button.click(fn=clear_chat_history, outputs=[chat_history])
824
+
825
  send_button.click(
826
  fn=handle_chat,
827
  inputs=[
 
856
 
857
 
858
  if __name__ == "__main__":
859
+ print(f"""
860
+ 🚀 Memulai Repository Chat Analysis
 
861
  """)
862
 
863
  app = create_ui()
864
+ app.launch(share=True)