ErNewdev0 commited on
Commit
12c0fac
Β·
verified Β·
1 Parent(s): 3ee4379

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +720 -590
app.py CHANGED
@@ -16,38 +16,41 @@ import dotenv
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('XAI_API_KEY')
25
- DEFAULT_GEMINI_KEY = os.getenv('GEMINI_API_KEY')
 
 
 
26
 
27
  # API settings
28
- OLLAMA_API = os.environ.get('OLLAMA_API', 'http://localhost:11434')
29
- XAI_BASE_URL = 'https://api.x.ai/v1'
30
 
31
  # Model lists
32
  OLLAMA_MODELS = [
33
- 'llama2',
34
- 'codellama',
35
- 'mistral',
36
- 'neural-chat',
37
- 'starling-lm',
38
- 'dolphin-phi',
39
- 'phi',
40
- 'orca-mini',
41
  ]
42
 
43
  XAI_MODELS = [
44
- 'grok-2-latest',
45
- 'grok-1',
46
  ]
47
 
48
  GEMINI_MODELS = [
49
- 'gemini-1.5-mini',
50
- 'gemini-pro-vision',
51
  ]
52
 
53
  # Help texts
@@ -112,296 +115,276 @@ Note:
112
 
113
 
114
  class AIProvider:
115
- OLLAMA = 'ollama'
116
- GEMINI = 'gemini'
117
- XAI = 'xai'
118
 
119
 
120
  class RepoAnalyzer:
121
- def __init__(self):
122
- self.current_repo = None
123
- self.repo_content = {}
124
- self.chat_history = []
125
-
126
- async def stream_xai_response(
127
- self, prompt: str, api_key: str = None, model: str = 'grok-2-latest'
128
- ) -> AsyncGenerator[str, None]:
129
- """Stream response dari X.AI (Grok) API"""
130
- try:
131
- # Use default key if none provided
132
- actual_key = api_key if api_key else DEFAULT_XAI_KEY
133
-
134
- if not actual_key:
135
- yield '⚠️ API Key X.AI diperlukan. Gunakan key Anda sendiri atau tunggu reset limit default key.'
136
- return
137
-
138
- client = AsyncOpenAI(api_key=actual_key, base_url=XAI_BASE_URL)
139
-
140
- # Prepare messages with repository context if available
141
- messages = [
142
- {
143
- 'role': 'system',
144
- 'content': 'Anda adalah asisten AI yang membantu menganalisis repository code. Berikan respons dalam Bahasa Indonesia.',
145
- }
146
- ]
147
-
148
- if self.current_repo:
149
- context = f'Repository: {self.current_repo}\n\n'
150
- repo_files = '\n'.join(list(self.repo_content.keys()))
151
- context += f'Files in repository:\n{repo_files}\n\n'
152
- messages.append({'role': 'system', 'content': context})
153
-
154
- messages.append({'role': 'user', 'content': prompt})
155
-
156
- stream = await client.chat.completions.create(
157
- model=model, messages=messages, stream=True
158
- )
159
-
160
- full_response = ''
161
- async for chunk in stream:
162
- if chunk.choices[0].delta.content:
163
- content = chunk.choices[0].delta.content
164
- full_response += content
165
- yield content
166
-
167
- self.chat_history.append({'role': 'user', 'content': prompt})
168
- self.chat_history.append({'role': 'assistant', 'content': full_response})
169
-
170
- except Exception as e:
171
- error_msg = f'⚠️ Error dalam X.AI API: {str(e)}'
172
- print(error_msg)
173
- yield error_msg
174
-
175
- async def stream_gemini_response(
176
- self, prompt: str, api_key: str
177
- ) -> AsyncGenerator[str, None]:
178
- """Stream response dari Gemini API"""
179
- try:
180
- if not api_key:
181
- yield '⚠️ API Key Gemini diperlukan. Klik icon bantuan (?) di samping input API Key untuk panduan mendapatkan key.'
182
- return
183
-
184
- genai.configure(api_key=api_key)
185
- model = genai.GenerativeModel('gemini-pro')
186
-
187
- # Tambahkan konteks repository jika ada
188
- if self.current_repo:
189
- context = f'Repository: {self.current_repo}\n\n'
190
- repo_files = '\n'.join(list(self.repo_content.keys()))
191
- context += f'Files in repository:\n{repo_files}\n\n'
192
- prompt = context + prompt
193
-
194
- response = model.generate_content(
195
- prompt,
196
- generation_config={'temperature': 0.7, 'top_p': 0.8, 'top_k': 40},
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(
215
- self, repo_url: str, github_token: str, branch: str = None
216
- ) -> tuple[bool, str]:
217
- """Clone repository GitHub dengan autentikasi"""
218
- if not repo_url:
219
- return False, '⚠️ URL repository diperlukan'
220
-
221
- repo_name = repo_url.split('/')[-1].replace('.git', '')
222
-
223
- if os.path.exists(repo_name):
224
- subprocess.run(['rm', '-rf', repo_name], check=True)
225
-
226
- try:
227
- owner_repo = '/'.join(repo_url.split('/')[-2:])
228
-
229
- # Cek apakah repository private
230
- headers = {'Authorization': f'token {github_token}'} if github_token else {}
231
- repo_check = requests.get(
232
- f'https://api.github.com/repos/{owner_repo}', headers=headers
233
- )
234
-
235
- if repo_check.status_code == 404:
236
- return False, '⚠️ Repository tidak ditemukan. Periksa URL repository.'
237
- elif repo_check.status_code == 401:
238
- return (
239
- False,
240
- '⚠️ Token GitHub tidak valid. Klik icon bantuan (?) untuk panduan mendapatkan token.',
241
- )
242
- elif repo_check.status_code == 403 and repo_check.json().get(
243
- 'private', False
244
- ):
245
- return (
246
- False,
247
- "⚠️ Ini adalah repository private. Token GitHub dengan akses 'repo' diperlukan.",
248
- )
249
-
250
- auth_url = (
251
- f'https://{github_token}@github.com/{owner_repo}'
252
- if github_token
253
- else f'https://github.com/{owner_repo}'
254
- )
255
-
256
- cmd = ['git', 'clone']
257
- if branch:
258
- cmd.extend(['--branch', branch])
259
- cmd.append(auth_url)
260
-
261
- process = subprocess.run(
262
- cmd,
263
- capture_output=True,
264
- text=True,
265
- env=dict(os.environ, GIT_ASKPASS='echo', GIT_TERMINAL_PROMPT='0'),
266
- )
267
-
268
- if process.returncode == 0:
269
- self.current_repo = repo_name
270
- # Scan dan simpan konten repository
271
- file_count = 0
272
- for file_path in Path(repo_name).rglob('*'):
273
- if file_path.is_file() and '.git' not in str(file_path):
274
- success, content = self.read_file_safely(str(file_path))
275
- if success:
276
- self.repo_content[str(file_path)] = content
277
- file_count += 1
278
-
279
- return (
280
- True,
281
- f'βœ… Repository berhasil di-clone!\n\nNama: {repo_name}\nJumlah file: {file_count}\n\nAnda sekarang bisa mengajukan pertanyaan tentang repository ini.',
282
- )
283
- else:
284
- return False, f'⚠️ Gagal clone repository:\n{process.stderr}'
285
-
286
- except Exception as e:
287
- return False, f'⚠️ Error: {str(e)}'
288
-
289
- def read_file_safely(self, file_path: str) -> tuple[bool, str]:
290
- """Baca file dengan aman menggunakan berbagai encoding"""
291
- encodings = ['utf-8', 'latin-1', 'cp1252']
292
- for encoding in encodings:
293
- try:
294
- with open(file_path, 'r', encoding=encoding) as f:
295
- content = f.read()
296
- return True, content
297
- except Exception as e:
298
- continue
299
- return False, 'Tidak dapat membaca file dengan encoding yang didukung'
300
 
301
 
302
  analyzer = RepoAnalyzer()
303
 
304
 
305
- async def handle_chat(
306
- message,
307
- history,
308
- provider_choice,
309
- model_name,
310
- xai_key,
311
- gemini_key,
312
- selected_files,
313
- analyzer=analyzer,
314
- ):
315
- """Menangani interaksi chat dengan model AI"""
316
- if not analyzer.current_repo:
317
- new_message = {
318
- 'role': 'assistant',
319
- 'content': '⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan.',
320
- }
321
- history = history or []
322
- history.append({'role': 'user', 'content': message})
323
- history.append(new_message)
324
- yield history
325
- return
326
-
327
- history = history or []
328
-
329
- # Format user message
330
- user_message = f'<div class="user-message">{message}</div>'
331
- history.append({'role': 'user', 'content': user_message})
332
-
333
- # Initialize assistant message
334
- assistant_message = '<div class="assistant-message">'
335
- history.append({'role': 'assistant', 'content': assistant_message})
336
-
337
- try:
338
- # Add context about selected files to the prompt
339
- file_context = ''
340
- if selected_files:
341
- file_context = '\n\nFile yang dipilih:\n'
342
- for file in selected_files:
343
- content = analyzer.repo_content.get(file, '')
344
- if content:
345
- file_context += f'\n{file}:\n```\n{content}\n```\n'
346
-
347
- enhanced_message = f'{message}\n{file_context}'
348
-
349
- full_response = ''
350
- if provider_choice == AIProvider.XAI:
351
- async for chunk in analyzer.stream_xai_response(
352
- enhanced_message, xai_key, model_name
353
- ):
354
- full_response += chunk
355
- # Format the response with code blocks
356
- formatted_response = format_response(full_response)
357
- history[-1]['content'] = (
358
- f'<div class="assistant-message">{formatted_response}</div>'
359
- )
360
- await asyncio.sleep(0.05)
361
- yield history
362
-
363
- elif provider_choice == AIProvider.GEMINI:
364
- async for chunk in analyzer.stream_gemini_response(
365
- enhanced_message, gemini_key or DEFAULT_GEMINI_KEY
366
- ):
367
- full_response += chunk
368
- formatted_response = format_response(full_response)
369
- history[-1]['content'] = (
370
- f'<div class="assistant-message">{formatted_response}</div>'
371
- )
372
- await asyncio.sleep(0.05)
373
- yield history
374
-
375
- else: # OLLAMA
376
- response = analyze_with_ollama(model_name, enhanced_message)
377
- words = response.split()
378
- for i in range(len(words)):
379
- full_response = ' '.join(words[: i + 1])
380
- formatted_response = format_response(full_response)
381
- history[-1]['content'] = (
382
- f'<div class="assistant-message">{formatted_response}</div>'
383
- )
384
- await asyncio.sleep(0.05)
385
- yield history
386
-
387
- except Exception as e:
388
- error_msg = f'⚠️ Error: {str(e)}'
389
- history[-1]['content'] = f'<div class="assistant-message">{error_msg}</div>'
390
- yield history
391
-
392
 
393
  def format_response(text):
394
- """Format response text with code blocks"""
395
- import re
396
- import uuid
397
-
398
- # Replace code blocks with styled versions
399
- def replace_code_block(match):
400
- language = match.group(1) or ''
401
- code = match.group(2)
402
- block_id = f'code-block-{uuid.uuid4().hex[:8]}'
403
-
404
- return f'''
405
  <div class="code-block">
406
  <div class="code-header">
407
  <span class="code-title">{language}</span>
@@ -411,22 +394,22 @@ def format_response(text):
411
  </div>
412
  '''
413
 
414
- # Replace ```language\ncode``` blocks
415
- text = re.sub(r'```(\w+)?\n(.*?)```', replace_code_block, text, flags=re.DOTALL)
416
-
417
- # Replace inline code
418
- text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
419
-
420
- return text
421
 
422
 
423
  def create_ui():
424
- global analyzer
425
- current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
426
 
427
- with gr.Blocks(title='Open Repo AI', theme=gr.themes.Soft()) as app:
428
- # CSS Styling
429
- gr.Markdown("""
430
  <style>
431
  /* General Styles */
432
  .container { max-width: 100% !important; padding: 1rem; }
@@ -607,40 +590,38 @@ def create_ui():
607
  </script>
608
  """)
609
 
610
- # Header
611
- with gr.Row(elem_classes='container'):
612
- gr.Markdown(f"""
613
  # AI Github Repository Chat
614
 
615
  <div class="header-info">
616
  <span class="timestamp">Current Date and Time (UTC - YYYY-MM-DD HH:MM:SS formatted): {current_time}</span>
617
- <span class="user-info">Current User's Login: {CURRENT_USER}</span>
618
  </div>
619
  """)
620
 
621
- # Main Tabs
622
- # Main Tabs Container
623
- with gr.Tabs() as tabs:
624
- # Configuration Tab
625
- with gr.Tab('πŸ› οΈ Konfigurasi'):
626
- provider = gr.Radio(
627
- choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
628
- label='Penyedia AI',
629
- value=AIProvider.XAI,
630
- )
631
-
632
- with gr.Group() as api_settings:
633
- # X.AI API Key section
634
- with gr.Row():
635
- with gr.Column(scale=3):
636
- xai_key = gr.Textbox(
637
- label='X.AI (Grok) API Key',
638
- type='password',
639
- placeholder='Opsional - Klik (?) untuk info',
640
- show_label=True,
641
- )
642
- with gr.Column(scale=1):
643
- gr.Markdown("""
644
  <details>
645
  <summary>❓ Cara Mendapatkan X.AI API Key</summary>
646
  <div class="help-content">
@@ -659,17 +640,17 @@ def create_ui():
659
  </details>
660
  """)
661
 
662
- # Gemini API Key section
663
- with gr.Row():
664
- with gr.Column(scale=3):
665
- gemini_key = gr.Textbox(
666
- label='Gemini API Key',
667
- type='password',
668
- placeholder='Opsional - Klik (?) untuk info',
669
- show_label=True,
670
- )
671
- with gr.Column(scale=1):
672
- gr.Markdown("""
673
  <details>
674
  <summary>❓ Cara Mendapatkan Gemini API Key</summary>
675
  <div class="help-content">
@@ -689,35 +670,35 @@ def create_ui():
689
  </details>
690
  """)
691
 
692
- with gr.Row():
693
- model_dropdown = gr.Dropdown(
694
- label='Model AI',
695
- choices=XAI_MODELS,
696
- value='grok-2-latest',
697
- interactive=True,
698
- )
699
-
700
- # Repository Analysis Tab
701
- with gr.Tab('πŸ“Š Analisis Repository'):
702
- with gr.Group():
703
- # Repository URL input
704
- with gr.Row():
705
- repo_url = gr.Textbox(
706
- label='URL Repository GitHub',
707
- placeholder='https://github.com/username/repository',
708
- elem_classes='mobile-full',
709
- )
710
-
711
- # GitHub Token section
712
- with gr.Row():
713
- with gr.Column(scale=2):
714
- github_token = gr.Textbox(
715
- label='Token GitHub',
716
- type='password',
717
- placeholder='Klik (?) untuk panduan',
718
- elem_classes='mobile-full',
719
- )
720
- gr.Markdown("""
721
  <details>
722
  <summary>❓ Cara Mendapatkan GitHub Token</summary>
723
  <div class="help-content">
@@ -737,221 +718,370 @@ def create_ui():
737
  </div>
738
  </details>
739
  """)
740
- with gr.Column(scale=1):
741
- branch = gr.Textbox(
742
- label='Branch (opsional)',
743
- placeholder='main',
744
- elem_classes='mobile-full',
745
- )
746
-
747
- # Clone Button and Status
748
- clone_button = gr.Button(
749
- 'πŸ”„ Clone Repository',
750
- variant='primary',
751
- elem_classes='mobile-full',
752
- )
753
-
754
- clone_status = gr.Markdown(
755
- value='', label='Status Repository', elem_classes='mobile-full'
756
- )
757
-
758
- # File Selection
759
- with gr.Group():
760
- gr.Markdown('### πŸ“Ž File yang Dipilih')
761
- with gr.Row():
762
- file_selector = gr.Dropdown(
763
- label='Pilih File dari Repository',
764
- choices=[],
765
- multiselect=True,
766
- value=[],
767
- allow_custom_value=True,
768
- max_choices=None,
769
- elem_classes='mobile-full',
770
- )
771
-
772
- file_list = gr.HTML(
773
- value="<div class='file-list'>Belum ada file yang dipilih</div>",
774
- label='Daftar File Terpilih',
775
- )
776
-
777
- # Examples Tab
778
- with gr.Tab('πŸ’‘ Examples'):
779
- with gr.Group():
780
- gr.Markdown("""
781
- ### 🎯 Contoh Pertanyaan
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
782
  <p>Klik pada kategori untuk melihat contoh pertanyaan yang bisa diajukan</p>
783
- """)
784
-
785
- gr.Markdown("""
786
- <div class="examples-wrapper">
787
- <details class="example-section">
788
- <summary>πŸ” Analisis Kode</summary>
789
- <div class="example-content">
790
- <ul>
791
- <li><button class="example-btn" onclick="setQuestion('Jelaskan logika dan alur dari kode ini')">Jelaskan logika dari kode</button></li>
792
- <li><button class="example-btn" onclick="setQuestion('Bagaimana cara mengoptimalkan performa kode ini?')">Optimasi performa</button></li>
793
- <li><button class="example-btn" onclick="setQuestion('Apakah ada potential bugs yang perlu diperbaiki?')">Cek potential bugs</button></li>
794
- </ul>
795
- </div>
796
- </details>
797
-
798
- <details class="example-section">
799
- <summary>πŸ“š Best Practices</summary>
800
- <div class="example-content">
801
- <ul>
802
- <li><button class="example-btn" onclick="setQuestion('Apa saja best practices yang bisa diterapkan di kode ini?')">Saran best practices</button></li>
803
- <li><button class="example-btn" onclick="setQuestion('Bagaimana cara meningkatkan maintainability kode ini?')">Improve maintainability</button></li>
804
- <li><button class="example-btn" onclick="setQuestion('Berikan saran untuk meningkatkan code quality')">Improve code quality</button></li>
805
- </ul>
806
- </div>
807
- </details>
808
-
809
- <details class="example-section">
810
- <summary>πŸ§ͺ Testing</summary>
811
- <div class="example-content">
812
- <ul>
813
- <li><button class="example-btn" onclick="setQuestion('Buatkan unit test untuk fungsi ini')">Generate unit tests</button></li>
814
- <li><button class="example-btn" onclick="setQuestion('Bagaimana cara test error handling yang baik?')">Test error handling</button></li>
815
- <li><button class="example-btn" onclick="setQuestion('Berikan saran untuk meningkatkan test coverage')">Improve test coverage</button></li>
816
- </ul>
817
- </div>
818
- </details>
819
-
820
- <details class="example-section">
821
- <summary>πŸ“ Dokumentasi</summary>
822
- <div class="example-content">
823
- <ul>
824
- <li><button class="example-btn" onclick="setQuestion('Buatkan dokumentasi untuk fungsi-fungsi utama')">Generate documentation</button></li>
825
- <li><button class="example-btn" onclick="setQuestion('Buat README.md untuk project ini')">Create README.md</button></li>
826
- <li><button class="example-btn" onclick="setQuestion('Jelaskan cara penggunaan library/framework')">Usage guide</button></li>
827
- </ul>
828
- </div>
829
- </details>
 
 
 
830
  </div>
831
- """)
832
-
833
- # Chat Interface (outside tabs)
834
- with gr.Group():
835
- chat_history = gr.Chatbot(
836
- label='πŸ“ Riwayat Chat',
837
- height=500,
838
- show_label=True,
839
- type='messages',
840
- elem_classes='mobile-full',
841
- )
842
-
843
- with gr.Row():
844
- chat_input = gr.Textbox(
845
- label='πŸ’­ Tanyakan tentang Repository',
846
- placeholder='Ketik pertanyaan Anda di sini...',
847
- lines=3,
848
- elem_classes='mobile-full',
849
- )
850
- send_button = gr.Button('πŸ“€ Kirim', variant='primary')
851
- clear_button = gr.Button('🧹 Bersihkan', variant='secondary')
852
-
853
- # Event Handlers
854
- def handle_clone(repo_url, github_token, branch):
855
- if not repo_url:
856
- return (
857
- '⚠️ URL repository diperlukan!',
858
- gr.Dropdown(choices=[]),
859
- "<div class='file-list'>Belum ada file yang dipilih</div>",
860
- )
861
-
862
- success, message = analyzer.clone_repository(repo_url, github_token, branch)
863
-
864
- if success:
865
- files = sorted(list(analyzer.repo_content.keys()))
866
- return (
867
- message,
868
- gr.Dropdown(choices=files, value=[]),
869
- "<div class='file-list'>Belum ada file yang dipilih</div>",
870
- )
871
-
872
- return (
873
- message,
874
- gr.Dropdown(choices=[]),
875
- "<div class='file-list'>Belum ada file yang dipilih</div>",
876
- )
877
-
878
- def update_file_list(selected):
879
- if not selected:
880
- return "<div class='file-list'>Belum ada file yang dipilih</div>"
881
-
882
- html = "<div class='file-list'>"
883
- for file in selected:
884
- html += f"<div class='file-item'><span>{file}</span></div>"
885
- html += '</div>'
886
- return html
887
-
888
- def clear_chat_history():
889
- return []
890
-
891
- def update_model_list(provider_choice):
892
- if provider_choice == AIProvider.XAI:
893
- return gr.Dropdown(choices=XAI_MODELS, value='grok-2-latest')
894
- elif provider_choice == AIProvider.GEMINI:
895
- return gr.Dropdown(choices=GEMINI_MODELS, value='gemini-1.5-mini')
896
- else: # OLLAMA
897
- return gr.Dropdown(choices=OLLAMA_MODELS, value='llama2')
898
-
899
- # Connect Events
900
- provider.change(
901
- fn=update_model_list, inputs=[provider], outputs=[model_dropdown]
902
- )
903
-
904
- clone_button.click(
905
- fn=handle_clone,
906
- inputs=[repo_url, github_token, branch],
907
- outputs=[clone_status, file_selector, file_list],
908
- )
909
-
910
- file_selector.change(
911
- fn=update_file_list, inputs=[file_selector], outputs=[file_list]
912
- )
913
-
914
- # Chat events
915
- clear_button.click(fn=clear_chat_history, outputs=[chat_history])
916
-
917
- send_button.click(
918
- fn=handle_chat,
919
- inputs=[
920
- chat_input,
921
- chat_history,
922
- provider,
923
- model_dropdown,
924
- xai_key,
925
- gemini_key,
926
- file_selector,
927
- ],
928
- outputs=chat_history,
929
- show_progress=True,
930
- ).then(fn=lambda: gr.update(value=''), outputs=chat_input)
931
-
932
- chat_input.submit(
933
- fn=handle_chat,
934
- inputs=[
935
- chat_input,
936
- chat_history,
937
- provider,
938
- model_dropdown,
939
- xai_key,
940
- gemini_key,
941
- file_selector,
942
- ],
943
- outputs=chat_history,
944
- show_progress=True,
945
- ).then(fn=lambda: gr.update(value=''), outputs=chat_input)
946
-
947
- return app
948
-
949
-
950
- if __name__ == '__main__':
951
- print("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
952
  πŸš€ Starting Repository Chat Analysis
953
  ====================================
954
  """)
955
 
956
- app = create_ui()
957
- app.launch(share=True, server_name='0.0.0.0', server_port=7860, debug=True)
 
16
  # Load environment variables
17
  dotenv.load_dotenv()
18
 
19
+ # Metadata# 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
 
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>
 
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
 
406
  def create_ui():
407
+ global analyzer
408
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
409
 
410
+ with gr.Blocks(title="Open Repo AI", theme=gr.themes.Soft()) as app:
411
+ # CSS Styling
412
+ gr.Markdown("""
413
  <style>
414
  /* General Styles */
415
  .container { max-width: 100% !important; padding: 1rem; }
 
590
  </script>
591
  """)
592
 
593
+ # Header
594
+ with gr.Row(elem_classes="container"):
595
+ gr.Markdown(f"""
596
  # AI Github Repository Chat
597
 
598
  <div class="header-info">
599
  <span class="timestamp">Current Date and Time (UTC - YYYY-MM-DD HH:MM:SS formatted): {current_time}</span>
 
600
  </div>
601
  """)
602
 
603
+ # Main Tabs Container
604
+ with gr.Tabs() as tabs:
605
+ # Configuration Tab
606
+ with gr.Tab("πŸ› οΈ Konfigurasi"):
607
+ provider = gr.Radio(
608
+ choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
609
+ label="Penyedia AI",
610
+ value=AIProvider.XAI
611
+ )
612
+
613
+ with gr.Group() as api_settings:
614
+ # X.AI API Key section
615
+ with gr.Row():
616
+ with gr.Column(scale=3):
617
+ xai_key = gr.Textbox(
618
+ label="X.AI (Grok) API Key",
619
+ type="password",
620
+ placeholder="Opsional - Klik (?) untuk info",
621
+ show_label=True
622
+ )
623
+ with gr.Column(scale=1):
624
+ gr.Markdown("""
 
625
  <details>
626
  <summary>❓ Cara Mendapatkan X.AI API Key</summary>
627
  <div class="help-content">
 
640
  </details>
641
  """)
642
 
643
+ # Gemini API Key section
644
+ with gr.Row():
645
+ with gr.Column(scale=3):
646
+ gemini_key = gr.Textbox(
647
+ label="Gemini API Key",
648
+ type="password",
649
+ placeholder="Opsional - Klik (?) untuk info",
650
+ show_label=True
651
+ )
652
+ with gr.Column(scale=1):
653
+ gr.Markdown("""
654
  <details>
655
  <summary>❓ Cara Mendapatkan Gemini API Key</summary>
656
  <div class="help-content">
 
670
  </details>
671
  """)
672
 
673
+ with gr.Row():
674
+ model_dropdown = gr.Dropdown(
675
+ label="Model AI",
676
+ choices=XAI_MODELS,
677
+ value="grok-2-latest",
678
+ interactive=True
679
+ )
680
+
681
+ # Repository Analysis Tab
682
+ with gr.Tab("πŸ“Š Analisis Repository"):
683
+ with gr.Group():
684
+ # Repository URL input
685
+ with gr.Row():
686
+ repo_url = gr.Textbox(
687
+ label="URL Repository GitHub",
688
+ placeholder="https://github.com/username/repository",
689
+ elem_classes="mobile-full"
690
+ )
691
+
692
+ # GitHub Token section
693
+ with gr.Row():
694
+ with gr.Column(scale=2):
695
+ github_token = gr.Textbox(
696
+ label="Token GitHub",
697
+ type="password",
698
+ placeholder="Klik (?) untuk panduan",
699
+ elem_classes="mobile-full"
700
+ )
701
+ gr.Markdown("""
702
  <details>
703
  <summary>❓ Cara Mendapatkan GitHub Token</summary>
704
  <div class="help-content">
 
718
  </div>
719
  </details>
720
  """)
721
+ with gr.Column(scale=1):
722
+ branch = gr.Textbox(
723
+ label="Branch (opsional)",
724
+ placeholder="main",
725
+ elem_classes="mobile-full"
726
+ )
727
+
728
+ # Clone Button and Status
729
+ clone_button = gr.Button(
730
+ "πŸ”„ Clone Repository",
731
+ variant="primary",
732
+ elem_classes="mobile-full"
733
+ )
734
+
735
+ clone_status = gr.Markdown(
736
+ value="",
737
+ label="Status Repository",
738
+ elem_classes="mobile-full"
739
+ )
740
+
741
+ # File Selection
742
+ with gr.Group():
743
+ gr.Markdown("### πŸ“Ž File yang Dipilih")
744
+ with gr.Row():
745
+ file_selector = gr.Dropdown(
746
+ label="Pilih File dari Repository",
747
+ choices=[],
748
+ multiselect=True,
749
+ value=[],
750
+ allow_custom_value=True,
751
+ max_choices=None,
752
+ elem_classes="mobile-full"
753
+ )
754
+
755
+ file_list = gr.HTML(
756
+ value="<div class='file-list'>Belum ada file yang dipilih</div>",
757
+ label="Daftar File Terpilih"
758
+ )
759
+
760
+ # Examples Tab
761
+ with gr.Tab("πŸ’‘ Examples"):
762
+ gr.Markdown("""
763
+ <style>
764
+ /* Styling untuk collapse */
765
+ .examples-container {
766
+ padding: 20px;
767
+ background: #fff;
768
+ border-radius: 8px;
769
+ }
770
+
771
+ .category-collapse {
772
+ margin-bottom: 15px;
773
+ border: 1px solid #e9ecef;
774
+ border-radius: 8px;
775
+ overflow: hidden;
776
+ }
777
+
778
+ .category-header {
779
+ padding: 15px;
780
+ background: #f8f9fa;
781
+ cursor: pointer;
782
+ user-select: none;
783
+ display: flex;
784
+ align-items: center;
785
+ justify-content: space-between;
786
+ transition: background-color 0.2s;
787
+ }
788
+
789
+ .category-header:hover {
790
+ background: #e9ecef;
791
+ }
792
+
793
+ .category-content {
794
+ padding: 15px;
795
+ display: none;
796
+ border-top: 1px solid #e9ecef;
797
+ }
798
+
799
+ .category-collapse.active .category-content {
800
+ display: block;
801
+ animation: slideDown 0.3s ease-out;
802
+ }
803
+
804
+ .category-header::after {
805
+ content: 'β–Ό';
806
+ font-size: 12px;
807
+ transition: transform 0.3s;
808
+ }
809
+
810
+ .category-collapse.active .category-header::after {
811
+ transform: rotate(180deg);
812
+ }
813
+
814
+ .example-button {
815
+ display: block;
816
+ width: 100%;
817
+ padding: 10px;
818
+ margin: 5px 0;
819
+ text-align: left;
820
+ background: #fff;
821
+ border: 1px solid #e9ecef;
822
+ border-radius: 4px;
823
+ cursor: pointer;
824
+ transition: all 0.2s;
825
+ }
826
+
827
+ .example-button:hover {
828
+ background: #f8f9fa;
829
+ border-color: #dee2e6;
830
+ }
831
+
832
+ @keyframes slideDown {
833
+ from {
834
+ opacity: 0;
835
+ transform: translateY(-10px);
836
+ }
837
+ to {
838
+ opacity: 1;
839
+ transform: translateY(0);
840
+ }
841
+ }
842
+ </style>
843
+
844
+ <div class="examples-container">
845
+ <h3>🎯 Contoh Pertanyaan</h3>
846
  <p>Klik pada kategori untuk melihat contoh pertanyaan yang bisa diajukan</p>
847
+
848
+ <div class="category-collapse">
849
+ <div class="category-header">
850
+ πŸ” Analisis Kode
851
+ </div>
852
+ <div class="category-content">
853
+ <button class="example-button" onclick="setQuestion('Jelaskan logika dan alur dari kode ini')">
854
+ Jelaskan logika dari kode
855
+ </button>
856
+ <button class="example-button" onclick="setQuestion('Bagaimana cara mengoptimalkan performa kode ini?')">
857
+ Optimasi performa
858
+ </button>
859
+ <button class="example-button" onclick="setQuestion('Apakah ada potential bugs yang perlu diperbaiki?')">
860
+ Cek potential bugs
861
+ </button>
862
+ </div>
863
+ </div>
864
+
865
+ <div class="category-collapse">
866
+ <div class="category-header">
867
+ πŸ“š Best Practices
868
+ </div>
869
+ <div class="category-content">
870
+ <button class="example-button" onclick="setQuestion('Apa saja best practices yang bisa diterapkan di kode ini?')">
871
+ Saran best practices
872
+ </button>
873
+ <button class="example-button" onclick="setQuestion('Bagaimana cara meningkatkan maintainability kode ini?')">
874
+ Improve maintainability
875
+ </button>
876
+ <button class="example-button" onclick="setQuestion('Berikan saran untuk meningkatkan code quality')">
877
+ Improve code quality
878
+ </button>
879
+ </div>
880
+ </div>
881
+
882
+ <div class="category-collapse">
883
+ <div class="category-header">
884
+ πŸ§ͺ Testing
885
+ </div>
886
+ <div class="category-content">
887
+ <button class="example-button" onclick="setQuestion('Buatkan unit test untuk fungsi ini')">
888
+ Generate unit tests
889
+ </button>
890
+ <button class="example-button" onclick="setQuestion('Bagaimana cara test error handling yang baik?')">
891
+ Test error handling
892
+ </button>
893
+ <button class="example-button" onclick="setQuestion('Berikan saran untuk meningkatkan test coverage')">
894
+ Improve test coverage
895
+ </button>
896
+ </div>
897
  </div>
898
+
899
+ <div class="category-collapse">
900
+ <div class="category-header">
901
+ πŸ“ Dokumentasi
902
+ </div>
903
+ <div class="category-content">
904
+ <button class="example-button" onclick="setQuestion('Buatkan dokumentasi untuk fungsi-fungsi utama')">
905
+ Generate documentation
906
+ </button>
907
+ <button class="example-button" onclick="setQuestion('Buat README.md untuk project ini')">
908
+ Create README.md
909
+ </button>
910
+ <button class="example-button" onclick="setQuestion('Jelaskan cara penggunaan library/framework')">
911
+ Usage guide
912
+ </button>
913
+ </div>
914
+ </div>
915
+ </div>
916
+
917
+ <script>
918
+ document.addEventListener('DOMContentLoaded', function() {
919
+ // Handle collapse functionality
920
+ const categories = document.querySelectorAll('.category-collapse');
921
+
922
+ categories.forEach(category => {
923
+ const header = category.querySelector('.category-header');
924
+
925
+ header.addEventListener('click', () => {
926
+ const isActive = category.classList.contains('active');
927
+
928
+ // Close all categories
929
+ categories.forEach(c => c.classList.remove('active'));
930
+
931
+ // If clicked category wasn't active, open it
932
+ if (!isActive) {
933
+ category.classList.add('active');
934
+ }
935
+ });
936
+ });
937
+ });
938
+
939
+ // Function to set question in chat input
940
+ function setQuestion(text) {
941
+ const chatInput = document.querySelector('textarea[data-testid="textbox"]');
942
+ if (chatInput) {
943
+ chatInput.value = text;
944
+ chatInput.focus();
945
+ }
946
+ }
947
+ </script>
948
+ """)
949
+
950
+ # Chat Interface (outside tabs)
951
+ # with gr.Group():
952
+ # chat_history = gr.Chatbot(
953
+ # label="πŸ“ Riwayat Chat",
954
+ # height=500,
955
+ # show_label=True,
956
+ # type="messages",
957
+ # elem_classes="mobile-full"
958
+ # )
959
+
960
+ # with gr.Row():
961
+ # chat_input = gr.Textbox(
962
+ # label="πŸ’­ Tanyakan tentang Repository",
963
+ # placeholder="Ketik pertanyaan Anda di sini...",
964
+ # lines=3,
965
+ # elem_classes="mobile-full"
966
+ # )
967
+ # send_button = gr.Button("πŸ“€ Kirim", variant="primary")
968
+ # clear_button = gr.Button("🧹 Bersihkan", variant="secondary")
969
+
970
+ # Event Handlers
971
+ def handle_clone(repo_url, github_token, branch):
972
+ if not repo_url:
973
+ return (
974
+ "⚠️ URL repository diperlukan!",
975
+ gr.Dropdown(choices=[]),
976
+ "<div class='file-list'>Belum ada file yang dipilih</div>"
977
+ )
978
+
979
+ success, message = analyzer.clone_repository(repo_url, github_token, branch)
980
+
981
+ if success:
982
+ files = sorted(list(analyzer.repo_content.keys()))
983
+ return (
984
+ message,
985
+ gr.Dropdown(choices=files, value=[]),
986
+ "<div class='file-list'>Belum ada file yang dipilih</div>"
987
+ )
988
+
989
+ return (
990
+ message,
991
+ gr.Dropdown(choices=[]),
992
+ "<div class='file-list'>Belum ada file yang dipilih</div>"
993
+ )
994
+
995
+ def update_file_list(selected):
996
+ if not selected:
997
+ return "<div class='file-list'>Belum ada file yang dipilih</div>"
998
+
999
+ html = "<div class='file-list'>"
1000
+ for file in selected:
1001
+ html += f"<div class='file-item'><span>{file}</span></div>"
1002
+ html += "</div>"
1003
+ return html
1004
+
1005
+ def clear_chat_history():
1006
+ return []
1007
+
1008
+ def update_model_list(provider_choice):
1009
+ if provider_choice == AIProvider.XAI:
1010
+ return gr.Dropdown(choices=XAI_MODELS, value="grok-2-latest")
1011
+ elif provider_choice == AIProvider.GEMINI:
1012
+ return gr.Dropdown(choices=GEMINI_MODELS, value="gemini-1.5-mini")
1013
+ else: # OLLAMA
1014
+ return gr.Dropdown(choices=OLLAMA_MODELS, value="llama2")
1015
+
1016
+ # Connect Events
1017
+ provider.change(
1018
+ fn=update_model_list,
1019
+ inputs=[provider],
1020
+ outputs=[model_dropdown]
1021
+ )
1022
+
1023
+ clone_button.click(
1024
+ fn=handle_clone,
1025
+ inputs=[repo_url, github_token, branch],
1026
+ outputs=[clone_status, file_selector, file_list]
1027
+ )
1028
+
1029
+ file_selector.change(
1030
+ fn=update_file_list,
1031
+ inputs=[file_selector],
1032
+ outputs=[file_list]
1033
+ )
1034
+
1035
+ # Chat events
1036
+ clear_button.click(
1037
+ fn=clear_chat_history,
1038
+ outputs=[chat_history]
1039
+ )
1040
+
1041
+ send_button.click(
1042
+ fn=handle_chat,
1043
+ inputs=[
1044
+ chat_input,
1045
+ chat_history,
1046
+ provider,
1047
+ model_dropdown,
1048
+ xai_key,
1049
+ gemini_key,
1050
+ file_selector
1051
+ ],
1052
+ outputs=chat_history,
1053
+ show_progress=True
1054
+ ).then(
1055
+ fn=lambda: gr.update(value=""),
1056
+ outputs=chat_input
1057
+ )
1058
+
1059
+ chat_input.submit(
1060
+ fn=handle_chat,
1061
+ inputs=[
1062
+ chat_input,
1063
+ chat_history,
1064
+ provider,
1065
+ model_dropdown,
1066
+ xai_key,
1067
+ gemini_key,
1068
+ file_selector
1069
+ ],
1070
+ outputs=chat_history,
1071
+ show_progress=True
1072
+ ).then(
1073
+ fn=lambda: gr.update(value=""),
1074
+ outputs=chat_input
1075
+ )
1076
+
1077
+ return app
1078
+
1079
+
1080
+ if __name__ == "__main__":
1081
+ print("""
1082
  πŸš€ Starting Repository Chat Analysis
1083
  ====================================
1084
  """)
1085
 
1086
+ app = create_ui()
1087
+ app.launch(share=True, server_name="0.0.0.0", server_port=7860, debug=True)