sofzcc commited on
Commit
99f8cd1
Β·
verified Β·
1 Parent(s): 49823a5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +179 -18
app.py CHANGED
@@ -587,19 +587,123 @@ def chat_respond(message: str, history: list) -> str:
587
 
588
 
589
  # -----------------------------
590
- # GRADIO UI
591
  # -----------------------------
592
 
593
- description = f"""
594
- πŸš€ **{CONFIG['client']['name']}**
595
 
596
- Ask questions and get instant answers from the knowledge base using semantic search.
597
 
598
- **Tips for better results:**
599
- - Be specific in your questions
600
- - Use keywords related to your topic
601
- - Ask one question at a time
602
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
 
604
  examples = [
605
  q["query"]
@@ -611,13 +715,70 @@ examples = [
611
  "Why is self-service important for customer support?",
612
  ]
613
 
614
- chat_interface = gr.ChatInterface(
615
- fn=chat_respond,
616
- title=f"πŸ€– {CONFIG['client']['name']}",
617
- description=description,
618
- examples=examples,
619
- cache_examples=False,
620
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
 
622
  # -----------------------------
623
  # LAUNCH
@@ -630,7 +791,7 @@ if __name__ == "__main__":
630
  if is_huggingface or is_container:
631
  env_label = "πŸ€— HuggingFace Spaces" if is_huggingface else "🐳 container"
632
  logger.info(f"Launching on {env_label}...")
633
- chat_interface.launch(server_name="0.0.0.0", server_port=7860)
634
  else:
635
  logger.info("πŸ’» Launching locally...")
636
- chat_interface.launch(share=False)
 
587
 
588
 
589
  # -----------------------------
590
+ # KB FILE MANAGEMENT
591
  # -----------------------------
592
 
593
+ ALLOWED_EXTENSIONS = {".txt", ".md"}
594
+ MAX_FILE_SIZE_MB = 10
595
 
 
596
 
597
+ def list_kb_files() -> str:
598
+ """Return a formatted markdown table of current KB files."""
599
+ paths = sorted(
600
+ glob.glob(os.path.join(KB_DIR, "*.txt")) +
601
+ glob.glob(os.path.join(KB_DIR, "*.md"))
602
+ )
603
+ if not paths:
604
+ return "_No files in knowledge base yet. Upload some documents to get started._"
605
+
606
+ rows = ["| File | Size |", "|------|------|"]
607
+ for p in paths:
608
+ size_kb = os.path.getsize(p) / 1024
609
+ rows.append(f"| {os.path.basename(p)} | {size_kb:.1f} KB |")
610
+ return "\n".join(rows)
611
+
612
+
613
+ def upload_kb_files(files) -> Tuple[str, str]:
614
+ """
615
+ Accept uploaded files, validate them, save to KB_DIR, and rebuild the index.
616
+ Returns (status_message, updated_file_list).
617
+ """
618
+ if not files:
619
+ return "⚠️ No files selected.", list_kb_files()
620
+
621
+ os.makedirs(KB_DIR, exist_ok=True)
622
+
623
+ saved = []
624
+ skipped = []
625
+
626
+ for file in files:
627
+ # Gradio passes either a filepath string or a NamedString object
628
+ filepath = file if isinstance(file, str) else file.name
629
+ original_name = os.path.basename(filepath)
630
+ ext = os.path.splitext(original_name)[1].lower()
631
+
632
+ if ext not in ALLOWED_EXTENSIONS:
633
+ skipped.append(f"{original_name} (unsupported type β€” use .txt or .md)")
634
+ continue
635
+
636
+ size_mb = os.path.getsize(filepath) / (1024 * 1024)
637
+ if size_mb > MAX_FILE_SIZE_MB:
638
+ skipped.append(f"{original_name} (too large β€” max {MAX_FILE_SIZE_MB} MB)")
639
+ continue
640
+
641
+ dest = os.path.join(KB_DIR, original_name)
642
+ try:
643
+ with open(filepath, "r", encoding="utf-8", errors="replace") as f:
644
+ content = f.read()
645
+ if not content.strip():
646
+ skipped.append(f"{original_name} (empty file)")
647
+ continue
648
+ with open(dest, "w", encoding="utf-8") as f:
649
+ f.write(content)
650
+ saved.append(original_name)
651
+ logger.info(f"KB file saved: {dest}")
652
+ except Exception as e:
653
+ skipped.append(f"{original_name} (error: {e})")
654
+ logger.error(f"Failed to save {original_name}: {e}")
655
+
656
+ if saved:
657
+ logger.info("Rebuilding KB index after upload...")
658
+ try:
659
+ kb_index.reload()
660
+ reload_msg = "βœ… Index rebuilt successfully."
661
+ except Exception as e:
662
+ reload_msg = f"⚠️ Index rebuild failed: {e}"
663
+ logger.error(reload_msg)
664
+ else:
665
+ reload_msg = ""
666
+
667
+ status_parts = []
668
+ if saved:
669
+ status_parts.append(f"βœ… **Uploaded:** {', '.join(saved)}")
670
+ if skipped:
671
+ status_parts.append(f"⚠️ **Skipped:** {', '.join(skipped)}")
672
+ if reload_msg:
673
+ status_parts.append(reload_msg)
674
+
675
+ return "\n\n".join(status_parts), list_kb_files()
676
+
677
+
678
+ def delete_kb_file(filename: str) -> Tuple[str, str]:
679
+ """Delete a file from the KB directory and rebuild the index."""
680
+ filename = filename.strip()
681
+ if not filename:
682
+ return "⚠️ No filename provided.", list_kb_files()
683
+
684
+ # Safety: strip any path components β€” only allow plain filenames
685
+ filename = os.path.basename(filename)
686
+ ext = os.path.splitext(filename)[1].lower()
687
+ if ext not in ALLOWED_EXTENSIONS:
688
+ return f"⚠️ Invalid file type: {filename}", list_kb_files()
689
+
690
+ target = os.path.join(KB_DIR, filename)
691
+ if not os.path.exists(target):
692
+ return f"⚠️ File not found: {filename}", list_kb_files()
693
+
694
+ try:
695
+ os.remove(target)
696
+ logger.info(f"Deleted KB file: {target}")
697
+ kb_index.reload()
698
+ return f"πŸ—‘οΈ **Deleted** `{filename}` and rebuilt index.", list_kb_files()
699
+ except Exception as e:
700
+ logger.error(f"Failed to delete {filename}: {e}")
701
+ return f"❌ Could not delete `{filename}`: {e}", list_kb_files()
702
+
703
+
704
+ # -----------------------------
705
+ # GRADIO UI
706
+ # -----------------------------
707
 
708
  examples = [
709
  q["query"]
 
715
  "Why is self-service important for customer support?",
716
  ]
717
 
718
+ with gr.Blocks(title=f"πŸ€– {CONFIG['client']['name']}") as demo:
719
+ gr.Markdown(f"# πŸ€– {CONFIG['client']['name']}")
720
+
721
+ with gr.Tabs():
722
+
723
+ # ── Tab 1: Chat ──────────────────────────────────────────────
724
+ with gr.TabItem("πŸ’¬ Chat"):
725
+ gr.Markdown("""
726
+ Ask questions and get instant answers from the knowledge base using semantic search.
727
+
728
+ **Tips:** Be specific Β· Use keywords Β· Ask one question at a time
729
+ """)
730
+ gr.ChatInterface(
731
+ fn=chat_respond,
732
+ examples=examples,
733
+ cache_examples=False,
734
+ )
735
+
736
+ # ── Tab 2: KB Manager ────────────────────────────────────────
737
+ with gr.TabItem("πŸ“ KB Manager"):
738
+ gr.Markdown("""
739
+ ### Manage Knowledge Base Documents
740
+
741
+ Upload `.txt` or `.md` files to expand what the assistant can answer.
742
+ The index is rebuilt automatically after every upload or deletion.
743
+ """)
744
+
745
+ with gr.Row():
746
+ with gr.Column(scale=2):
747
+ upload_input = gr.File(
748
+ label="Upload documents (.txt or .md, max 10 MB each)",
749
+ file_count="multiple",
750
+ file_types=[".txt", ".md"],
751
+ )
752
+ upload_btn = gr.Button("⬆️ Upload & Index", variant="primary")
753
+ upload_status = gr.Markdown(label="Upload Status")
754
+
755
+ with gr.Column(scale=1):
756
+ gr.Markdown("#### Current KB Files")
757
+ kb_file_list = gr.Markdown(value=list_kb_files)
758
+
759
+ gr.Markdown("#### Delete a File")
760
+ delete_input = gr.Textbox(
761
+ placeholder="exact filename e.g. my_doc.md",
762
+ label="Filename to delete",
763
+ show_label=False,
764
+ )
765
+ delete_btn = gr.Button("πŸ—‘οΈ Delete", variant="stop")
766
+ delete_status = gr.Markdown(label="Delete Status")
767
+
768
+ # Wire up upload
769
+ upload_btn.click(
770
+ fn=upload_kb_files,
771
+ inputs=[upload_input],
772
+ outputs=[upload_status, kb_file_list],
773
+ )
774
+
775
+ # Wire up delete
776
+ delete_btn.click(
777
+ fn=delete_kb_file,
778
+ inputs=[delete_input],
779
+ outputs=[delete_status, kb_file_list],
780
+ )
781
+
782
 
783
  # -----------------------------
784
  # LAUNCH
 
791
  if is_huggingface or is_container:
792
  env_label = "πŸ€— HuggingFace Spaces" if is_huggingface else "🐳 container"
793
  logger.info(f"Launching on {env_label}...")
794
+ demo.launch(server_name="0.0.0.0", server_port=7860)
795
  else:
796
  logger.info("πŸ’» Launching locally...")
797
+ demo.launch(share=False)