ErNewdev0 commited on
Commit
b5df743
·
verified ·
1 Parent(s): 1034afa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +219 -435
app.py CHANGED
@@ -1,14 +1,14 @@
1
  import gradio as gr
2
  import os
 
3
  from pathlib import Path
4
  import subprocess
5
  import requests
6
  import json
7
- import time
8
- import asyncio
9
  from datetime import datetime
10
  import textwrap
11
  import google.generativeai as genai
 
12
  from typing import Generator, AsyncGenerator, List
13
  from openai import AsyncOpenAI
14
  import dotenv
@@ -50,6 +50,8 @@ GEMINI_MODELS = [
50
  "gemini-pro-vision",
51
  ]
52
 
 
 
53
  GITHUB_TOKEN_HELP = """
54
  ### Cara Mendapatkan GitHub Token:
55
 
@@ -95,7 +97,6 @@ Catatan:
95
  - Ideal untuk privasi dan penggunaan offline
96
  """
97
 
98
- # Help texts
99
  XAI_API_HELP = """
100
  ### Cara Mendapatkan X.AI (Grok) API Key:
101
 
@@ -278,246 +279,84 @@ class RepoAnalyzer:
278
  continue
279
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
280
 
281
- async def handle_chat(message, history, provider_choice, model_name, xai_key, gemini_key, selected_files):
282
- if not analyzer.current_repo:
283
- yield history + [[message, "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan."]]
284
- return
285
-
286
- history = history or []
287
- history.append([message, ""])
288
-
289
- try:
290
- # Add context about selected files to the prompt
291
- file_context = ""
292
- if selected_files:
293
- file_context = "\n\nFile yang dipilih:\n"
294
- for file in selected_files:
295
- content = analyzer.repo_content.get(file, "")
296
- escaped_content = content.replace('`', r'\`')
297
-
298
- html = (
299
- '<div class="wrapper-artifact">'
300
- f'<div class="header-artifact">'
301
- f'<span>{file}</span>'
302
- f'<button class="copy-button" onclick="copyToClipboard(`{escaped_content}`)">Copy</button>'
303
- '</div>'
304
- '<div class="content-artifact">'
305
- f'<pre><code>{content}</code></pre>'
306
- '</div>'
307
- '</div>'
308
- )
309
- file_context += html
310
-
311
- enhanced_message = f"{message}\n{file_context}"
312
-
313
- full_response = ""
314
- if provider_choice == AIProvider.XAI:
315
- async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
316
- # Wrap code blocks in custom styling
317
- chunk = process_code_blocks(chunk)
318
- full_response += chunk
319
- await asyncio.sleep(0.08)
320
- history[-1][1] = full_response
321
- yield history
322
-
323
- elif provider_choice == AIProvider.GEMINI:
324
- async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
325
- chunk = process_code_blocks(chunk)
326
- full_response += chunk
327
- await asyncio.sleep(0.08)
328
- history[-1][1] = full_response
329
- yield history
330
-
331
- else: # OLLAMA
332
- response = analyze_with_ollama(model_name, enhanced_message)
333
- response = process_code_blocks(response)
334
- words = response.split()
335
- for i in range(len(words)):
336
- full_response = " ".join(words[:i+1])
337
- await asyncio.sleep(0.08)
338
- history[-1][1] = full_response
339
- yield history
340
-
341
- except Exception as e:
342
- history[-1][1] = f"⚠️ Error: {str(e)}"
343
- yield history
344
-
345
- def process_code_blocks(text):
346
- """Process markdown code blocks to use custom artifact styling"""
347
- import re
348
-
349
- # Pattern for code blocks with language specification
350
- pattern = r"```(\w+)\n(.*?)```"
351
-
352
- def replace_code_block(match):
353
- language = match.group(1)
354
- code = match.group(2)
355
- escaped_code = code.replace('`', r'\`')
356
-
357
- html = (
358
- '<div class="wrapper-artifact">'
359
- f'<div class="header-artifact">'
360
- f'<span>{language}</span>'
361
- f'<button class="copy-button" onclick="copyToClipboard(`{escaped_code}`)">Copy</button>'
362
- '</div>'
363
- '<div class="content-artifact">'
364
- f'<pre><code>{code}</code></pre>'
365
- '</div>'
366
- '</div>'
367
- )
368
- return html
369
-
370
- # Replace all code blocks in the text
371
- processed_text = re.sub(pattern, replace_code_block, text, flags=re.DOTALL)
372
- return processed_text
373
-
374
- analyzer = RepoAnalyzer()
375
-
376
  def create_ui():
377
- # Get local time
378
- local_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
379
  analyzer = RepoAnalyzer()
 
380
 
381
  with gr.Blocks(title="Repository Chat Analysis", theme=gr.themes.Soft()) as app:
382
  gr.Markdown("""
383
- <style>
384
- .container { max-width: 100% !important; padding: 1rem; }
385
- .mobile-full { width: 100% !important; }
386
- .file-list { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
387
- .file-item { display: flex; justify-content: space-between; padding: 5px 0; }
388
- .file-remove { color: red; cursor: pointer; }
389
-
390
- /* Enhanced code block styling */
391
- .wrapper-artifact {
392
- border: 1px solid #e0e0e0;
393
- border-radius: 8px;
394
- margin: 16px 0;
395
- background: #f8f9fa;
396
- }
397
- .header-artifact {
398
- padding: 8px 16px;
399
- background: #f1f3f4;
400
- border-bottom: 1px solid #e0e0e0;
401
- border-radius: 8px 8px 0 0;
402
- font-family: monospace;
403
- display: flex;
404
- justify-content: space-between;
405
- align-items: center;
406
- }
407
- .content-artifact {
408
- padding: 16px;
409
- overflow-x: auto;
410
- background: #ffffff;
411
- border-radius: 0 0 8px 8px;
412
- }
413
- .content-artifact pre {
414
- margin: 0;
415
- padding: 0;
416
- }
417
- .copy-button {
418
- background: #e0e0e0;
419
- border: none;
420
- padding: 4px 8px;
421
- border-radius: 4px;
422
- cursor: pointer;
423
- font-size: 12px;
424
- }
425
- .copy-button:hover {
426
- background: #d0d0d0;
427
- }
428
 
429
- /* Chat interface styling */
430
- .fullscreen-chat {
431
- height: calc(100vh - 200px) !important;
432
- margin: 20px 0;
433
- }
434
- .chat-input {
435
- position: sticky;
436
- bottom: 0;
437
- background: white;
438
- padding: 10px 0;
439
- border-top: 1px solid #eee;
440
- }
441
- .chat-message {
442
- margin: 8px 0;
443
- }
444
- @media (max-width: 768px) {
445
- .gr-form { flex-direction: column !important; }
446
- .gr-group { margin: 0.5rem 0 !important; }
447
- .fullscreen-chat {
448
- height: calc(100vh - 300px) !important;
449
- }
450
- }
451
- </style>
452
- <script>
453
- function copyToClipboard(text) {
454
- navigator.clipboard.writeText(text).then(() => {
455
- const button = event.target;
456
- button.textContent = 'Copied!';
457
- setTimeout(() => {
458
- button.textContent = 'Copy';
459
- }, 2000);
460
- });
461
- }
462
- </script>
463
  """)
464
-
465
- # Header
466
- with gr.Row(elem_classes="container"):
467
- gr.Markdown(f"# 🤖 Repository Chat Analysis\n\n📅 {local_time}")
468
 
469
- # Main Interface
470
  with gr.Tabs() as tabs:
471
- # Configuration Tab
472
- with gr.Tab("🛠️ Konfigurasi", elem_classes="mobile-full"):
473
- provider = gr.Radio(
474
- choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
475
- label="Penyedia AI",
476
- value=AIProvider.XAI,
477
- interactive=True,
478
- elem_classes="mobile-full"
479
- )
480
-
481
- with gr.Group() as api_settings:
482
- # XAI API Key
483
- with gr.Group():
484
- with gr.Row():
485
- xai_key = gr.Textbox(
486
- label="X.AI (Grok) API Key",
487
- type="password",
488
- placeholder="Opsional - Klik icon (?) untuk info",
489
- show_label=True,
490
- scale=3
491
- )
492
- with gr.Column(scale=1):
493
- gr.Markdown(XAI_API_HELP)
494
-
495
- # Gemini API Key
496
- with gr.Group():
497
- with gr.Row():
498
- gemini_key = gr.Textbox(
499
- label="Gemini API Key",
500
- type="password",
501
- placeholder="Opsional - Kosongkan untuk gunakan key default",
502
- show_label=True,
503
- scale=3
504
- )
505
- with gr.Column(scale=1):
506
- gr.Markdown(GEMINI_API_HELP)
507
 
508
- # Model Selection
 
 
 
 
 
 
 
509
  with gr.Row():
510
- model_dropdown = gr.Dropdown(
511
- label="Model AI",
512
- choices=XAI_MODELS,
513
- value="grok-2-latest",
514
- interactive=True,
515
- elem_classes="mobile-full"
 
 
 
 
 
 
 
 
516
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
- # Repository Analysis Tab
519
  with gr.Tab("📊 Analisis Repository", elem_classes="mobile-full"):
520
- # Repository Input Section
521
  with gr.Group():
522
  with gr.Row():
523
  repo_url = gr.Textbox(
@@ -540,8 +379,7 @@ def create_ui():
540
  placeholder="main",
541
  elem_classes="mobile-full"
542
  )
543
-
544
- # Clone Repository Section
545
  clone_button = gr.Button(
546
  "🔄 Clone Repository",
547
  variant="primary",
@@ -554,7 +392,7 @@ def create_ui():
554
  elem_classes="mobile-full"
555
  )
556
 
557
- # File Selection Section
558
  with gr.Group():
559
  gr.Markdown("### 📎 File yang Dipilih")
560
 
@@ -571,223 +409,169 @@ def create_ui():
571
  label="Daftar File Terpilih"
572
  )
573
 
574
- # Chat Interface (Outside tabs for full width)
575
- with gr.Group():
576
- chat_history = gr.Chatbot(
577
- label="📝 Riwayat Chat",
578
- elem_classes="fullscreen-chat",
579
- height=None,
580
- show_label=True,
581
- type="messages"
582
- )
583
-
584
- with gr.Group(elem_classes="chat-input"):
585
- chat_input = gr.Textbox(
586
- label="💭 Tanyakan tentang Repository",
587
- placeholder="Ketik pertanyaan Anda di sini...",
588
- lines=3,
589
- elem_classes="mobile-full"
 
 
 
 
 
 
 
 
 
 
 
 
590
  )
591
- with gr.Row():
592
- clear_button = gr.Button("🧹 Bersihkan", variant="secondary")
593
- send_button = gr.Button("📤 Kirim", variant="primary")
594
 
595
- # Event Handlers
596
- def clear_chat_history():
597
- return None
 
 
 
598
 
599
- def handle_clone(repo_url, github_token, branch):
600
- if not repo_url:
601
- return "⚠️ URL repository diperlukan!", [], []
602
-
603
- success, message = analyzer.clone_repository(repo_url, github_token, branch)
604
-
605
- if success:
606
- # Get files with metadata for better display
607
- files = []
608
- file_metadata = []
609
 
610
- for file_path in analyzer.repo_content.keys():
611
- files.append(file_path)
612
- content = analyzer.repo_content[file_path]
613
- size = len(content.encode('utf-8'))
614
- file_metadata.append({
615
- 'path': file_path,
616
- 'size': f"{size / 1024:.1f} KB",
617
- 'lines': len(content.splitlines())
618
- })
619
-
620
- # Create rich HTML display for clone status
621
- status_html = f"""
622
- <div style="padding: 1rem; background: #f0f8ff; border-radius: 8px; border: 1px solid #add8e6;">
623
- <h3 style="margin-top: 0;">✅ Repository berhasil di-clone!</h3>
624
- <p><strong>Nama:</strong> {analyzer.current_repo}</p>
625
- <p><strong>Jumlah file:</strong> {len(files)}</p>
626
- <p><strong>Status:</strong> Siap untuk analisis</p>
627
- </div>
628
- """
629
-
630
- # Create initial file list display
631
- file_list_html = create_file_list_html(file_metadata)
632
 
633
- return status_html, files, file_list_html
634
- return message, [], []
 
 
 
 
635
 
636
- def create_file_list_html(file_metadata):
637
- if not file_metadata:
638
- return "<div class='file-list'>Belum ada file yang dipilih</div>"
639
-
640
- html = "<div class='file-list'>"
641
- html += "<style>\n"
642
- html += ".file-list { max-height: 300px; overflow-y: auto; }\n"
643
- html += ".file-item { display: flex; justify-content: space-between; padding: 8px; border-bottom: 1px solid #eee; }\n"
644
- html += ".file-item:hover { background: #f5f5f5; }\n"
645
- html += ".file-info { display: flex; gap: 1rem; color: #666; font-size: 0.9em; }\n"
646
- html += "</style>"
647
-
648
- for metadata in file_metadata:
649
- html += f"""
650
- <div class='file-item'>
651
- <div class='file-path'>{metadata['path']}</div>
652
- <div class='file-info'>
653
- <span>{metadata['size']}</span>
654
- <span>{metadata['lines']} lines</span>
655
- </div>
656
- </div>
657
- """
658
- html += "</div>"
659
- return html
660
-
661
- def update_file_list(selected):
662
- if not selected:
663
- return "<div class='file-list'>Belum ada file yang dipilih</div>"
664
-
665
- file_metadata = []
666
- for file_path in selected:
667
- if file_path in analyzer.repo_content:
668
- content = analyzer.repo_content[file_path]
669
- size = len(content.encode('utf-8'))
670
- file_metadata.append({
671
- 'path': file_path,
672
- 'size': f"{size / 1024:.1f} KB",
673
- 'lines': len(content.splitlines())
674
- })
675
-
676
- return create_file_list_html(file_metadata)
677
 
678
- # Modified handle_chat function with better file context handling
679
- async def handle_chat(message, history, provider_choice, model_name, xai_key, gemini_key, selected_files):
680
- if not analyzer.current_repo:
681
- yield history + [[message, "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan."]]
682
- return
683
-
684
- history = history or []
685
- history.append([message, ""])
686
-
687
- try:
688
- # Improved file context formatting
689
- file_context = ""
690
- if selected_files:
691
- file_context = "\n\nAnalisis berdasarkan file yang dipilih:\n"
692
- for file in selected_files:
693
- content = analyzer.repo_content.get(file, "")
694
- file_context += f"\n### File: {file}\n```\n{content}\n```\n"
695
-
696
- enhanced_message = f"{message}\n{file_context}"
697
-
698
- # Rest of the handle_chat function remains the same
699
- full_response = ""
700
- if provider_choice == AIProvider.XAI:
701
- async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
702
- chunk = process_code_blocks(chunk)
703
- full_response += chunk
704
- await asyncio.sleep(0.08)
705
- history[-1][1] = full_response
706
- yield history
707
-
708
- elif provider_choice == AIProvider.GEMINI:
709
- async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
710
- chunk = process_code_blocks(chunk)
711
- full_response += chunk
712
- await asyncio.sleep(0.08)
713
- history[-1][1] = full_response
714
- yield history
715
-
716
- else: # OLLAMA
717
- response = analyze_with_ollama(model_name, enhanced_message)
718
- response = process_code_blocks(response)
719
- words = response.split()
720
- for i in range(len(words)):
721
- full_response = " ".join(words[:i+1])
722
- await asyncio.sleep(0.08)
723
- history[-1][1] = full_response
 
 
724
  yield history
725
-
726
- except Exception as e:
727
- history[-1][1] = f"⚠️ Error: {str(e)}"
728
- yield history
729
 
730
- # Add additional CSS for improved repository display
731
- gr.Markdown("""
732
- <style>
733
- /* Additional styles for repository content */
734
- .repo-status {
735
- margin: 1rem 0;
736
- padding: 1rem;
737
- background: #f0f8ff;
738
- border-radius: 8px;
739
- border: 1px solid #add8e6;
740
- }
741
- .file-list {
742
- border: 1px solid #ddd;
743
- border-radius: 8px;
744
- margin-top: 1rem;
745
- background: white;
746
- }
747
- .file-item {
748
- transition: background-color 0.2s;
749
- }
750
- .file-info {
751
- opacity: 0.7;
752
- }
753
- .file-path {
754
- font-family: monospace;
755
- overflow: hidden;
756
- text-overflow: ellipsis;
757
- white-space: nowrap;
758
- }
759
-
760
- /* Improved scrollbars for file list */
761
- .file-list::-webkit-scrollbar {
762
- width: 8px;
763
- height: 8px;
764
- }
765
- .file-list::-webkit-scrollbar-track {
766
- background: #f1f1f1;
767
- border-radius: 4px;
768
- }
769
- .file-list::-webkit-scrollbar-thumb {
770
- background: #888;
771
- border-radius: 4px;
772
- }
773
- .file-list::-webkit-scrollbar-thumb:hover {
774
- background: #555;
775
- }
776
- </style>
777
- """)
778
 
779
- return app
780
 
781
  if __name__ == "__main__":
782
  print(f"""
783
  🚀 Memulai Repository Chat Analysis
784
- 📅 Current Date and Time (UTC): {datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")}
785
  """)
786
 
787
  app = create_ui()
788
- app.launch(
789
- share=True,
790
- show_error=True,
791
- server_name="0.0.0.0",
792
- server_port=7860
793
- )
 
1
  import gradio as gr
2
  import os
3
+ import time
4
  from pathlib import Path
5
  import subprocess
6
  import requests
7
  import json
 
 
8
  from datetime import datetime
9
  import textwrap
10
  import google.generativeai as genai
11
+ import asyncio
12
  from typing import Generator, AsyncGenerator, List
13
  from openai import AsyncOpenAI
14
  import dotenv
 
50
  "gemini-pro-vision",
51
  ]
52
 
53
+ # Help texts
54
+
55
  GITHUB_TOKEN_HELP = """
56
  ### Cara Mendapatkan GitHub Token:
57
 
 
97
  - Ideal untuk privasi dan penggunaan offline
98
  """
99
 
 
100
  XAI_API_HELP = """
101
  ### Cara Mendapatkan X.AI (Grok) API Key:
102
 
 
279
  continue
280
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  def create_ui():
 
 
283
  analyzer = RepoAnalyzer()
284
+ selected_files = []
285
 
286
  with gr.Blocks(title="Repository Chat Analysis", theme=gr.themes.Soft()) as app:
287
  gr.Markdown("""
288
+ <style>
289
+ .container { max-width: 100% !important; padding: 1rem; }
290
+ .mobile-full { width: 100% !important; }
291
+ .file-list { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
292
+ .file-item { display: flex; justify-content: space-between; padding: 5px 0; }
293
+ .file-remove { color: red; cursor: pointer; }
294
+ @media (max-width: 768px) {
295
+ .gr-form { flex-direction: column !important; }
296
+ .gr-group { margin: 0.5rem 0 !important; }
297
+ }
298
+ </style>
299
+ """)
300
+
301
+ with gr.Row(elem_classes="container"):
302
+ gr.Markdown(f"""
303
+ # AI Github Repository Chat
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
+ 📅 Waktu: {CURRENT_TIME}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  """)
 
 
 
 
307
 
 
308
  with gr.Tabs() as tabs:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
+ with gr.Tab("🛠️ Konfigurasi"):
311
+ provider = gr.Radio(
312
+ choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
313
+ label="Penyedia AI",
314
+ value=AIProvider.XAI
315
+ )
316
+
317
+ with gr.Group() as api_settings:
318
  with gr.Row():
319
+ xai_key = gr.Textbox(
320
+ label="X.AI (Grok) API Key",
321
+ type="password",
322
+ placeholder="Opsional - Klik icon (?) untuk info. Kosongkan untuk gunakan key default",
323
+ show_label=True
324
+ )
325
+ gr.Markdown(XAI_API_HELP)
326
+
327
+ with gr.Row():
328
+ gemini_key = gr.Textbox(
329
+ label="Gemini API Key",
330
+ type="password",
331
+ placeholder="Opsional - Kosongkan untuk gunakan key default",
332
+ show_label=True
333
  )
334
+ gr.Markdown(GEMINI_API_HELP)
335
+
336
+ # Model selection based on provider
337
+ with gr.Row():
338
+ model_dropdown = gr.Dropdown(
339
+ label="Model AI",
340
+ choices=XAI_MODELS,
341
+ value="grok-2-latest",
342
+ interactive=True
343
+ )
344
+
345
+ def update_model_list(provider_choice):
346
+ if provider_choice == AIProvider.XAI:
347
+ return gr.Dropdown(choices=XAI_MODELS, value="grok-2-latest")
348
+ elif provider_choice == AIProvider.GEMINI:
349
+ return gr.Dropdown(choices=GEMINI_MODELS, value="gemini-pro")
350
+ else: # OLLAMA
351
+ return gr.Dropdown(choices=OLLAMA_MODELS, value="llama2")
352
+
353
+ provider.change(
354
+ fn=update_model_list,
355
+ inputs=[provider],
356
+ outputs=[model_dropdown]
357
+ )
358
 
 
359
  with gr.Tab("📊 Analisis Repository", elem_classes="mobile-full"):
 
360
  with gr.Group():
361
  with gr.Row():
362
  repo_url = gr.Textbox(
 
379
  placeholder="main",
380
  elem_classes="mobile-full"
381
  )
382
+
 
383
  clone_button = gr.Button(
384
  "🔄 Clone Repository",
385
  variant="primary",
 
392
  elem_classes="mobile-full"
393
  )
394
 
395
+ # Add file selection components
396
  with gr.Group():
397
  gr.Markdown("### 📎 File yang Dipilih")
398
 
 
409
  label="Daftar File Terpilih"
410
  )
411
 
412
+ def update_file_list(selected: List[str]) -> str:
413
+ if not selected:
414
+ return "<div class='file-list'>Belum ada file yang dipilih</div>"
415
+
416
+ html = "<div class='file-list'>"
417
+ for file in selected:
418
+ html += f"<div class='file-item'><span>{file}</span></div>"
419
+ html += "</div>"
420
+ return html
421
+
422
+ def handle_clone(repo_url, github_token, branch):
423
+ if not repo_url:
424
+ return "⚠️ URL repository diperlukan!", [], []
425
+
426
+ success, message = analyzer.clone_repository(repo_url, github_token, branch)
427
+
428
+ if success:
429
+ # Get list of files from cloned repository
430
+ files = list(analyzer.repo_content.keys())
431
+ return message, files, []
432
+
433
+ return message, [], []
434
+
435
+ # Connect clone button click event
436
+ clone_button.click(
437
+ fn=handle_clone,
438
+ inputs=[repo_url, github_token, branch],
439
+ outputs=[clone_status, file_selector, file_list]
440
  )
 
 
 
441
 
442
+ # Update file list when selection changes
443
+ file_selector.change(
444
+ fn=update_file_list,
445
+ inputs=[file_selector],
446
+ outputs=[file_list]
447
+ )
448
 
449
+ gr.Markdown("""
450
+ ### 💡 Contoh Pertanyaan:
451
+ - "Jelaskan struktur utama dari repository ini"
452
+ - "Analisis kode di file yang dipilih"
453
+ - "Bagaimana cara memperbaiki [masalah specific] di file-file ini?"
454
+ - "Bandingkan implementasi di file-file yang dipilih"
455
+ """)
 
 
 
456
 
457
+ # Improved chat interface
458
+ with gr.Group():
459
+ chat_input = gr.Textbox(
460
+ label="💭 Tanyakan tentang Repository",
461
+ placeholder="Ketik pertanyaan Anda di sini...",
462
+ lines=3,
463
+ elem_classes="mobile-full"
464
+ )
465
+ send_button = gr.Button(
466
+ "📤 Kirim",
467
+ variant="primary",
468
+ elem_classes="mobile-full"
469
+ )
 
 
 
 
 
 
 
 
 
470
 
471
+ chat_history = gr.Chatbot(
472
+ label="📝 Riwayat Chat",
473
+ height=500,
474
+ show_label=True,
475
+ elem_classes="mobile-full"
476
+ )
477
 
478
+ loading_indicator = gr.HTML(
479
+ '<div id="loading" style="display:none">Memproses permintaan...</div>'
480
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
 
482
+ async def handle_chat(message, history, provider_choice, model_name, xai_key, gemini_key, selected_files):
483
+ if not analyzer.current_repo:
484
+ yield history + [[message, "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan."]]
485
+ return
486
+
487
+ history = history or []
488
+ history.append([message, ""])
489
+
490
+ try:
491
+ # Add context about selected files to the prompt
492
+ file_context = ""
493
+ if selected_files:
494
+ file_context = "\n\nFile yang dipilih:\n"
495
+ for file in selected_files:
496
+ content = analyzer.repo_content.get(file, "")
497
+ file_context += f"\n{file}:\n```\n{content}\n```\n"
498
+
499
+ enhanced_message = f"{message}\n{file_context}"
500
+
501
+ full_response = ""
502
+ if provider_choice == AIProvider.XAI:
503
+ async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
504
+ full_response += chunk
505
+ # Add delay between chunks for readability
506
+ await asyncio.sleep(0.05)
507
+ history[-1][1] = full_response
508
+ yield history
509
+
510
+ elif provider_choice == AIProvider.GEMINI:
511
+ async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
512
+ full_response += chunk
513
+ # Add delay between chunks for readability
514
+ await asyncio.sleep(0.05)
515
+ history[-1][1] = full_response
516
+ yield history
517
+
518
+ else: # OLLAMA
519
+ response = analyze_with_ollama(model_name, enhanced_message)
520
+ # Simulate streaming for OLLAMA with delay
521
+ words = response.split()
522
+ for i in range(len(words)):
523
+ full_response = " ".join(words[:i+1])
524
+ await asyncio.sleep(0.05)
525
+ history[-1][1] = full_response
526
+ yield history
527
+
528
+ except Exception as e:
529
+ history[-1][1] = f"⚠️ Error: {str(e)}"
530
  yield history
 
 
 
 
531
 
532
+ # Connect chat events with file selection
533
+ send_event = send_button.click(
534
+ fn=handle_chat,
535
+ inputs=[
536
+ chat_input,
537
+ chat_history,
538
+ provider,
539
+ model_dropdown,
540
+ xai_key,
541
+ gemini_key,
542
+ file_selector
543
+ ],
544
+ outputs=chat_history,
545
+ show_progress=True
546
+ ).then(
547
+ fn=lambda: gr.update(value=""),
548
+ outputs=chat_input
549
+ )
550
+
551
+ input_event = chat_input.submit(
552
+ fn=handle_chat,
553
+ inputs=[
554
+ chat_input,
555
+ chat_history,
556
+ provider,
557
+ model_dropdown,
558
+ xai_key,
559
+ gemini_key,
560
+ file_selector
561
+ ],
562
+ outputs=chat_history,
563
+ show_progress=True
564
+ ).then(
565
+ fn=lambda: gr.update(value=""),
566
+ outputs=chat_input
567
+ )
 
 
 
 
 
 
 
 
 
 
 
 
568
 
569
+ return app
570
 
571
  if __name__ == "__main__":
572
  print(f"""
573
  🚀 Memulai Repository Chat Analysis
 
574
  """)
575
 
576
  app = create_ui()
577
+ app.launch(share=True)