ErNewdev0 commited on
Commit
73a4f15
·
verified ·
1 Parent(s): 49bae1b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +303 -283
app.py CHANGED
@@ -21,7 +21,10 @@ 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("XAI_API_KEY", "xai-vfjhklL384Z4HKdItsZomqpFlXubTZJAFnISQUpV7dE8lRnWwYBVPSCxSTlu08wDbAcv720bx2dDiQ9x")
 
 
 
25
  DEFAULT_GEMINI_KEY = os.getenv("GEMINI_API_KEY")
26
 
27
  # API settings
@@ -37,7 +40,7 @@ OLLAMA_MODELS = [
37
  "starling-lm",
38
  "dolphin-phi",
39
  "phi",
40
- "orca-mini"
41
  ]
42
 
43
  XAI_MODELS = [
@@ -110,37 +113,41 @@ Note:
110
  - Masukkan API key Anda sendiri jika default mencapai limit
111
  """
112
 
 
113
  class AIProvider:
114
  OLLAMA = "ollama"
115
  GEMINI = "gemini"
116
  XAI = "xai"
117
 
 
118
  class RepoAnalyzer:
119
  def __init__(self):
120
  self.current_repo = None
121
  self.repo_content = {}
122
  self.chat_history = []
123
 
124
- async def stream_xai_response(self, prompt: str, api_key: str = None, model: str = "grok-2-latest") -> AsyncGenerator[str, None]:
 
 
125
  """Stream response dari X.AI (Grok) API"""
126
  try:
127
  # Use default key if none provided
128
  actual_key = api_key if api_key else DEFAULT_XAI_KEY
129
-
130
  if not actual_key:
131
  yield "⚠️ API Key X.AI diperlukan. Gunakan key Anda sendiri atau tunggu reset limit default key."
132
  return
133
 
134
- client = AsyncOpenAI(
135
- api_key=actual_key,
136
- base_url=XAI_BASE_URL
137
- )
138
 
139
  # Prepare messages with repository context if available
140
  messages = [
141
- {"role": "system", "content": "Anda adalah asisten AI yang membantu menganalisis repository code. Berikan respons dalam Bahasa Indonesia."}
 
 
 
142
  ]
143
-
144
  if self.current_repo:
145
  context = f"Repository: {self.current_repo}\n\n"
146
  repo_files = "\n".join(list(self.repo_content.keys()))
@@ -150,9 +157,7 @@ class RepoAnalyzer:
150
  messages.append({"role": "user", "content": prompt})
151
 
152
  stream = await client.chat.completions.create(
153
- model=model,
154
- messages=messages,
155
- stream=True
156
  )
157
 
158
  full_response = ""
@@ -170,7 +175,9 @@ class RepoAnalyzer:
170
  print(error_msg)
171
  yield error_msg
172
 
173
- async def stream_gemini_response(self, prompt: str, api_key: str) -> AsyncGenerator[str, None]:
 
 
174
  """Stream response dari Gemini API"""
175
  try:
176
  if not api_key:
@@ -178,110 +185,126 @@ class RepoAnalyzer:
178
  return
179
 
180
  genai.configure(api_key=api_key)
181
- model = genai.GenerativeModel('gemini-pro')
182
-
183
  # Tambahkan konteks repository jika ada
184
  if self.current_repo:
185
  context = f"Repository: {self.current_repo}\n\n"
186
  repo_files = "\n".join(list(self.repo_content.keys()))
187
  context += f"Files in repository:\n{repo_files}\n\n"
188
  prompt = context + prompt
189
-
190
  response = model.generate_content(
191
  prompt,
192
- generation_config={
193
- "temperature": 0.7,
194
- "top_p": 0.8,
195
- "top_k": 40
196
- },
197
- stream=True
198
  )
199
-
200
  full_response = ""
201
  async for chunk in response:
202
  if chunk.text:
203
  full_response += chunk.text
204
  yield chunk.text
205
-
206
  self.chat_history.append({"role": "user", "content": prompt})
207
  self.chat_history.append({"role": "assistant", "content": full_response})
208
-
209
  except Exception as e:
210
  error_msg = f"⚠️ Error dalam Gemini API: {str(e)}\n\nPastikan API Key valid dan memiliki kuota yang cukup."
211
  print(error_msg)
212
  yield error_msg
213
 
214
- def clone_repository(self, repo_url: str, github_token: str, branch: str = None) -> tuple[bool, str]:
 
 
215
  """Clone repository GitHub dengan autentikasi"""
216
  if not repo_url:
217
  return False, "⚠️ URL repository diperlukan"
218
 
219
- repo_name = repo_url.split('/')[-1].replace('.git', '')
220
-
221
  if os.path.exists(repo_name):
222
- subprocess.run(['rm', '-rf', repo_name], check=True)
223
 
224
  try:
225
- owner_repo = '/'.join(repo_url.split('/')[-2:])
226
-
227
  # Cek apakah repository private
228
- headers = {'Authorization': f'token {github_token}'} if github_token else {}
229
- repo_check = requests.get(f"https://api.github.com/repos/{owner_repo}", headers=headers)
230
-
 
 
231
  if repo_check.status_code == 404:
232
  return False, "⚠️ Repository tidak ditemukan. Periksa URL repository."
233
  elif repo_check.status_code == 401:
234
- return False, "⚠️ Token GitHub tidak valid. Klik icon bantuan (?) untuk panduan mendapatkan token."
235
- elif repo_check.status_code == 403 and repo_check.json().get('private', False):
236
- return False, "⚠️ Ini adalah repository private. Token GitHub dengan akses 'repo' diperlukan."
 
 
 
 
 
 
 
 
237
 
238
- auth_url = f"https://{github_token}@github.com/{owner_repo}" if github_token else f"https://github.com/{owner_repo}"
 
 
 
 
239
 
240
- cmd = ['git', 'clone']
241
  if branch:
242
- cmd.extend(['--branch', branch])
243
  cmd.append(auth_url)
244
 
245
  process = subprocess.run(
246
  cmd,
247
  capture_output=True,
248
  text=True,
249
- env=dict(os.environ, GIT_ASKPASS='echo', GIT_TERMINAL_PROMPT='0')
250
  )
251
 
252
  if process.returncode == 0:
253
  self.current_repo = repo_name
254
  # Scan dan simpan konten repository
255
  file_count = 0
256
- for file_path in Path(repo_name).rglob('*'):
257
- if file_path.is_file() and '.git' not in str(file_path):
258
  success, content = self.read_file_safely(str(file_path))
259
  if success:
260
  self.repo_content[str(file_path)] = content
261
  file_count += 1
262
-
263
- return True, f"✅ Repository berhasil di-clone!\n\nNama: {repo_name}\nJumlah file: {file_count}\n\nAnda sekarang bisa mengajukan pertanyaan tentang repository ini."
 
 
 
264
  else:
265
  return False, f"⚠️ Gagal clone repository:\n{process.stderr}"
266
-
267
  except Exception as e:
268
  return False, f"⚠️ Error: {str(e)}"
269
 
270
  def read_file_safely(self, file_path: str) -> tuple[bool, str]:
271
  """Baca file dengan aman menggunakan berbagai encoding"""
272
- encodings = ['utf-8', 'latin-1', 'cp1252']
273
  for encoding in encodings:
274
  try:
275
- with open(file_path, 'r', encoding=encoding) as f:
276
  content = f.read()
277
  return True, content
278
  except Exception as e:
279
  continue
280
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
281
 
 
282
  def create_ui():
283
  analyzer = RepoAnalyzer()
284
-
285
  with gr.Blocks(title="Repository Chat Analysis", theme=gr.themes.Soft()) as app:
286
  gr.Markdown("""
287
  <style>
@@ -310,9 +333,9 @@ def create_ui():
310
  provider = gr.Radio(
311
  choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
312
  label="Penyedia AI",
313
- value=AIProvider.XAI
314
  )
315
-
316
  with gr.Group() as api_settings:
317
  with gr.Row():
318
  xai_key = gr.Textbox(
@@ -320,18 +343,18 @@ def create_ui():
320
  type="password",
321
  placeholder="Opsional - Klik icon (?) untuk info",
322
  show_label=True,
323
- scale=3
324
  )
325
  with gr.Column(scale=1):
326
  gr.Markdown(XAI_API_HELP)
327
-
328
  with gr.Row():
329
  gemini_key = gr.Textbox(
330
  label="Gemini API Key",
331
  type="password",
332
  placeholder="Opsional - Kosongkan untuk gunakan key default",
333
  show_label=True,
334
- scale=3
335
  )
336
  with gr.Column(scale=1):
337
  gr.Markdown(GEMINI_API_HELP)
@@ -342,7 +365,7 @@ def create_ui():
342
  label="Model AI",
343
  choices=XAI_MODELS,
344
  value="grok-2-latest",
345
- interactive=True
346
  )
347
 
348
  # Repository Analysis Tab
@@ -351,47 +374,40 @@ def create_ui():
351
  with gr.Row():
352
  repo_url = gr.Textbox(
353
  label="URL Repository GitHub",
354
- placeholder="https://github.com/username/repository"
355
  )
356
-
357
  with gr.Row():
358
  with gr.Column(scale=2):
359
  github_token = gr.Textbox(
360
- label="Token GitHub",
361
  type="password",
362
- placeholder="Klik icon (?) untuk panduan"
363
  )
364
  with gr.Column(scale=1):
365
  branch = gr.Textbox(
366
- label="Branch (opsional)",
367
- placeholder="main"
368
  )
369
 
370
- clone_button = gr.Button(
371
- "🔄 Clone Repository",
372
- variant="primary"
373
- )
374
-
375
- clone_status = gr.Markdown(
376
- value="",
377
- label="Status Repository"
378
- )
379
 
380
  # File Selection
381
  with gr.Group():
382
  gr.Markdown("### 📎 File yang Dipilih")
383
-
384
  with gr.Row():
385
  file_selector = gr.Dropdown(
386
  label="Pilih File dari Repository",
387
  choices=[],
388
  multiselect=True,
389
- value=[] # Initialize with empty list
390
  )
391
-
392
  file_list = gr.HTML(
393
  value="<div class='file-list'>Belum ada file yang dipilih</div>",
394
- label="Daftar File Terpilih"
395
  )
396
 
397
  # Chat Interface
@@ -400,14 +416,14 @@ def create_ui():
400
  label="📝 Riwayat Chat",
401
  height=500,
402
  show_label=True,
403
- type="messages" # Fix deprecation warning
404
  )
405
-
406
  with gr.Row():
407
  chat_input = gr.Textbox(
408
  label="💭 Tanyakan tentang Repository",
409
  placeholder="Ketik pertanyaan Anda di sini...",
410
- lines=3
411
  )
412
  send_button = gr.Button("📤 Kirim", variant="primary")
413
 
@@ -419,20 +435,22 @@ def create_ui():
419
  def handle_clone(repo_url, github_token, branch):
420
  if not repo_url:
421
  return "⚠️ URL repository diperlukan!", [], []
422
-
423
  success, message = analyzer.clone_repository(repo_url, github_token, branch)
424
-
425
  if success:
426
  # Get list of files from cloned repository
427
- files = sorted(list(analyzer.repo_content.keys())) # Sort files alphabetically
 
 
428
  return message, files, []
429
-
430
  return message, [], []
431
 
432
  def update_file_list(selected):
433
  if not selected:
434
  return "<div class='file-list'>Belum ada file yang dipilih</div>"
435
-
436
  html = "<div class='file-list'>"
437
  for file in selected:
438
  html += f"<div class='file-item'><span>{file}</span></div>"
@@ -449,239 +467,241 @@ def create_ui():
449
 
450
  # Connect Events
451
  provider.change(
452
- fn=update_model_list,
453
- inputs=[provider],
454
- outputs=[model_dropdown]
455
  )
456
 
457
  clone_button.click(
458
  fn=handle_clone,
459
  inputs=[repo_url, github_token, branch],
460
- outputs=[clone_status, file_selector, file_list]
461
  )
462
 
463
  file_selector.change(
464
- fn=update_file_list,
465
- inputs=[file_selector],
466
- outputs=[file_list]
467
  )
468
 
469
- with gr.Tab("📊 Analisis Repository", elem_classes="mobile-full"):
470
- with gr.Group():
471
- with gr.Row():
472
- repo_url = gr.Textbox(
473
- label="URL Repository GitHub",
474
- placeholder="https://github.com/username/repository",
475
- elem_classes="mobile-full"
476
- )
477
-
478
- with gr.Row():
479
- with gr.Column(scale=2):
480
- github_token = gr.Textbox(
481
- label="Token GitHub",
482
- type="password",
483
- placeholder="Klik icon (?) untuk panduan",
484
- elem_classes="mobile-full"
485
- )
486
- with gr.Column(scale=1):
487
- branch = gr.Textbox(
488
- label="Branch (opsional)",
489
- placeholder="main",
490
- elem_classes="mobile-full"
491
- )
492
-
493
- clone_button = gr.Button(
494
- "🔄 Clone Repository",
495
- variant="primary",
496
- elem_classes="mobile-full"
497
- )
498
-
499
- clone_status = gr.Markdown(
500
- value="",
501
- label="Status Repository",
502
- elem_classes="mobile-full"
503
- )
504
 
505
- # Add file selection components
506
- with gr.Group():
507
- gr.Markdown("### 📎 File yang Dipilih")
508
-
509
- with gr.Row():
510
- file_selector = gr.Dropdown(
511
- label="Pilih File dari Repository",
512
- choices=[],
513
- multiselect=True,
514
- elem_classes="mobile-full"
515
  )
516
-
517
- file_list = gr.HTML(
518
- value="<div class='file-list'>Belum ada file yang dipilih</div>",
519
- label="Daftar File Terpilih"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  )
521
 
522
- def update_file_list(selected: List[str]) -> str:
523
- if not selected:
524
- return "<div class='file-list'>Belum ada file yang dipilih</div>"
525
-
526
- html = "<div class='file-list'>"
527
- for file in selected:
528
- html += f"<div class='file-item'><span>{file}</span></div>"
529
- html += "</div>"
530
- return html
531
-
532
- def handle_clone(repo_url, github_token, branch):
533
- if not repo_url:
534
- return "⚠️ URL repository diperlukan!", [], []
535
-
536
- success, message = analyzer.clone_repository(repo_url, github_token, branch)
537
-
538
- if success:
539
- # Get list of files from cloned repository
540
- files = list(analyzer.repo_content.keys())
541
- return message, files, []
542
-
543
- return message, [], []
544
-
545
- # Connect clone button click event
546
- clone_button.click(
547
- fn=handle_clone,
548
- inputs=[repo_url, github_token, branch],
549
- outputs=[clone_status, file_selector, file_list]
550
  )
551
 
552
- # Update file list when selection changes
553
- file_selector.change(
554
- fn=update_file_list,
555
- inputs=[file_selector],
556
- outputs=[file_list]
 
 
 
 
 
 
 
 
 
 
 
557
  )
558
 
559
- gr.Markdown("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  ### 💡 Contoh Pertanyaan:
561
  - "Jelaskan struktur utama dari repository ini"
562
  - "Analisis kode di file yang dipilih"
563
  - "Bagaimana cara memperbaiki [masalah specific] di file-file ini?"
564
  - "Bandingkan implementasi di file-file yang dipilih"
565
  """)
566
-
567
- # Improved chat interface
568
- with gr.Group():
569
- chat_input = gr.Textbox(
570
- label="💭 Tanyakan tentang Repository",
571
- placeholder="Ketik pertanyaan Anda di sini...",
572
- lines=3,
573
- elem_classes="mobile-full"
574
- )
575
- send_button = gr.Button(
576
- "📤 Kirim",
577
- variant="primary",
578
- elem_classes="mobile-full"
579
- )
580
-
581
- chat_history = gr.Chatbot(
582
- label="📝 Riwayat Chat",
583
- height=500,
584
- show_label=True,
585
- elem_classes="mobile-full"
586
- )
587
 
588
- loading_indicator = gr.HTML(
589
- '<div id="loading" style="display:none">Memproses permintaan...</div>'
 
 
 
 
 
590
  )
591
-
592
- async def handle_chat(message, history, provider_choice, model_name, xai_key, gemini_key, selected_files):
593
- if not analyzer.current_repo:
594
- yield history + [[message, "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan."]]
595
- return
596
-
597
- history = history or []
598
- history.append([message, ""])
599
-
600
- try:
601
- # Add context about selected files to the prompt
602
- file_context = ""
603
- if selected_files:
604
- file_context = "\n\nFile yang dipilih:\n"
605
- for file in selected_files:
606
- content = analyzer.repo_content.get(file, "")
607
- file_context += f"\n{file}:\n```\n{content}\n```\n"
608
-
609
- enhanced_message = f"{message}\n{file_context}"
610
-
611
- full_response = ""
612
- if provider_choice == AIProvider.XAI:
613
- async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
614
- full_response += chunk
615
- # Add delay between chunks for readability
616
- await asyncio.sleep(0.05)
617
- history[-1][1] = full_response
618
- yield history
619
-
620
- elif provider_choice == AIProvider.GEMINI:
621
- async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
622
- full_response += chunk
623
- # Add delay between chunks for readability
624
- await asyncio.sleep(0.05)
625
- history[-1][1] = full_response
626
- yield history
627
-
628
- else: # OLLAMA
629
- response = analyze_with_ollama(model_name, enhanced_message)
630
- # Simulate streaming for OLLAMA with delay
631
- words = response.split()
632
- for i in range(len(words)):
633
- full_response = " ".join(words[:i+1])
634
- await asyncio.sleep(0.05)
635
- history[-1][1] = full_response
636
- yield history
637
-
638
- except Exception as e:
639
- history[-1][1] = f"⚠️ Error: {str(e)}"
640
- yield history
641
-
642
- # Connect chat events with file selection
643
- send_event = send_button.click(
644
- fn=handle_chat,
645
- inputs=[
646
- chat_input,
647
- chat_history,
648
- provider,
649
- model_dropdown,
650
- xai_key,
651
- gemini_key,
652
- file_selector
653
- ],
654
- outputs=chat_history,
655
- show_progress=True
656
- ).then(
657
- fn=lambda: gr.update(value=""),
658
- outputs=chat_input
659
  )
660
 
661
- input_event = chat_input.submit(
662
- fn=handle_chat,
663
- inputs=[
664
- chat_input,
665
- chat_history,
666
- provider,
667
- model_dropdown,
668
- xai_key,
669
- gemini_key,
670
- file_selector
671
- ],
672
- outputs=chat_history,
673
- show_progress=True
674
- ).then(
675
- fn=lambda: gr.update(value=""),
676
- outputs=chat_input
677
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
 
679
  return app
680
 
 
681
  if __name__ == "__main__":
682
  print(f"""
683
  🚀 Memulai Repository Chat Analysis
684
  """)
685
-
686
  app = create_ui()
687
- app.launch(share=True)
 
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
+ "xai-vfjhklL384Z4HKdItsZomqpFlXubTZJAFnISQUpV7dE8lRnWwYBVPSCxSTlu08wDbAcv720bx2dDiQ9x",
27
+ )
28
  DEFAULT_GEMINI_KEY = os.getenv("GEMINI_API_KEY")
29
 
30
  # API settings
 
40
  "starling-lm",
41
  "dolphin-phi",
42
  "phi",
43
+ "orca-mini",
44
  ]
45
 
46
  XAI_MODELS = [
 
113
  - Masukkan API key Anda sendiri jika default mencapai limit
114
  """
115
 
116
+
117
  class AIProvider:
118
  OLLAMA = "ollama"
119
  GEMINI = "gemini"
120
  XAI = "xai"
121
 
122
+
123
  class RepoAnalyzer:
124
  def __init__(self):
125
  self.current_repo = None
126
  self.repo_content = {}
127
  self.chat_history = []
128
 
129
+ async def stream_xai_response(
130
+ self, prompt: str, api_key: str = None, model: str = "grok-2-latest"
131
+ ) -> AsyncGenerator[str, None]:
132
  """Stream response dari X.AI (Grok) API"""
133
  try:
134
  # Use default key if none provided
135
  actual_key = api_key if api_key else DEFAULT_XAI_KEY
136
+
137
  if not actual_key:
138
  yield "⚠️ API Key X.AI diperlukan. Gunakan key Anda sendiri atau tunggu reset limit default key."
139
  return
140
 
141
+ client = AsyncOpenAI(api_key=actual_key, base_url=XAI_BASE_URL)
 
 
 
142
 
143
  # Prepare messages with repository context if available
144
  messages = [
145
+ {
146
+ "role": "system",
147
+ "content": "Anda adalah asisten AI yang membantu menganalisis repository code. Berikan respons dalam Bahasa Indonesia.",
148
+ }
149
  ]
150
+
151
  if self.current_repo:
152
  context = f"Repository: {self.current_repo}\n\n"
153
  repo_files = "\n".join(list(self.repo_content.keys()))
 
157
  messages.append({"role": "user", "content": prompt})
158
 
159
  stream = await client.chat.completions.create(
160
+ model=model, messages=messages, stream=True
 
 
161
  )
162
 
163
  full_response = ""
 
175
  print(error_msg)
176
  yield error_msg
177
 
178
+ async def stream_gemini_response(
179
+ self, prompt: str, api_key: str
180
+ ) -> AsyncGenerator[str, None]:
181
  """Stream response dari Gemini API"""
182
  try:
183
  if not api_key:
 
185
  return
186
 
187
  genai.configure(api_key=api_key)
188
+ model = genai.GenerativeModel("gemini-pro")
189
+
190
  # Tambahkan konteks repository jika ada
191
  if self.current_repo:
192
  context = f"Repository: {self.current_repo}\n\n"
193
  repo_files = "\n".join(list(self.repo_content.keys()))
194
  context += f"Files in repository:\n{repo_files}\n\n"
195
  prompt = context + prompt
196
+
197
  response = model.generate_content(
198
  prompt,
199
+ generation_config={"temperature": 0.7, "top_p": 0.8, "top_k": 40},
200
+ stream=True,
 
 
 
 
201
  )
202
+
203
  full_response = ""
204
  async for chunk in response:
205
  if chunk.text:
206
  full_response += chunk.text
207
  yield chunk.text
208
+
209
  self.chat_history.append({"role": "user", "content": prompt})
210
  self.chat_history.append({"role": "assistant", "content": full_response})
211
+
212
  except Exception as e:
213
  error_msg = f"⚠️ Error dalam Gemini API: {str(e)}\n\nPastikan API Key valid dan memiliki kuota yang cukup."
214
  print(error_msg)
215
  yield error_msg
216
 
217
+ def clone_repository(
218
+ self, repo_url: str, github_token: str, branch: str = None
219
+ ) -> tuple[bool, str]:
220
  """Clone repository GitHub dengan autentikasi"""
221
  if not repo_url:
222
  return False, "⚠️ URL repository diperlukan"
223
 
224
+ repo_name = repo_url.split("/")[-1].replace(".git", "")
225
+
226
  if os.path.exists(repo_name):
227
+ subprocess.run(["rm", "-rf", repo_name], check=True)
228
 
229
  try:
230
+ owner_repo = "/".join(repo_url.split("/")[-2:])
231
+
232
  # Cek apakah repository private
233
+ headers = {"Authorization": f"token {github_token}"} if github_token else {}
234
+ repo_check = requests.get(
235
+ f"https://api.github.com/repos/{owner_repo}", headers=headers
236
+ )
237
+
238
  if repo_check.status_code == 404:
239
  return False, "⚠️ Repository tidak ditemukan. Periksa URL repository."
240
  elif repo_check.status_code == 401:
241
+ return (
242
+ False,
243
+ "⚠️ Token GitHub tidak valid. Klik icon bantuan (?) untuk panduan mendapatkan token.",
244
+ )
245
+ elif repo_check.status_code == 403 and repo_check.json().get(
246
+ "private", False
247
+ ):
248
+ return (
249
+ False,
250
+ "⚠️ Ini adalah repository private. Token GitHub dengan akses 'repo' diperlukan.",
251
+ )
252
 
253
+ auth_url = (
254
+ f"https://{github_token}@github.com/{owner_repo}"
255
+ if github_token
256
+ else f"https://github.com/{owner_repo}"
257
+ )
258
 
259
+ cmd = ["git", "clone"]
260
  if branch:
261
+ cmd.extend(["--branch", branch])
262
  cmd.append(auth_url)
263
 
264
  process = subprocess.run(
265
  cmd,
266
  capture_output=True,
267
  text=True,
268
+ env=dict(os.environ, GIT_ASKPASS="echo", GIT_TERMINAL_PROMPT="0"),
269
  )
270
 
271
  if process.returncode == 0:
272
  self.current_repo = repo_name
273
  # Scan dan simpan konten repository
274
  file_count = 0
275
+ for file_path in Path(repo_name).rglob("*"):
276
+ if file_path.is_file() and ".git" not in str(file_path):
277
  success, content = self.read_file_safely(str(file_path))
278
  if success:
279
  self.repo_content[str(file_path)] = content
280
  file_count += 1
281
+
282
+ return (
283
+ True,
284
+ f"✅ Repository berhasil di-clone!\n\nNama: {repo_name}\nJumlah file: {file_count}\n\nAnda sekarang bisa mengajukan pertanyaan tentang repository ini.",
285
+ )
286
  else:
287
  return False, f"⚠️ Gagal clone repository:\n{process.stderr}"
288
+
289
  except Exception as e:
290
  return False, f"⚠️ Error: {str(e)}"
291
 
292
  def read_file_safely(self, file_path: str) -> tuple[bool, str]:
293
  """Baca file dengan aman menggunakan berbagai encoding"""
294
+ encodings = ["utf-8", "latin-1", "cp1252"]
295
  for encoding in encodings:
296
  try:
297
+ with open(file_path, "r", encoding=encoding) as f:
298
  content = f.read()
299
  return True, content
300
  except Exception as e:
301
  continue
302
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
303
 
304
+
305
  def create_ui():
306
  analyzer = RepoAnalyzer()
307
+
308
  with gr.Blocks(title="Repository Chat Analysis", theme=gr.themes.Soft()) as app:
309
  gr.Markdown("""
310
  <style>
 
333
  provider = gr.Radio(
334
  choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
335
  label="Penyedia AI",
336
+ value=AIProvider.XAI,
337
  )
338
+
339
  with gr.Group() as api_settings:
340
  with gr.Row():
341
  xai_key = gr.Textbox(
 
343
  type="password",
344
  placeholder="Opsional - Klik icon (?) untuk info",
345
  show_label=True,
346
+ scale=3,
347
  )
348
  with gr.Column(scale=1):
349
  gr.Markdown(XAI_API_HELP)
350
+
351
  with gr.Row():
352
  gemini_key = gr.Textbox(
353
  label="Gemini API Key",
354
  type="password",
355
  placeholder="Opsional - Kosongkan untuk gunakan key default",
356
  show_label=True,
357
+ scale=3,
358
  )
359
  with gr.Column(scale=1):
360
  gr.Markdown(GEMINI_API_HELP)
 
365
  label="Model AI",
366
  choices=XAI_MODELS,
367
  value="grok-2-latest",
368
+ interactive=True,
369
  )
370
 
371
  # Repository Analysis Tab
 
374
  with gr.Row():
375
  repo_url = gr.Textbox(
376
  label="URL Repository GitHub",
377
+ placeholder="https://github.com/username/repository",
378
  )
379
+
380
  with gr.Row():
381
  with gr.Column(scale=2):
382
  github_token = gr.Textbox(
383
+ label="Token GitHub",
384
  type="password",
385
+ placeholder="Klik icon (?) untuk panduan",
386
  )
387
  with gr.Column(scale=1):
388
  branch = gr.Textbox(
389
+ label="Branch (opsional)", placeholder="main"
 
390
  )
391
 
392
+ clone_button = gr.Button("🔄 Clone Repository", variant="primary")
393
+
394
+ clone_status = gr.Markdown(value="", label="Status Repository")
 
 
 
 
 
 
395
 
396
  # File Selection
397
  with gr.Group():
398
  gr.Markdown("### 📎 File yang Dipilih")
399
+
400
  with gr.Row():
401
  file_selector = gr.Dropdown(
402
  label="Pilih File dari Repository",
403
  choices=[],
404
  multiselect=True,
405
+ value=[], # Initialize with empty list
406
  )
407
+
408
  file_list = gr.HTML(
409
  value="<div class='file-list'>Belum ada file yang dipilih</div>",
410
+ label="Daftar File Terpilih",
411
  )
412
 
413
  # Chat Interface
 
416
  label="📝 Riwayat Chat",
417
  height=500,
418
  show_label=True,
419
+ type="messages", # Fix deprecation warning
420
  )
421
+
422
  with gr.Row():
423
  chat_input = gr.Textbox(
424
  label="💭 Tanyakan tentang Repository",
425
  placeholder="Ketik pertanyaan Anda di sini...",
426
+ lines=3,
427
  )
428
  send_button = gr.Button("📤 Kirim", variant="primary")
429
 
 
435
  def handle_clone(repo_url, github_token, branch):
436
  if not repo_url:
437
  return "⚠️ URL repository diperlukan!", [], []
438
+
439
  success, message = analyzer.clone_repository(repo_url, github_token, branch)
440
+
441
  if success:
442
  # Get list of files from cloned repository
443
+ files = sorted(
444
+ list(analyzer.repo_content.keys())
445
+ ) # Sort files alphabetically
446
  return message, files, []
447
+
448
  return message, [], []
449
 
450
  def update_file_list(selected):
451
  if not selected:
452
  return "<div class='file-list'>Belum ada file yang dipilih</div>"
453
+
454
  html = "<div class='file-list'>"
455
  for file in selected:
456
  html += f"<div class='file-item'><span>{file}</span></div>"
 
467
 
468
  # Connect Events
469
  provider.change(
470
+ fn=update_model_list, inputs=[provider], outputs=[model_dropdown]
 
 
471
  )
472
 
473
  clone_button.click(
474
  fn=handle_clone,
475
  inputs=[repo_url, github_token, branch],
476
+ outputs=[clone_status, file_selector, file_list],
477
  )
478
 
479
  file_selector.change(
480
+ fn=update_file_list, inputs=[file_selector], outputs=[file_list]
 
 
481
  )
482
 
483
+ with gr.Tab("📊 Analisis Repository", elem_classes="mobile-full"):
484
+ with gr.Group():
485
+ with gr.Row():
486
+ repo_url = gr.Textbox(
487
+ label="URL Repository GitHub",
488
+ placeholder="https://github.com/username/repository",
489
+ elem_classes="mobile-full",
490
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491
 
492
+ with gr.Row():
493
+ with gr.Column(scale=2):
494
+ github_token = gr.Textbox(
495
+ label="Token GitHub",
496
+ type="password",
497
+ placeholder="Klik icon (?) untuk panduan",
498
+ elem_classes="mobile-full",
 
 
 
499
  )
500
+ with gr.Column(scale=1):
501
+ branch = gr.Textbox(
502
+ label="Branch (opsional)",
503
+ placeholder="main",
504
+ elem_classes="mobile-full",
505
+ )
506
+
507
+ clone_button = gr.Button(
508
+ "🔄 Clone Repository", variant="primary", elem_classes="mobile-full"
509
+ )
510
+
511
+ clone_status = gr.Markdown(
512
+ value="", label="Status Repository", elem_classes="mobile-full"
513
+ )
514
+
515
+ # Add file selection components
516
+ with gr.Group():
517
+ gr.Markdown("### 📎 File yang Dipilih")
518
+
519
+ with gr.Row():
520
+ file_selector = gr.Dropdown(
521
+ label="Pilih File dari Repository",
522
+ choices=[],
523
+ multiselect=True,
524
+ elem_classes="mobile-full",
525
  )
526
 
527
+ file_list = gr.HTML(
528
+ value="<div class='file-list'>Belum ada file yang dipilih</div>",
529
+ label="Daftar File Terpilih",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  )
531
 
532
+ def update_file_list(selected: List[str]) -> str:
533
+ if not selected:
534
+ return "<div class='file-list'>Belum ada file yang dipilih</div>"
535
+
536
+ html = "<div class='file-list'>"
537
+ for file in selected:
538
+ html += f"<div class='file-item'><span>{file}</span></div>"
539
+ html += "</div>"
540
+ return html
541
+
542
+ def handle_clone(repo_url, github_token, branch):
543
+ if not repo_url:
544
+ return "⚠️ URL repository diperlukan!", [], []
545
+
546
+ success, message = analyzer.clone_repository(
547
+ repo_url, github_token, branch
548
  )
549
 
550
+ if success:
551
+ # Get list of files from cloned repository
552
+ files = list(analyzer.repo_content.keys())
553
+ return message, files, []
554
+
555
+ return message, [], []
556
+
557
+ # Connect clone button click event
558
+ clone_button.click(
559
+ fn=handle_clone,
560
+ inputs=[repo_url, github_token, branch],
561
+ outputs=[clone_status, file_selector, file_list],
562
+ )
563
+
564
+ # Update file list when selection changes
565
+ file_selector.change(
566
+ fn=update_file_list, inputs=[file_selector], outputs=[file_list]
567
+ )
568
+
569
+ gr.Markdown("""
570
  ### 💡 Contoh Pertanyaan:
571
  - "Jelaskan struktur utama dari repository ini"
572
  - "Analisis kode di file yang dipilih"
573
  - "Bagaimana cara memperbaiki [masalah specific] di file-file ini?"
574
  - "Bandingkan implementasi di file-file yang dipilih"
575
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
 
577
+ # Improved chat interface
578
+ with gr.Group():
579
+ chat_input = gr.Textbox(
580
+ label="💭 Tanyakan tentang Repository",
581
+ placeholder="Ketik pertanyaan Anda di sini...",
582
+ lines=3,
583
+ elem_classes="mobile-full",
584
  )
585
+ send_button = gr.Button(
586
+ "📤 Kirim", variant="primary", elem_classes="mobile-full"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  )
588
 
589
+ chat_history = gr.Chatbot(
590
+ label="📝 Riwayat Chat",
591
+ height=500,
592
+ show_label=True,
593
+ elem_classes="mobile-full",
594
+ )
595
+
596
+ loading_indicator = gr.HTML(
597
+ '<div id="loading" style="display:none">Memproses permintaan...</div>'
598
+ )
599
+
600
+ async def handle_chat(
601
+ message,
602
+ history,
603
+ provider_choice,
604
+ model_name,
605
+ xai_key,
606
+ gemini_key,
607
+ selected_files,
608
+ ):
609
+ if not analyzer.current_repo:
610
+ yield history + [
611
+ [
612
+ message,
613
+ "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan.",
614
+ ]
615
+ ]
616
+ return
617
+
618
+ history = history or []
619
+ history.append([message, ""])
620
+
621
+ try:
622
+ # Add context about selected files to the prompt
623
+ file_context = ""
624
+ if selected_files:
625
+ file_context = "\n\nFile yang dipilih:\n"
626
+ for file in selected_files:
627
+ content = analyzer.repo_content.get(file, "")
628
+ file_context += f"\n{file}:\n```\n{content}\n```\n"
629
+
630
+ enhanced_message = f"{message}\n{file_context}"
631
+
632
+ full_response = ""
633
+ if provider_choice == AIProvider.XAI:
634
+ async for chunk in analyzer.stream_xai_response(
635
+ enhanced_message, xai_key, model_name
636
+ ):
637
+ full_response += chunk
638
+ # Add delay between chunks for readability
639
+ await asyncio.sleep(0.05)
640
+ history[-1][1] = full_response
641
+ yield history
642
+
643
+ elif provider_choice == AIProvider.GEMINI:
644
+ async for chunk in analyzer.stream_gemini_response(
645
+ enhanced_message, gemini_key or DEFAULT_GEMINI_KEY
646
+ ):
647
+ full_response += chunk
648
+ # Add delay between chunks for readability
649
+ await asyncio.sleep(0.05)
650
+ history[-1][1] = full_response
651
+ yield history
652
+
653
+ else: # OLLAMA
654
+ response = analyze_with_ollama(model_name, enhanced_message)
655
+ # Simulate streaming for OLLAMA with delay
656
+ words = response.split()
657
+ for i in range(len(words)):
658
+ full_response = " ".join(words[: i + 1])
659
+ await asyncio.sleep(0.05)
660
+ history[-1][1] = full_response
661
+ yield history
662
+
663
+ except Exception as e:
664
+ history[-1][1] = f"⚠️ Error: {str(e)}"
665
+ yield history
666
+
667
+ # Connect chat events with file selection
668
+ send_event = send_button.click(
669
+ fn=handle_chat,
670
+ inputs=[
671
+ chat_input,
672
+ chat_history,
673
+ provider,
674
+ model_dropdown,
675
+ xai_key,
676
+ gemini_key,
677
+ file_selector,
678
+ ],
679
+ outputs=chat_history,
680
+ show_progress=True,
681
+ ).then(fn=lambda: gr.update(value=""), outputs=chat_input)
682
+
683
+ input_event = chat_input.submit(
684
+ fn=handle_chat,
685
+ inputs=[
686
+ chat_input,
687
+ chat_history,
688
+ provider,
689
+ model_dropdown,
690
+ xai_key,
691
+ gemini_key,
692
+ file_selector,
693
+ ],
694
+ outputs=chat_history,
695
+ show_progress=True,
696
+ ).then(fn=lambda: gr.update(value=""), outputs=chat_input)
697
 
698
  return app
699
 
700
+
701
  if __name__ == "__main__":
702
  print(f"""
703
  🚀 Memulai Repository Chat Analysis
704
  """)
705
+
706
  app = create_ui()
707
+ app.launch(share=True)