ErNewdev0 commited on
Commit
4c56d3b
·
verified ·
1 Parent(s): 318447b

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1139
app.py DELETED
@@ -1,1139 +0,0 @@
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# 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
- "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
-
305
- analyzer = RepoAnalyzer()
306
-
307
-
308
- async def handle_chat(message, history, provider_choice, model_name, xai_key, gemini_key, selected_files, analyzer=analyzer):
309
- """Menangani interaksi chat dengan model AI"""
310
- if not analyzer.current_repo:
311
- new_message = {
312
- "role": "assistant",
313
- "content": "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan."
314
- }
315
- history = history or []
316
- history.append({"role": "user", "content": message})
317
- history.append(new_message)
318
- yield history
319
- return
320
-
321
- history = history or []
322
-
323
- # Format user message
324
- user_message = f'<div class="user-message">{message}</div>'
325
- history.append({"role": "user", "content": user_message})
326
-
327
- # Initialize assistant message
328
- assistant_message = '<div class="assistant-message">'
329
- history.append({"role": "assistant", "content": assistant_message})
330
-
331
- try:
332
- # Add context about selected files to the prompt
333
- file_context = ""
334
- if selected_files:
335
- file_context = "\n\nFile yang dipilih:\n"
336
- for file in selected_files:
337
- content = analyzer.repo_content.get(file, "")
338
- if content:
339
- file_context += f"\n{file}:\n```\n{content}\n```\n"
340
-
341
- enhanced_message = f"{message}\n{file_context}"
342
-
343
- full_response = ""
344
- if provider_choice == AIProvider.XAI:
345
- async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
346
- full_response += chunk
347
- # Format the response with code blocks
348
- formatted_response = format_response(full_response)
349
- history[-1]["content"] = f'<div class="assistant-message">{formatted_response}</div>'
350
- await asyncio.sleep(0.05)
351
- yield history
352
-
353
- elif provider_choice == AIProvider.GEMINI:
354
- async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
355
- full_response += chunk
356
- formatted_response = format_response(full_response)
357
- history[-1]["content"] = f'<div class="assistant-message">{formatted_response}</div>'
358
- await asyncio.sleep(0.05)
359
- yield history
360
-
361
- else: # OLLAMA
362
- response = analyze_with_ollama(model_name, enhanced_message)
363
- words = response.split()
364
- for i in range(len(words)):
365
- full_response = " ".join(words[:i + 1])
366
- formatted_response = format_response(full_response)
367
- history[-1]["content"] = f'<div class="assistant-message">{formatted_response}</div>'
368
- await asyncio.sleep(0.05)
369
- yield history
370
-
371
- except Exception as e:
372
- error_msg = f"⚠️ Error: {str(e)}"
373
- history[-1]["content"] = f'<div class="assistant-message">{error_msg}</div>'
374
- yield history
375
-
376
- def format_response(text):
377
- """Format response text with code blocks"""
378
- import re
379
- import uuid
380
-
381
- # Replace code blocks with styled versions
382
- def replace_code_block(match):
383
- language = match.group(1) or ''
384
- code = match.group(2)
385
- block_id = f'code-block-{uuid.uuid4().hex[:8]}'
386
-
387
- return f'''
388
- <div class="code-block">
389
- <div class="code-header">
390
- <span class="code-title">{language}</span>
391
- <button class="code-copy-btn" id="{block_id}-btn" onclick="copyCode('{block_id}')">Copy</button>
392
- </div>
393
- <pre class="code-content" id="{block_id}">{code}</pre>
394
- </div>
395
- '''
396
-
397
- # Replace ```language\ncode``` blocks
398
- text = re.sub(r'```(\w+)?\n(.*?)```', replace_code_block, text, flags=re.DOTALL)
399
-
400
- # Replace inline code
401
- text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
402
-
403
- return text
404
-
405
- def clear_all():
406
- """Clear semua input dan status"""
407
- return (
408
- "", # repo_url
409
- "", # github_token
410
- "", # branch
411
- "", # clone_status
412
- [], # chat_history
413
- [], # file_selector
414
- "<div class='file-list'>Belum ada file yang dipilih</div>", # file_list
415
- "", # xai_key
416
- "", # gemini_key
417
- "grok-2-latest", # model_dropdown default value
418
- )
419
-
420
- def create_ui():
421
- global analyzer
422
- current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
423
-
424
- with gr.Blocks(title="Open Repo AI", theme=gr.themes.Soft()) as app:
425
- # CSS Styling
426
- gr.Markdown("""
427
- <style>
428
- /* General Styles */
429
- .container { max-width: 100% !important; padding: 1rem; }
430
- .mobile-full { width: 100% !important; }
431
-
432
- /* Header Styles */
433
- .header-info {
434
- display: flex;
435
- justify-content: space-between;
436
- padding: 10px;
437
- background: #f8f9fa;
438
- border-radius: 4px;
439
- margin: 10px 0;
440
- font-family: monospace;
441
- }
442
- .timestamp, .user-info {
443
- padding: 5px 10px;
444
- background: #fff;
445
- border-radius: 4px;
446
- border: 1px solid #e9ecef;
447
- }
448
-
449
- /* File List Styles */
450
- .file-list {
451
- margin: 10px 0;
452
- padding: 10px;
453
- border: 1px solid #ddd;
454
- border-radius: 4px;
455
- }
456
- .file-item {
457
- display: flex;
458
- justify-content: space-between;
459
- padding: 5px 0;
460
- }
461
-
462
- /* Collapsible Sections */
463
- details {
464
- margin: 10px 0;
465
- border: 1px solid #e9ecef;
466
- border-radius: 8px;
467
- overflow: hidden;
468
- }
469
- summary {
470
- padding: 15px;
471
- background: #f8f9fa;
472
- cursor: pointer;
473
- font-weight: 600;
474
- display: flex;
475
- align-items: center;
476
- transition: background-color 0.3s;
477
- }
478
- summary:hover {
479
- background: #e9ecef;
480
- }
481
- .help-content {
482
- padding: 15px;
483
- background: white;
484
- }
485
-
486
- /* Example Sections */
487
- .examples-wrapper {
488
- margin: 20px 0;
489
- }
490
- .example-section {
491
- margin: 10px 0;
492
- }
493
- .example-content {
494
- padding: 15px;
495
- background: white;
496
- }
497
- .example-btn {
498
- width: 100%;
499
- text-align: left;
500
- padding: 8px 12px;
501
- background: #f8f9fa;
502
- border: 1px solid #e9ecef;
503
- border-radius: 4px;
504
- cursor: pointer;
505
- transition: all 0.3s;
506
- }
507
- .example-btn:hover {
508
- background: #e9ecef;
509
- border-color: #dee2e6;
510
- }
511
-
512
- /* Chat Message Styling */
513
- .message-wrap {
514
- display: flex;
515
- flex-direction: column;
516
- gap: 1rem;
517
- }
518
- .user-message, .assistant-message {
519
- padding: 1rem;
520
- border-radius: 8px;
521
- max-width: 90%;
522
- }
523
- .user-message {
524
- align-self: flex-end;
525
- background: #007AFF;
526
- color: white;
527
- }
528
- .assistant-message {
529
- align-self: flex-start;
530
- background: #f8f9fa;
531
- border: 1px solid #e9ecef;
532
- }
533
-
534
- /* Code Block Styling */
535
- .code-block {
536
- position: relative;
537
- margin: 1rem 0;
538
- border-radius: 8px;
539
- overflow: hidden;
540
- border: 1px solid #e9ecef;
541
- }
542
- .code-header {
543
- display: flex;
544
- justify-content: space-between;
545
- align-items: center;
546
- padding: 0.5rem 1rem;
547
- background: #f8f9fa;
548
- border-bottom: 1px solid #e9ecef;
549
- }
550
- .code-title {
551
- font-family: monospace;
552
- font-weight: bold;
553
- color: #495057;
554
- }
555
- .code-copy-btn {
556
- padding: 4px 8px;
557
- font-size: 12px;
558
- color: #6c757d;
559
- background: white;
560
- border: 1px solid #ced4da;
561
- border-radius: 4px;
562
- cursor: pointer;
563
- transition: all 0.2s;
564
- }
565
- .code-copy-btn:hover {
566
- background: #e9ecef;
567
- border-color: #adb5bd;
568
- }
569
- .code-content {
570
- padding: 1rem;
571
- background: #282a36;
572
- color: #f8f8f2;
573
- overflow-x: auto;
574
- font-family: monospace;
575
- }
576
-
577
- /* Media Queries */
578
- @media (max-width: 768px) {
579
- .gr-form { flex-direction: column !important; }
580
- .gr-group { margin: 0.5rem 0 !important; }
581
- }
582
- .clear-button {
583
- background: #dc3545 !important;
584
- color: white !important;
585
- border: none !important;
586
- padding: 0.5rem 1rem !important;
587
- border-radius: 4px !important;
588
- cursor: pointer !important;
589
- transition: background-color 0.2s !important;
590
- }
591
-
592
- .clear-button:hover {
593
- background: #c82333 !important;
594
- }
595
-
596
- /* Button Container */
597
- .button-container {
598
- display: flex;
599
- gap: 10px;
600
- margin-top: 10px;
601
- }
602
-
603
- .button-container button {
604
- flex: 1;
605
- }
606
- </style>
607
-
608
- <script>
609
- function copyCode(blockId) {
610
- const codeBlock = document.getElementById(blockId);
611
- const code = codeBlock.innerText;
612
- navigator.clipboard.writeText(code).then(() => {
613
- const btn = document.querySelector(`#${blockId}-btn`);
614
- btn.innerText = 'Copied!';
615
- setTimeout(() => {
616
- btn.innerText = 'Copy';
617
- }, 2000);
618
- });
619
- }
620
-
621
- function setQuestion(text) {
622
- const chatInput = document.querySelector('textarea[data-testid="textbox"]');
623
- if (chatInput) {
624
- chatInput.value = text;
625
- chatInput.focus();
626
- }
627
- }
628
- </script>
629
- """)
630
-
631
- # Header
632
- with gr.Row(elem_classes="container"):
633
- gr.Markdown(f"""
634
- # AI Github Repository Chat
635
-
636
- <div class="header-info">
637
- <span class="timestamp">Current Date and Time (UTC - YYYY-MM-DD HH:MM:SS formatted): {current_time}</span>
638
- </div>
639
- """)
640
-
641
- # Main Tabs Container
642
- with gr.Tabs() as tabs:
643
- # Configuration Tab
644
- with gr.Tab("🛠️ Konfigurasi"):
645
- provider = gr.Radio(
646
- choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
647
- label="Penyedia AI",
648
- value=AIProvider.XAI
649
- )
650
-
651
- with gr.Group() as api_settings:
652
- # X.AI API Key section
653
- with gr.Row():
654
- with gr.Column(scale=3):
655
- xai_key = gr.Textbox(
656
- label="X.AI (Grok) API Key",
657
- type="password",
658
- placeholder="Opsional - Klik (?) untuk info",
659
- show_label=True
660
- )
661
- with gr.Column(scale=1):
662
- gr.Markdown("""
663
- <details>
664
- <summary>❓ Cara Mendapatkan X.AI API Key</summary>
665
- <div class="help-content">
666
- <ol>
667
- <li>Kunjungi <a href="https://x.ai" target="_blank">X.AI Developer Portal</a></li>
668
- <li>Daftar/Login ke akun Anda</li>
669
- <li>Buat API Key baru</li>
670
- <li>Salin API Key</li>
671
- </ol>
672
- <p><strong>Catatan:</strong></p>
673
- <ul>
674
- <li>Jika tidak diisi, akan menggunakan API key default</li>
675
- <li>Masukkan API key Anda sendiri jika default mencapai limit</li>
676
- </ul>
677
- </div>
678
- </details>
679
- """)
680
-
681
- # Gemini API Key section
682
- with gr.Row():
683
- with gr.Column(scale=3):
684
- gemini_key = gr.Textbox(
685
- label="Gemini API Key",
686
- type="password",
687
- placeholder="Opsional - Klik (?) untuk info",
688
- show_label=True
689
- )
690
- with gr.Column(scale=1):
691
- gr.Markdown("""
692
- <details>
693
- <summary>❓ Cara Mendapatkan Gemini API Key</summary>
694
- <div class="help-content">
695
- <ol>
696
- <li>Kunjungi <a href="https://makersuite.google.com/app/apikey" target="_blank">Google AI Studio</a></li>
697
- <li>Login dengan akun Google Anda</li>
698
- <li>Klik "Create API Key"</li>
699
- <li>Salin API Key yang dihasilkan</li>
700
- </ol>
701
- <p><strong>Catatan:</strong></p>
702
- <ul>
703
- <li>Gemini memberikan kuota gratis setiap bulan</li>
704
- <li>Key bisa dibuat ulang jika diperlukan</li>
705
- <li>Monitor penggunaan di <a href="https://console.cloud.google.com/" target="_blank">Google Cloud Console</a></li>
706
- </ul>
707
- </div>
708
- </details>
709
- """)
710
-
711
- with gr.Row():
712
- model_dropdown = gr.Dropdown(
713
- label="Model AI",
714
- choices=XAI_MODELS,
715
- value="grok-2-latest",
716
- interactive=True
717
- )
718
-
719
- # Repository Analysis Tab
720
- with gr.Tab("📊 Analisis Repository"):
721
- with gr.Group():
722
- # Repository URL input
723
- with gr.Row():
724
- repo_url = gr.Textbox(
725
- label="URL Repository GitHub",
726
- placeholder="https://github.com/username/repository",
727
- elem_classes="mobile-full"
728
- )
729
-
730
- # GitHub Token section
731
- with gr.Row():
732
- with gr.Column(scale=2):
733
- github_token = gr.Textbox(
734
- label="Token GitHub",
735
- type="password",
736
- placeholder="Klik (?) untuk panduan",
737
- elem_classes="mobile-full"
738
- )
739
- gr.Markdown("""
740
- <details>
741
- <summary>❓ Cara Mendapatkan GitHub Token</summary>
742
- <div class="help-content">
743
- <ol>
744
- <li>Kunjungi <a href="https://github.com/settings/tokens" target="_blank">GitHub Token Settings</a></li>
745
- <li>Klik "Generate new token" > "Generate new token (classic)"</li>
746
- <li>Beri nama token Anda di "Note"</li>
747
- <li>Pilih scope:
748
- <ul>
749
- <li><code>repo</code> (untuk akses repository private)</li>
750
- <li><code>read:packages</code> (opsional, untuk akses package)</li>
751
- </ul>
752
- </li>
753
- <li>Klik "Generate token"</li>
754
- <li><strong>PENTING:</strong> Salin token segera! Token hanya ditampilkan sekali</li>
755
- </ol>
756
- </div>
757
- </details>
758
- """)
759
- with gr.Column(scale=1):
760
- branch = gr.Textbox(
761
- label="Branch (opsional)",
762
- placeholder="main",
763
- elem_classes="mobile-full"
764
- )
765
-
766
- # Clone Button and Status
767
- clone_button = gr.Button(
768
- "🔄 Clone Repository",
769
- variant="primary",
770
- elem_classes="mobile-full"
771
- )
772
-
773
- clone_status = gr.Markdown(
774
- value="",
775
- label="Status Repository",
776
- elem_classes="mobile-full"
777
- )
778
-
779
- # File Selection
780
- with gr.Group():
781
- gr.Markdown("### 📎 File yang Dipilih")
782
- with gr.Row():
783
- file_selector = gr.Dropdown(
784
- label="Pilih File dari Repository",
785
- choices=[],
786
- multiselect=True,
787
- value=[],
788
- allow_custom_value=True,
789
- max_choices=None,
790
- elem_classes="mobile-full"
791
- )
792
-
793
- file_list = gr.HTML(
794
- value="<div class='file-list'>Belum ada file yang dipilih</div>",
795
- label="Daftar File Terpilih"
796
- )
797
-
798
- # Examples Tab
799
- with gr.Tab("💡 Examples"):
800
- gr.Markdown("""
801
- <style>
802
- /* Styling untuk collapse */
803
- .examples-container {
804
- padding: 20px;
805
- background: #fff;
806
- border-radius: 8px;
807
- }
808
-
809
- .category-collapse {
810
- margin-bottom: 15px;
811
- border: 1px solid #e9ecef;
812
- border-radius: 8px;
813
- overflow: hidden;
814
- }
815
-
816
- .category-header {
817
- padding: 15px;
818
- background: #f8f9fa;
819
- cursor: pointer;
820
- user-select: none;
821
- display: flex;
822
- align-items: center;
823
- justify-content: space-between;
824
- transition: background-color 0.2s;
825
- }
826
-
827
- .category-header:hover {
828
- background: #e9ecef;
829
- }
830
-
831
- .category-content {
832
- padding: 15px;
833
- display: none;
834
- border-top: 1px solid #e9ecef;
835
- }
836
-
837
- .category-collapse.active .category-content {
838
- display: block;
839
- animation: slideDown 0.3s ease-out;
840
- }
841
-
842
- .category-header::after {
843
- content: '▼';
844
- font-size: 12px;
845
- transition: transform 0.3s;
846
- }
847
-
848
- .category-collapse.active .category-header::after {
849
- transform: rotate(180deg);
850
- }
851
-
852
- .example-button {
853
- display: block;
854
- width: 100%;
855
- padding: 10px;
856
- margin: 5px 0;
857
- text-align: left;
858
- background: #fff;
859
- border: 1px solid #e9ecef;
860
- border-radius: 4px;
861
- cursor: pointer;
862
- transition: all 0.2s;
863
- }
864
-
865
- .example-button:hover {
866
- background: #f8f9fa;
867
- border-color: #dee2e6;
868
- }
869
-
870
- @keyframes slideDown {
871
- from {
872
- opacity: 0;
873
- transform: translateY(-10px);
874
- }
875
- to {
876
- opacity: 1;
877
- transform: translateY(0);
878
- }
879
- }
880
- </style>
881
-
882
- <div class="examples-container">
883
- <h3>🎯 Contoh Pertanyaan</h3>
884
- <p>Klik pada kategori untuk melihat contoh pertanyaan yang bisa diajukan</p>
885
-
886
- <div class="category-collapse">
887
- <div class="category-header">
888
- 🔍 Analisis Kode
889
- </div>
890
- <div class="category-content">
891
- <button class="example-button" onclick="setQuestion('Jelaskan logika dan alur dari kode ini')">
892
- Jelaskan logika dari kode
893
- </button>
894
- <button class="example-button" onclick="setQuestion('Bagaimana cara mengoptimalkan performa kode ini?')">
895
- Optimasi performa
896
- </button>
897
- <button class="example-button" onclick="setQuestion('Apakah ada potential bugs yang perlu diperbaiki?')">
898
- Cek potential bugs
899
- </button>
900
- </div>
901
- </div>
902
-
903
- <div class="category-collapse">
904
- <div class="category-header">
905
- 📚 Best Practices
906
- </div>
907
- <div class="category-content">
908
- <button class="example-button" onclick="setQuestion('Apa saja best practices yang bisa diterapkan di kode ini?')">
909
- Saran best practices
910
- </button>
911
- <button class="example-button" onclick="setQuestion('Bagaimana cara meningkatkan maintainability kode ini?')">
912
- Improve maintainability
913
- </button>
914
- <button class="example-button" onclick="setQuestion('Berikan saran untuk meningkatkan code quality')">
915
- Improve code quality
916
- </button>
917
- </div>
918
- </div>
919
-
920
- <div class="category-collapse">
921
- <div class="category-header">
922
- 🧪 Testing
923
- </div>
924
- <div class="category-content">
925
- <button class="example-button" onclick="setQuestion('Buatkan unit test untuk fungsi ini')">
926
- Generate unit tests
927
- </button>
928
- <button class="example-button" onclick="setQuestion('Bagaimana cara test error handling yang baik?')">
929
- Test error handling
930
- </button>
931
- <button class="example-button" onclick="setQuestion('Berikan saran untuk meningkatkan test coverage')">
932
- Improve test coverage
933
- </button>
934
- </div>
935
- </div>
936
-
937
- <div class="category-collapse">
938
- <div class="category-header">
939
- 📝 Dokumentasi
940
- </div>
941
- <div class="category-content">
942
- <button class="example-button" onclick="setQuestion('Buatkan dokumentasi untuk fungsi-fungsi utama')">
943
- Generate documentation
944
- </button>
945
- <button class="example-button" onclick="setQuestion('Buat README.md untuk project ini')">
946
- Create README.md
947
- </button>
948
- <button class="example-button" onclick="setQuestion('Jelaskan cara penggunaan library/framework')">
949
- Usage guide
950
- </button>
951
- </div>
952
- </div>
953
- </div>
954
-
955
- <script>
956
- document.addEventListener('DOMContentLoaded', function() {
957
- // Handle collapse functionality
958
- const categories = document.querySelectorAll('.category-collapse');
959
-
960
- categories.forEach(category => {
961
- const header = category.querySelector('.category-header');
962
-
963
- header.addEventListener('click', () => {
964
- const isActive = category.classList.contains('active');
965
-
966
- // Close all categories
967
- categories.forEach(c => c.classList.remove('active'));
968
-
969
- // If clicked category wasn't active, open it
970
- if (!isActive) {
971
- category.classList.add('active');
972
- }
973
- });
974
- });
975
- });
976
-
977
- // Function to set question in chat input
978
- function setQuestion(text) {
979
- const chatInput = document.querySelector('textarea[data-testid="textbox"]');
980
- if (chatInput) {
981
- chatInput.value = text;
982
- chatInput.focus();
983
- }
984
- }
985
- </script>
986
- """)
987
-
988
- # Chat Interface (outside tabs)
989
- with gr.Group():
990
- chat_history = gr.Chatbot(
991
- label="📝 Riwayat Chat",
992
- height=500,
993
- show_label=True,
994
- type="messages",
995
- elem_classes="mobile-full"
996
- )
997
-
998
- with gr.Row():
999
- chat_input = gr.Textbox(
1000
- label="💭 Tanyakan tentang Repository",
1001
- placeholder="Ketik pertanyaan Anda di sini...",
1002
- lines=3,
1003
- elem_classes="mobile-full",
1004
- )
1005
- with gr.Column(scale=1):
1006
- send_button = gr.Button("📤 Kirim", variant="primary")
1007
- clear_button = gr.Button("🧹 Bersihkan Semua", variant="secondary")
1008
-
1009
- # Event Handlers
1010
- def handle_clone(repo_url, github_token, branch):
1011
- if not repo_url:
1012
- return (
1013
- "⚠️ URL repository diperlukan!",
1014
- gr.Dropdown(choices=[]),
1015
- "<div class='file-list'>Belum ada file yang dipilih</div>"
1016
- )
1017
-
1018
- success, message = analyzer.clone_repository(repo_url, github_token, branch)
1019
-
1020
- if success:
1021
- files = sorted(list(analyzer.repo_content.keys()))
1022
- return (
1023
- message,
1024
- gr.Dropdown(choices=files, value=[]),
1025
- "<div class='file-list'>Belum ada file yang dipilih</div>"
1026
- )
1027
-
1028
- return (
1029
- message,
1030
- gr.Dropdown(choices=[]),
1031
- "<div class='file-list'>Belum ada file yang dipilih</div>"
1032
- )
1033
-
1034
- def update_file_list(selected):
1035
- if not selected:
1036
- return "<div class='file-list'>Belum ada file yang dipilih</div>"
1037
-
1038
- html = "<div class='file-list'>"
1039
- for file in selected:
1040
- html += f"<div class='file-item'><span>{file}</span></div>"
1041
- html += "</div>"
1042
- return html
1043
-
1044
- def clear_chat_history():
1045
- return []
1046
-
1047
- def update_model_list(provider_choice):
1048
- if provider_choice == AIProvider.XAI:
1049
- return gr.Dropdown(choices=XAI_MODELS, value="grok-2-latest")
1050
- elif provider_choice == AIProvider.GEMINI:
1051
- return gr.Dropdown(choices=GEMINI_MODELS, value="gemini-1.5-mini")
1052
- else: # OLLAMA
1053
- return gr.Dropdown(choices=OLLAMA_MODELS, value="llama2")
1054
-
1055
- # Connect Events
1056
- provider.change(
1057
- fn=update_model_list,
1058
- inputs=[provider],
1059
- outputs=[model_dropdown]
1060
- )
1061
-
1062
- clone_button.click(
1063
- fn=handle_clone,
1064
- inputs=[repo_url, github_token, branch],
1065
- outputs=[clone_status, file_selector, file_list]
1066
- )
1067
-
1068
- file_selector.change(
1069
- fn=update_file_list,
1070
- inputs=[file_selector],
1071
- outputs=[file_list]
1072
- )
1073
-
1074
- clear_button = gr.Button("🧹 Bersihkan Semua", variant="secondary")
1075
-
1076
- # Chat events
1077
- clear_button.click(
1078
- fn=clear_all,
1079
- outputs=[
1080
- repo_url,
1081
- github_token,
1082
- branch,
1083
- clone_status,
1084
- chat_history,
1085
- file_selector,
1086
- file_list,
1087
- xai_key,
1088
- gemini_key,
1089
- model_dropdown,
1090
- ]
1091
- )
1092
-
1093
- send_button.click(
1094
- fn=handle_chat,
1095
- inputs=[
1096
- chat_input,
1097
- chat_history,
1098
- provider,
1099
- model_dropdown,
1100
- xai_key,
1101
- gemini_key,
1102
- file_selector
1103
- ],
1104
- outputs=chat_history,
1105
- show_progress=True
1106
- ).then(
1107
- fn=lambda: gr.update(value=""),
1108
- outputs=chat_input
1109
- )
1110
-
1111
- chat_input.submit(
1112
- fn=handle_chat,
1113
- inputs=[
1114
- chat_input,
1115
- chat_history,
1116
- provider,
1117
- model_dropdown,
1118
- xai_key,
1119
- gemini_key,
1120
- file_selector
1121
- ],
1122
- outputs=chat_history,
1123
- show_progress=True
1124
- ).then(
1125
- fn=lambda: gr.update(value=""),
1126
- outputs=chat_input
1127
- )
1128
-
1129
- return app
1130
-
1131
-
1132
- if __name__ == "__main__":
1133
- print("""
1134
- 🚀 Starting Repository Chat Analysis
1135
- ====================================
1136
- """)
1137
-
1138
- app = create_ui()
1139
- app.launch(share=True, server_name="0.0.0.0", server_port=7860, debug=True)