ErNewdev0 commited on
Commit
cedc4db
·
verified ·
1 Parent(s): 5a92fe1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +363 -284
app.py CHANGED
@@ -278,315 +278,394 @@ class RepoAnalyzer:
278
  continue
279
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
280
 
281
- def create_ui():
282
- # Get local time
283
- local_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
284
- analyzer = RepoAnalyzer()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
- with gr.Blocks(title="Repository Chat Analysis", theme=gr.themes.Soft()) as app:
287
- gr.Markdown("""
288
- <style>
289
- .container { max-width: 100% !important; padding: 1rem; }
290
- .mobile-full { width: 100% !important; }
291
- .file-list { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
292
- .file-item { display: flex; justify-content: space-between; padding: 5px 0; }
293
- .file-remove { color: red; cursor: pointer; }
294
 
295
- /* Enhanced code block styling */
296
- .wrapper-artifact {
297
- border: 1px solid #e0e0e0;
298
- border-radius: 8px;
299
- margin: 16px 0;
300
- background: #f8f9fa;
301
- }
302
- .header-artifact {
303
- padding: 8px 16px;
304
- background: #f1f3f4;
305
- border-bottom: 1px solid #e0e0e0;
306
- border-radius: 8px 8px 0 0;
307
- font-family: monospace;
308
- display: flex;
309
- justify-content: space-between;
310
- align-items: center;
311
- }
312
- .content-artifact {
313
- padding: 16px;
314
- overflow-x: auto;
315
- background: #ffffff;
316
- border-radius: 0 0 8px 8px;
317
- }
318
- .content-artifact pre {
319
- margin: 0;
320
- padding: 0;
321
- }
322
- .copy-button {
323
- background: #e0e0e0;
324
- border: none;
325
- padding: 4px 8px;
326
- border-radius: 4px;
327
- cursor: pointer;
328
- font-size: 12px;
329
- }
330
- .copy-button:hover {
331
- background: #d0d0d0;
332
- }
333
 
334
- /* Chat interface styling */
335
- .fullscreen-chat {
336
- height: calc(100vh - 200px) !important;
337
- margin: 20px 0;
338
- }
339
- .chat-input {
340
- position: sticky;
341
- bottom: 0;
342
- background: white;
343
- padding: 10px 0;
344
- border-top: 1px solid #eee;
345
- }
346
- .chat-message {
347
- margin: 8px 0;
348
- }
349
- @media (max-width: 768px) {
350
- .gr-form { flex-direction: column !important; }
351
- .gr-group { margin: 0.5rem 0 !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  .fullscreen-chat {
353
- height: calc(100vh - 300px) !important;
 
354
  }
355
- }
356
- </style>
357
- <script>
358
- function copyToClipboard(text) {
359
- navigator.clipboard.writeText(text).then(() => {
360
- const button = event.target;
361
- button.textContent = 'Copied!';
362
- setTimeout(() => {
363
- button.textContent = 'Copy';
364
- }, 2000);
365
- });
366
- }
367
- </script>
368
- """)
369
-
370
- with gr.Row(elem_classes="container"):
371
- gr.Markdown(f"# 🤖 Repository Chat Analysis\n\n📅 {local_time}")
372
-
373
- with gr.Tabs() as tabs:
374
- with gr.Tab("🛠️ Konfigurasi", elem_classes="mobile-full"):
375
- provider = gr.Radio(
376
- choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
377
- label="Penyedia AI",
378
- value=AIProvider.XAI,
379
- interactive=True,
380
- elem_classes="mobile-full"
381
- )
382
-
383
- with gr.Group() as api_settings:
384
- with gr.Group():
385
- with gr.Row():
386
- xai_key = gr.Textbox(
387
- label="X.AI (Grok) API Key",
388
- type="password",
389
- placeholder="Opsional - Klik icon (?) untuk info",
390
- show_label=True,
391
- scale=3
392
- )
393
- with gr.Column(scale=1):
394
- gr.Markdown(XAI_API_HELP)
395
-
396
- with gr.Group():
397
- with gr.Row():
398
- gemini_key = gr.Textbox(
399
- label="Gemini API Key",
400
- type="password",
401
- placeholder="Opsional - Kosongkan untuk gunakan key default",
402
- show_label=True,
403
- scale=3
404
- )
405
- with gr.Column(scale=1):
406
- gr.Markdown(GEMINI_API_HELP)
407
-
408
- # Model selection with better layout
409
- with gr.Row():
410
- model_dropdown = gr.Dropdown(
411
- label="Model AI",
412
- choices=XAI_MODELS,
413
- value="grok-2-latest",
414
  interactive=True,
415
  elem_classes="mobile-full"
416
  )
417
-
418
- with gr.Tab("📊 Analisis Repository", elem_classes="mobile-full"):
419
- with gr.Group():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  with gr.Row():
421
- repo_url = gr.Textbox(
422
- label="URL Repository GitHub",
423
- placeholder="https://github.com/username/repository",
 
 
424
  elem_classes="mobile-full"
425
  )
426
-
427
- with gr.Row():
428
- with gr.Column(scale=2):
429
- github_token = gr.Textbox(
430
- label="Token GitHub",
431
- type="password",
432
- placeholder="Klik icon (?) untuk panduan",
433
  elem_classes="mobile-full"
434
  )
435
- with gr.Column(scale=1):
436
- branch = gr.Textbox(
437
- label="Branch (opsional)",
438
- placeholder="main",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  elem_classes="mobile-full"
440
  )
441
-
442
- clone_button = gr.Button(
443
- "🔄 Clone Repository",
444
- variant="primary",
445
- elem_classes="mobile-full"
 
 
 
 
 
 
 
 
 
446
  )
447
 
448
- clone_status = gr.Markdown(
449
- value="",
450
- label="Status Repository",
451
- elem_classes="mobile-full"
452
- )
453
-
454
- # File selection components
455
- with gr.Group():
456
- gr.Markdown("### 📎 File yang Dipilih")
457
-
458
- with gr.Row():
459
- file_selector = gr.Dropdown(
460
- label="Pilih File dari Repository",
461
- choices=[],
462
- multiselect=True,
463
- elem_classes="mobile-full"
464
- )
465
-
466
- file_list = gr.HTML(
467
- value="<div class='file-list'>Belum ada file yang dipilih</div>",
468
- label="Daftar File Terpilih"
469
  )
 
 
 
470
 
471
- # Enhanced chat interface with fullscreen support
472
- with gr.Group():
473
- chat_history = gr.Chatbot(
474
- label="📝 Riwayat Chat",
475
- elem_classes="fullscreen-chat",
476
- height=None, # Let CSS handle the height
477
- show_label=True
478
- )
479
-
480
- with gr.Group(elem_classes="chat-input"):
481
- chat_input = gr.Textbox(
482
- label="💭 Tanyakan tentang Repository",
483
- placeholder="Ketik pertanyaan Anda di sini...",
484
- lines=3,
485
- elem_classes="mobile-full"
486
- )
487
- with gr.Row():
488
- clear_button = gr.Button("🧹 Bersihkan", variant="secondary")
489
- send_button = gr.Button("📤 Kirim", variant="primary")
490
 
491
- loading_indicator = gr.HTML(
492
- '<div id="loading" style="display:none">Memproses permintaan...</div>'
493
- )
 
494
 
495
- async def handle_chat(message, history, provider_choice, model_name, xai_key, gemini_key, selected_files):
496
- if not analyzer.current_repo:
497
- yield history + [[message, "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan."]]
498
- return
499
-
500
- history = history or []
501
- history.append([message, ""])
502
-
503
- try:
504
- # Add context about selected files to the prompt
505
- file_context = ""
506
- if selected_files:
507
- file_context = "\n\nFile yang dipilih:\n"
508
- for file in selected_files:
509
- content = analyzer.repo_content.get(file, "")
510
- escaped_content = content.replace('`', r'\`')
511
-
512
- html = (
513
- '<div class="wrapper-artifact">'
514
- f'<div class="header-artifact">'
515
- f'<span>{file}</span>'
516
- f'<button class="copy-button" onclick="copyToClipboard(`{escaped_content}`)">Copy</button>'
517
- '</div>'
518
- '<div class="content-artifact">'
519
- f'<pre><code>{content}</code></pre>'
520
- '</div>'
521
- '</div>'
522
- )
523
- file_context += html
524
-
525
- enhanced_message = f"{message}\n{file_context}"
526
-
527
- full_response = ""
528
- if provider_choice == AIProvider.XAI:
529
- async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
530
- # Wrap code blocks in custom styling
531
- chunk = process_code_blocks(chunk)
532
- full_response += chunk
533
- await asyncio.sleep(0.08)
534
- history[-1][1] = full_response
535
- yield history
536
-
537
- elif provider_choice == AIProvider.GEMINI:
538
- async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
539
- chunk = process_code_blocks(chunk)
540
- full_response += chunk
541
- await asyncio.sleep(0.08)
542
- history[-1][1] = full_response
543
- yield history
544
-
545
- else: # OLLAMA
546
- response = analyze_with_ollama(model_name, enhanced_message)
547
- response = process_code_blocks(response)
548
- words = response.split()
549
- for i in range(len(words)):
550
- full_response = " ".join(words[:i+1])
551
- await asyncio.sleep(0.08)
552
- history[-1][1] = full_response
553
- yield history
554
-
555
- except Exception as e:
556
- history[-1][1] = f"⚠️ Error: {str(e)}"
557
- yield history
558
 
559
- def process_code_blocks(text):
560
- """Process markdown code blocks to use custom artifact styling"""
561
- import re
562
-
563
- # Pattern for code blocks with language specification
564
- pattern = r"```(\w+)\n(.*?)```"
565
-
566
- def replace_code_block(match):
567
- language = match.group(1)
568
- code = match.group(2)
569
- escaped_code = code.replace('`', r'\`')
570
-
571
- html = (
572
- '<div class="wrapper-artifact">'
573
- f'<div class="header-artifact">'
574
- f'<span>{language}</span>'
575
- f'<button class="copy-button" onclick="copyToClipboard(`{escaped_code}`)">Copy</button>'
576
- '</div>'
577
- '<div class="content-artifact">'
578
- f'<pre><code>{code}</code></pre>'
579
- '</div>'
580
- '</div>'
581
  )
582
- return html
583
-
584
- # Replace all code blocks in the text
585
- processed_text = re.sub(pattern, replace_code_block, text, flags=re.DOTALL)
586
- return processed_text
587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
 
589
- return app
590
 
591
  if __name__ == "__main__":
592
  print(f"""
 
278
  continue
279
  return False, "Tidak dapat membaca file dengan encoding yang didukung"
280
 
281
+ async def handle_chat(message, history, provider_choice, model_name, xai_key, gemini_key, selected_files):
282
+ if not analyzer.current_repo:
283
+ yield history + [[message, "⚠️ Mohon clone repository terlebih dahulu sebelum mengajukan pertanyaan."]]
284
+ return
285
+
286
+ history = history or []
287
+ history.append([message, ""])
288
+
289
+ try:
290
+ # Add context about selected files to the prompt
291
+ file_context = ""
292
+ if selected_files:
293
+ file_context = "\n\nFile yang dipilih:\n"
294
+ for file in selected_files:
295
+ content = analyzer.repo_content.get(file, "")
296
+ escaped_content = content.replace('`', r'\`')
297
+
298
+ html = (
299
+ '<div class="wrapper-artifact">'
300
+ f'<div class="header-artifact">'
301
+ f'<span>{file}</span>'
302
+ f'<button class="copy-button" onclick="copyToClipboard(`{escaped_content}`)">Copy</button>'
303
+ '</div>'
304
+ '<div class="content-artifact">'
305
+ f'<pre><code>{content}</code></pre>'
306
+ '</div>'
307
+ '</div>'
308
+ )
309
+ file_context += html
310
+
311
+ enhanced_message = f"{message}\n{file_context}"
312
+
313
+ full_response = ""
314
+ if provider_choice == AIProvider.XAI:
315
+ async for chunk in analyzer.stream_xai_response(enhanced_message, xai_key, model_name):
316
+ # Wrap code blocks in custom styling
317
+ chunk = process_code_blocks(chunk)
318
+ full_response += chunk
319
+ await asyncio.sleep(0.08)
320
+ history[-1][1] = full_response
321
+ yield history
322
+
323
+ elif provider_choice == AIProvider.GEMINI:
324
+ async for chunk in analyzer.stream_gemini_response(enhanced_message, gemini_key or DEFAULT_GEMINI_KEY):
325
+ chunk = process_code_blocks(chunk)
326
+ full_response += chunk
327
+ await asyncio.sleep(0.08)
328
+ history[-1][1] = full_response
329
+ yield history
330
+
331
+ else: # OLLAMA
332
+ response = analyze_with_ollama(model_name, enhanced_message)
333
+ response = process_code_blocks(response)
334
+ words = response.split()
335
+ for i in range(len(words)):
336
+ full_response = " ".join(words[:i+1])
337
+ await asyncio.sleep(0.08)
338
+ history[-1][1] = full_response
339
+ yield history
340
+
341
+ except Exception as e:
342
+ history[-1][1] = f"⚠️ Error: {str(e)}"
343
+ yield history
344
 
345
+ def process_code_blocks(text):
346
+ """Process markdown code blocks to use custom artifact styling"""
347
+ import re
348
+
349
+ # Pattern for code blocks with language specification
350
+ pattern = r"```(\w+)\n(.*?)```"
 
 
351
 
352
+ def replace_code_block(match):
353
+ language = match.group(1)
354
+ code = match.group(2)
355
+ escaped_code = code.replace('`', r'\`')
356
+
357
+ html = (
358
+ '<div class="wrapper-artifact">'
359
+ f'<div class="header-artifact">'
360
+ f'<span>{language}</span>'
361
+ f'<button class="copy-button" onclick="copyToClipboard(`{escaped_code}`)">Copy</button>'
362
+ '</div>'
363
+ '<div class="content-artifact">'
364
+ f'<pre><code>{code}</code></pre>'
365
+ '</div>'
366
+ '</div>'
367
+ )
368
+ return html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
+ # Replace all code blocks in the text
371
+ processed_text = re.sub(pattern, replace_code_block, text, flags=re.DOTALL)
372
+ return processed_text
373
+
374
+ def create_ui():
375
+ # Get local time
376
+ local_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
377
+ analyzer = RepoAnalyzer()
378
+
379
+ with gr.Blocks(title="Repository Chat Analysis", theme=gr.themes.Soft()) as app:
380
+ gr.Markdown("""
381
+ <style>
382
+ .container { max-width: 100% !important; padding: 1rem; }
383
+ .mobile-full { width: 100% !important; }
384
+ .file-list { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
385
+ .file-item { display: flex; justify-content: space-between; padding: 5px 0; }
386
+ .file-remove { color: red; cursor: pointer; }
387
+
388
+ /* Enhanced code block styling */
389
+ .wrapper-artifact {
390
+ border: 1px solid #e0e0e0;
391
+ border-radius: 8px;
392
+ margin: 16px 0;
393
+ background: #f8f9fa;
394
+ }
395
+ .header-artifact {
396
+ padding: 8px 16px;
397
+ background: #f1f3f4;
398
+ border-bottom: 1px solid #e0e0e0;
399
+ border-radius: 8px 8px 0 0;
400
+ font-family: monospace;
401
+ display: flex;
402
+ justify-content: space-between;
403
+ align-items: center;
404
+ }
405
+ .content-artifact {
406
+ padding: 16px;
407
+ overflow-x: auto;
408
+ background: #ffffff;
409
+ border-radius: 0 0 8px 8px;
410
+ }
411
+ .content-artifact pre {
412
+ margin: 0;
413
+ padding: 0;
414
+ }
415
+ .copy-button {
416
+ background: #e0e0e0;
417
+ border: none;
418
+ padding: 4px 8px;
419
+ border-radius: 4px;
420
+ cursor: pointer;
421
+ font-size: 12px;
422
+ }
423
+ .copy-button:hover {
424
+ background: #d0d0d0;
425
+ }
426
+
427
+ /* Chat interface styling */
428
  .fullscreen-chat {
429
+ height: calc(100vh - 200px) !important;
430
+ margin: 20px 0;
431
  }
432
+ .chat-input {
433
+ position: sticky;
434
+ bottom: 0;
435
+ background: white;
436
+ padding: 10px 0;
437
+ border-top: 1px solid #eee;
438
+ }
439
+ .chat-message {
440
+ margin: 8px 0;
441
+ }
442
+ @media (max-width: 768px) {
443
+ .gr-form { flex-direction: column !important; }
444
+ .gr-group { margin: 0.5rem 0 !important; }
445
+ .fullscreen-chat {
446
+ height: calc(100vh - 300px) !important;
447
+ }
448
+ }
449
+ </style>
450
+ <script>
451
+ function copyToClipboard(text) {
452
+ navigator.clipboard.writeText(text).then(() => {
453
+ const button = event.target;
454
+ button.textContent = 'Copied!';
455
+ setTimeout(() => {
456
+ button.textContent = 'Copy';
457
+ }, 2000);
458
+ });
459
+ }
460
+ </script>
461
+ """)
462
+
463
+ with gr.Row(elem_classes="container"):
464
+ gr.Markdown(f"# 🤖 Repository Chat Analysis\n\n📅 {local_time}")
465
+
466
+ with gr.Tabs() as tabs:
467
+ with gr.Tab("🛠️ Konfigurasi", elem_classes="mobile-full"):
468
+ provider = gr.Radio(
469
+ choices=[AIProvider.XAI, AIProvider.GEMINI, AIProvider.OLLAMA],
470
+ label="Penyedia AI",
471
+ value=AIProvider.XAI,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  interactive=True,
473
  elem_classes="mobile-full"
474
  )
475
+
476
+ with gr.Group() as api_settings:
477
+ with gr.Group():
478
+ with gr.Row():
479
+ xai_key = gr.Textbox(
480
+ label="X.AI (Grok) API Key",
481
+ type="password",
482
+ placeholder="Opsional - Klik icon (?) untuk info",
483
+ show_label=True,
484
+ scale=3
485
+ )
486
+ with gr.Column(scale=1):
487
+ gr.Markdown(XAI_API_HELP)
488
+
489
+ with gr.Group():
490
+ with gr.Row():
491
+ gemini_key = gr.Textbox(
492
+ label="Gemini API Key",
493
+ type="password",
494
+ placeholder="Opsional - Kosongkan untuk gunakan key default",
495
+ show_label=True,
496
+ scale=3
497
+ )
498
+ with gr.Column(scale=1):
499
+ gr.Markdown(GEMINI_API_HELP)
500
+
501
+ # Model selection with better layout
502
  with gr.Row():
503
+ model_dropdown = gr.Dropdown(
504
+ label="Model AI",
505
+ choices=XAI_MODELS,
506
+ value="grok-2-latest",
507
+ interactive=True,
508
  elem_classes="mobile-full"
509
  )
510
+
511
+ with gr.Tab("📊 Analisis Repository", elem_classes="mobile-full"):
512
+ with gr.Group():
513
+ with gr.Row():
514
+ repo_url = gr.Textbox(
515
+ label="URL Repository GitHub",
516
+ placeholder="https://github.com/username/repository",
517
  elem_classes="mobile-full"
518
  )
519
+
520
+ with gr.Row():
521
+ with gr.Column(scale=2):
522
+ github_token = gr.Textbox(
523
+ label="Token GitHub",
524
+ type="password",
525
+ placeholder="Klik icon (?) untuk panduan",
526
+ elem_classes="mobile-full"
527
+ )
528
+ with gr.Column(scale=1):
529
+ branch = gr.Textbox(
530
+ label="Branch (opsional)",
531
+ placeholder="main",
532
+ elem_classes="mobile-full"
533
+ )
534
+
535
+ clone_button = gr.Button(
536
+ "🔄 Clone Repository",
537
+ variant="primary",
538
+ elem_classes="mobile-full"
539
+ )
540
+
541
+ clone_status = gr.Markdown(
542
+ value="",
543
+ label="Status Repository",
544
+ elem_classes="mobile-full"
545
+ )
546
+
547
+ # File selection components
548
+ with gr.Group():
549
+ gr.Markdown("### 📎 File yang Dipilih")
550
+
551
+ with gr.Row():
552
+ file_selector = gr.Dropdown(
553
+ label="Pilih File dari Repository",
554
+ choices=[],
555
+ multiselect=True,
556
  elem_classes="mobile-full"
557
  )
558
+
559
+ file_list = gr.HTML(
560
+ value="<div class='file-list'>Belum ada file yang dipilih</div>",
561
+ label="Daftar File Terpilih"
562
+ )
563
+
564
+ # Enhanced chat interface with fullscreen support
565
+ with gr.Group():
566
+ chat_history = gr.Chatbot(
567
+ label="📝 Riwayat Chat",
568
+ elem_classes="fullscreen-chat",
569
+ height=None, # Let CSS handle the height
570
+ show_label=True,
571
+ type="messages" # Fix deprecation warning
572
  )
573
 
574
+ with gr.Group(elem_classes="chat-input"):
575
+ chat_input = gr.Textbox(
576
+ label="💭 Tanyakan tentang Repository",
577
+ placeholder="Ketik pertanyaan Anda di sini...",
578
+ lines=3,
579
+ elem_classes="mobile-full"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  )
581
+ with gr.Row():
582
+ clear_button = gr.Button("🧹 Bersihkan", variant="secondary")
583
+ send_button = gr.Button("📤 Kirim", variant="primary")
584
 
585
+ # Connect event handlers
586
+ def clear_chat_history():
587
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
 
589
+ clear_button.click(
590
+ fn=clear_chat_history,
591
+ outputs=chat_history
592
+ )
593
 
594
+ def handle_clone(repo_url, github_token, branch):
595
+ if not repo_url:
596
+ return "⚠️ URL repository diperlukan!", [], []
597
+
598
+ success, message = analyzer.clone_repository(repo_url, github_token, branch)
599
+
600
+ if success:
601
+ # Get list of files from cloned repository
602
+ files = list(analyzer.repo_content.keys())
603
+ return message, files, []
604
+
605
+ return message, [], []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
 
607
+ # Connect clone button click event
608
+ clone_button.click(
609
+ fn=handle_clone,
610
+ inputs=[repo_url, github_token, branch],
611
+ outputs=[clone_status, file_selector, file_list]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  )
 
 
 
 
 
613
 
614
+ def update_file_list(selected):
615
+ if not selected:
616
+ return "<div class='file-list'>Belum ada file yang dipilih</div>"
617
+
618
+ html = "<div class='file-list'>"
619
+ for file in selected:
620
+ html += f"<div class='file-item'><span>{file}</span></div>"
621
+ html += "</div>"
622
+ return html
623
+
624
+ # Update file list when selection changes
625
+ file_selector.change(
626
+ fn=update_file_list,
627
+ inputs=[file_selector],
628
+ outputs=[file_list]
629
+ )
630
+
631
+ # Connect chat events
632
+ send_event = send_button.click(
633
+ fn=handle_chat,
634
+ inputs=[
635
+ chat_input,
636
+ chat_history,
637
+ provider,
638
+ model_dropdown,
639
+ xai_key,
640
+ gemini_key,
641
+ file_selector
642
+ ],
643
+ outputs=chat_history,
644
+ show_progress=True
645
+ ).then(
646
+ fn=lambda: gr.update(value=""),
647
+ outputs=chat_input
648
+ )
649
+
650
+ input_event = chat_input.submit(
651
+ fn=handle_chat,
652
+ inputs=[
653
+ chat_input,
654
+ chat_history,
655
+ provider,
656
+ model_dropdown,
657
+ xai_key,
658
+ gemini_key,
659
+ file_selector
660
+ ],
661
+ outputs=chat_history,
662
+ show_progress=True
663
+ ).then(
664
+ fn=lambda: gr.update(value=""),
665
+ outputs=chat_input
666
+ )
667
 
668
+ return app
669
 
670
  if __name__ == "__main__":
671
  print(f"""