ErNewdev0 commited on
Commit
8562792
Β·
verified Β·
1 Parent(s): 0390256

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +723 -0
app.py ADDED
@@ -0,0 +1,723 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
15
+
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
+ "xai-vfjhklL384Z4HKdItsZomqpFlXubTZJAFnISQUpV7dE8lRnWwYBVPSCxSTlu08wDbAcv720bx2dDiQ9x",
27
+ )
28
+ DEFAULT_GEMINI_KEY = os.getenv("GEMINI_API_KEY")
29
+
30
+ # API settings
31
+ OLLAMA_API = os.environ.get("OLLAMA_API", "http://localhost:11434")
32
+ XAI_BASE_URL = "https://api.x.ai/v1"
33
+
34
+ # Model lists
35
+ OLLAMA_MODELS = [
36
+ "llama2",
37
+ "codellama",
38
+ "mistral",
39
+ "neural-chat",
40
+ "starling-lm",
41
+ "dolphin-phi",
42
+ "phi",
43
+ "orca-mini",
44
+ ]
45
+
46
+ XAI_MODELS = [
47
+ "grok-2-latest",
48
+ "grok-1",
49
+ ]
50
+
51
+ GEMINI_MODELS = [
52
+ "gemini-1.5-mini",
53
+ "gemini-pro-vision",
54
+ ]
55
+
56
+ # Help texts
57
+
58
+ GITHUB_TOKEN_HELP = """
59
+ ### Cara Mendapatkan GitHub Token:
60
+
61
+ 1. Kunjungi [GitHub Token Settings](https://github.com/settings/tokens)
62
+ 2. Klik "Generate new token" > "Generate new token (classic)"
63
+ 3. Beri nama token Anda di "Note"
64
+ 4. Pilih scope:
65
+ - `repo` (untuk akses repository private)
66
+ - `read:packages` (opsional, untuk akses package)
67
+ 5. Klik "Generate token"
68
+ 6. **PENTING**: Salin token segera! Token hanya ditampilkan sekali
69
+
70
+ Token diperlukan untuk:
71
+ - Mengakses repository private
72
+ - Clone repository dengan rate limit lebih tinggi
73
+ - Mengakses fitur GitHub API
74
+ """
75
+
76
+ GEMINI_API_HELP = """
77
+ ### Cara Mendapatkan Gemini API Key:
78
+
79
+ 1. Kunjungi [Google AI Studio](https://makersuite.google.com/app/apikey)
80
+ 2. Login dengan akun Google Anda
81
+ 3. Klik "Create API Key"
82
+ 4. Salin API Key yang dihasilkan
83
+
84
+ Catatan:
85
+ - Gemini memberikan kuota gratis setiap bulan
86
+ - Key bisa dibuat ulang jika diperlukan
87
+ - Monitor penggunaan di [Google Cloud Console](https://console.cloud.google.com/)
88
+ """
89
+
90
+ OLLAMA_HELP = """
91
+ ### Cara Menggunakan Ollama:
92
+
93
+ 1. Install Ollama dari [ollama.ai](https://ollama.ai)
94
+ 2. Jalankan Ollama di komputer Anda
95
+ 3. Pastikan Ollama berjalan di http://localhost:11434
96
+
97
+ Catatan:
98
+ - Ollama berjalan secara lokal di komputer Anda
99
+ - Tidak memerlukan API key
100
+ - Ideal untuk privasi dan penggunaan offline
101
+ """
102
+
103
+ XAI_API_HELP = """
104
+ ### Cara Mendapatkan X.AI (Grok) API Key:
105
+
106
+ 1. Kunjungi [X.AI Developer Portal](https://x.ai)
107
+ 2. Daftar/Login ke akun Anda
108
+ 3. Buat API Key baru
109
+ 4. Salin API Key
110
+
111
+ Note:
112
+ - Jika tidak diisi, akan menggunakan API key default
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()))
154
+ context += f"Files in repository:\n{repo_files}\n\n"
155
+ messages.append({"role": "system", "content": context})
156
+
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 = ""
164
+ async for chunk in stream:
165
+ if chunk.choices[0].delta.content:
166
+ content = chunk.choices[0].delta.content
167
+ full_response += content
168
+ yield content
169
+
170
+ self.chat_history.append({"role": "user", "content": prompt})
171
+ self.chat_history.append({"role": "assistant", "content": full_response})
172
+
173
+ except Exception as e:
174
+ error_msg = f"⚠️ Error dalam X.AI API: {str(e)}"
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:
184
+ yield "⚠️ API Key Gemini diperlukan. Klik icon bantuan (?) di samping input API Key untuk panduan mendapatkan 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
+ analyzer = RepoAnalyzer()
305
+
306
+ async def handle_chat(
307
+ message,
308
+ history,
309
+ provider_choice,
310
+ model_name,
311
+ xai_key,
312
+ gemini_key,
313
+ selected_files,
314
+ analyzer=analyzer,
315
+ ):
316
+ """Menangani interaksi chat dengan model AI"""
317
+ if not analyzer.current_repo:
318
+ new_message = {
319
+ "role": "assistant",
320
+ "content": "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan.",
321
+ }
322
+ history = history or []
323
+ history.append({"role": "user", "content": message})
324
+ history.append(new_message)
325
+ yield history
326
+ return
327
+
328
+ history = history or []
329
+ history.append({"role": "user", "content": message})
330
+ history.append({"role": "assistant", "content": ""})
331
+
332
+ try:
333
+ # Add context about selected files to the prompt
334
+ file_context = ""
335
+ if selected_files:
336
+ file_context = "\n\nFile yang dipilih:\n"
337
+ for file in selected_files:
338
+ content = analyzer.repo_content.get(file, "")
339
+ if content: # Only include files that exist
340
+ file_context += f"\n{file}:\n```\n{content}\n```\n"
341
+
342
+ enhanced_message = f"{message}\n{file_context}"
343
+
344
+ full_response = ""
345
+ if provider_choice == AIProvider.XAI:
346
+ async for chunk in analyzer.stream_xai_response(
347
+ enhanced_message, xai_key, model_name
348
+ ):
349
+ full_response += chunk
350
+ # Add delay between chunks for readability
351
+ await asyncio.sleep(0.05)
352
+ history[-1]["content"] = full_response
353
+ yield history
354
+
355
+ elif provider_choice == AIProvider.GEMINI:
356
+ async for chunk in analyzer.stream_gemini_response(
357
+ enhanced_message, gemini_key or DEFAULT_GEMINI_KEY
358
+ ):
359
+ full_response += chunk
360
+ # Add delay between chunks for readability
361
+ await asyncio.sleep(0.05)
362
+ history[-1]["content"] = full_response
363
+ yield history
364
+
365
+ else: # OLLAMA
366
+ response = analyze_with_ollama(model_name, enhanced_message)
367
+ # Simulate streaming for OLLAMA with delay
368
+ words = response.split()
369
+ for i in range(len(words)):
370
+ full_response = " ".join(words[: i + 1])
371
+ await asyncio.sleep(0.05)
372
+ history[-1]["content"] = full_response
373
+ yield history
374
+
375
+ except Exception as e:
376
+ history[-1]["content"] = f"⚠️ Error: {str(e)}"
377
+ yield history
378
+
379
+
380
+ def create_ui():
381
+ """
382
+ Membuat antarmuka pengguna untuk aplikasi Open Repo AI menggunakan Gradio.
383
+ """
384
+ # Gunakan analyzer global
385
+ global analyzer
386
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
387
+
388
+ # Define CSS styles outside the main UI for better maintainability
389
+ CSS_STYLES = """
390
+ <style>
391
+ .container {
392
+ max-width: 100% !important;
393
+ padding: 1rem;
394
+ margin: 0 auto;
395
+ }
396
+ .header {
397
+ margin-bottom: 2rem;
398
+ padding: 1rem;
399
+ background: #f8f9fa;
400
+ border-radius: 8px;
401
+ }
402
+ .mobile-full {
403
+ width: 100% !important;
404
+ }
405
+ .file-list {
406
+ margin: 10px 0;
407
+ padding: 10px;
408
+ border: 1px solid #ddd;
409
+ border-radius: 4px;
410
+ background: #fff;
411
+ }
412
+ .file-item {
413
+ display: flex;
414
+ justify-content: space-between;
415
+ padding: 8px;
416
+ margin: 4px 0;
417
+ background: #f8f9fa;
418
+ border-radius: 4px;
419
+ }
420
+ .quick-example-btn {
421
+ margin: 5px;
422
+ padding: 8px 15px;
423
+ border: 1px solid #e9ecef;
424
+ border-radius: 4px;
425
+ background: #f8f9fa;
426
+ cursor: pointer;
427
+ transition: all 0.3s ease;
428
+ }
429
+ .quick-example-btn:hover {
430
+ background: #e9ecef;
431
+ transform: translateY(-1px);
432
+ }
433
+ .chat-container {
434
+ margin-top: 2rem;
435
+ padding: 1rem;
436
+ background: #fff;
437
+ border-radius: 8px;
438
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
439
+ }
440
+ .help-text {
441
+ font-size: 0.9rem;
442
+ color: #6c757d;
443
+ margin: 0.5rem 0;
444
+ }
445
+ @media (max-width: 768px) {
446
+ .gr-form {
447
+ flex-direction: column !important;
448
+ }
449
+ .gr-group {
450
+ margin: 0.5rem 0 !important;
451
+ }
452
+ .container {
453
+ padding: 0.5rem;
454
+ }
455
+ }
456
+ </style>
457
+ """
458
+
459
+ with gr.Blocks(title="Open Repo AI", theme=gr.themes.Soft()) as app:
460
+ # Apply CSS
461
+ gr.Markdown(CSS_STYLES)
462
+
463
+ # Header Section
464
+ with gr.Row(elem_classes="header") as header:
465
+ gr.Markdown(f"""
466
+ # πŸ€– Open Repo AI Assistant
467
+
468
+ πŸ“… Current Date and Time (UTC): {current_time}
469
+ """)
470
+
471
+ # Main Tabs
472
+ with gr.Tabs() as tabs:
473
+ # Configuration Tab
474
+ with gr.Tab("πŸ› οΈ Configuration", elem_id="config-tab"):
475
+ with gr.Group():
476
+ provider = gr.Radio(
477
+ choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
478
+ label="AI Provider",
479
+ value=AIProvider.XAI,
480
+ elem_classes="provider-select"
481
+ )
482
+
483
+ # API Settings
484
+ with gr.Group(elem_classes="api-settings") as api_settings:
485
+ with gr.Row():
486
+ with gr.Column(scale=3):
487
+ xai_key = gr.Textbox(
488
+ label="X.AI (Grok) API Key",
489
+ type="password",
490
+ placeholder="Optional - Click (?) for info",
491
+ elem_classes="api-key-input"
492
+ )
493
+ gemini_key = gr.Textbox(
494
+ label="Gemini API Key",
495
+ type="password",
496
+ placeholder="Optional - Leave blank for default key",
497
+ elem_classes="api-key-input"
498
+ )
499
+ with gr.Column(scale=1):
500
+ gr.Markdown(XAI_API_HELP)
501
+ gr.Markdown(GEMINI_API_HELP)
502
+
503
+ # Model Selection
504
+ model_dropdown = gr.Dropdown(
505
+ label="AI Model",
506
+ choices=XAI_MODELS,
507
+ value="grok-2-latest",
508
+ interactive=True,
509
+ elem_classes="model-select"
510
+ )
511
+
512
+ # Repository Analysis Tab
513
+ with gr.Tab("πŸ“Š Repository Analysis", elem_id="repo-tab"):
514
+ with gr.Group(elem_classes="repo-config"):
515
+ # Repository Settings
516
+ with gr.Row():
517
+ repo_url = gr.Textbox(
518
+ label="GitHub Repository URL",
519
+ placeholder="https://github.com/username/repository",
520
+ elem_classes="repo-input"
521
+ )
522
+
523
+ with gr.Row():
524
+ with gr.Column(scale=2):
525
+ github_token = gr.Textbox(
526
+ label="GitHub Token",
527
+ type="password",
528
+ placeholder="Click (?) for guide",
529
+ elem_classes="token-input"
530
+ )
531
+ gr.Markdown(GITHUB_TOKEN_HELP)
532
+ with gr.Column(scale=1):
533
+ branch = gr.Textbox(
534
+ label="Branch (optional)",
535
+ placeholder="main",
536
+ elem_classes="branch-input"
537
+ )
538
+
539
+ # Clone Section
540
+ with gr.Row():
541
+ clone_button = gr.Button(
542
+ "πŸ”„ Clone Repository",
543
+ variant="primary",
544
+ elem_classes="clone-btn"
545
+ )
546
+ clone_status = gr.Markdown(
547
+ value="",
548
+ label="Repository Status",
549
+ elem_classes="clone-status"
550
+ )
551
+
552
+ # File Selection
553
+ with gr.Group(elem_classes="file-selection"):
554
+ gr.Markdown("### πŸ“Ž Selected Files")
555
+ file_selector = gr.Dropdown(
556
+ label="Select Files from Repository",
557
+ choices=[],
558
+ multiselect=True,
559
+ value=[],
560
+ allow_custom_value=True,
561
+ max_choices=None,
562
+ elem_classes="file-dropdown"
563
+ )
564
+ file_list = gr.HTML(
565
+ value="<div class='file-list'>No files selected</div>",
566
+ label="Selected Files List"
567
+ )
568
+
569
+ # Chat Interface
570
+ with gr.Group(elem_classes="chat-container") as chat_group:
571
+ chat_history = gr.Chatbot(
572
+ label="πŸ’¬ Chat History",
573
+ height=500,
574
+ show_label=True,
575
+ elem_classes="chat-history"
576
+ )
577
+
578
+ with gr.Row():
579
+ chat_input = gr.Textbox(
580
+ label="Ask about Repository",
581
+ placeholder="Type your question here...",
582
+ lines=3,
583
+ elem_classes="chat-input"
584
+ )
585
+
586
+ with gr.Row():
587
+ send_button = gr.Button(
588
+ "πŸ“€ Send",
589
+ variant="primary",
590
+ elem_classes="send-btn"
591
+ )
592
+ clear_button = gr.Button(
593
+ "🧹 Clear",
594
+ variant="secondary",
595
+ elem_classes="clear-btn"
596
+ )
597
+
598
+ # Event Handlers
599
+ def update_model_list(provider_choice: str) -> dict:
600
+ """Update model dropdown based on selected provider."""
601
+ models_map = {
602
+ AIProvider.XAI: (XAI_MODELS, "grok-2-latest"),
603
+ AIProvider.GEMINI: (GEMINI_MODELS, "gemini-1.5-mini"),
604
+ AIProvider.OLLAMA: (OLLAMA_MODELS, "llama2")
605
+ }
606
+ choices, default = models_map.get(provider_choice, (XAI_MODELS, "grok-2-latest"))
607
+ return gr.Dropdown(choices=choices, value=default)
608
+
609
+ def handle_clone(repo_url: str, github_token: str, branch: str) -> tuple:
610
+ """Handle repository cloning and update UI components."""
611
+ if not repo_url:
612
+ return (
613
+ "⚠️ Repository URL is required!",
614
+ gr.Dropdown(choices=[]),
615
+ "<div class='file-list'>No files selected</div>"
616
+ )
617
+
618
+ success, message = analyzer.clone_repository(repo_url, github_token, branch)
619
+
620
+ if success:
621
+ files = sorted(list(analyzer.repo_content.keys()))
622
+ return (
623
+ message,
624
+ gr.Dropdown(choices=files, value=[]),
625
+ "<div class='file-list'>No files selected</div>"
626
+ )
627
+
628
+ return (
629
+ message,
630
+ gr.Dropdown(choices=[]),
631
+ "<div class='file-list'>No files selected</div>"
632
+ )
633
+
634
+ def update_file_list(selected: List[str]) -> str:
635
+ """Update the file list display."""
636
+ if not selected:
637
+ return "<div class='file-list'>No files selected</div>"
638
+
639
+ file_items = "".join([
640
+ f"<div class='file-item'><span>{file}</span></div>"
641
+ for file in selected
642
+ ])
643
+ return f"<div class='file-list'>{file_items}</div>"
644
+
645
+ def clear_chat_history() -> list:
646
+ """Clear the chat history."""
647
+ return []
648
+
649
+ # Connect Events
650
+ provider.change(
651
+ fn=update_model_list,
652
+ inputs=[provider],
653
+ outputs=[model_dropdown]
654
+ )
655
+
656
+ clone_button.click(
657
+ fn=handle_clone,
658
+ inputs=[repo_url, github_token, branch],
659
+ outputs=[clone_status, file_selector, file_list]
660
+ )
661
+
662
+ file_selector.change(
663
+ fn=update_file_list,
664
+ inputs=[file_selector],
665
+ outputs=[file_list]
666
+ )
667
+
668
+ clear_button.click(
669
+ fn=clear_chat_history,
670
+ outputs=[chat_history]
671
+ )
672
+
673
+ # Chat Events
674
+ chat_handlers = [
675
+ send_button.click(
676
+ fn=handle_chat,
677
+ inputs=[
678
+ chat_input,
679
+ chat_history,
680
+ provider,
681
+ model_dropdown,
682
+ xai_key,
683
+ gemini_key,
684
+ file_selector
685
+ ],
686
+ outputs=chat_history,
687
+ show_progress=True
688
+ ),
689
+ chat_input.submit(
690
+ fn=handle_chat,
691
+ inputs=[
692
+ chat_input,
693
+ chat_history,
694
+ provider,
695
+ model_dropdown,
696
+ xai_key,
697
+ gemini_key,
698
+ file_selector
699
+ ],
700
+ outputs=chat_history,
701
+ show_progress=True
702
+ )
703
+ ]
704
+
705
+ # Clear input after sending
706
+ for handler in chat_handlers:
707
+ handler.then(fn=lambda: "", outputs=chat_input)
708
+
709
+ return app
710
+
711
+ if __name__ == "__main__":
712
+ print("""
713
+ πŸš€ Starting Repository Chat Analysis
714
+ ====================================
715
+ """)
716
+
717
+ app = create_ui()
718
+ app.launch(
719
+ share=True,
720
+ server_name="0.0.0.0",
721
+ server_port=7860,
722
+ debug=True
723
+ )