# documents_tab.py """Document upload, list, delete, and selection.""" from __future__ import annotations from typing import List, Tuple import gradio as gr from core.bootstrap import get_db, get_ingestion_service from gradio_ui.state import SearchMode, footer_hint def _doc_table_rows() -> List[List[str]]: db = get_db() return [ [ doc.get("document_id", ""), doc.get("document_name", ""), str(doc.get("chunk_count", 0)), ] for doc in db.list_documents() ] def _doc_choices() -> List[Tuple[str, str]]: db = get_db() return [ (doc.get("document_name") or doc.get("document_id", ""), doc.get("document_id", "")) for doc in db.list_documents() if doc.get("document_id") ] def _choice_label(doc_id: str, name: str, chunks: str) -> str: return f"{name} · {chunks} chunks" def _choices_with_meta(rows: List[List[str]]) -> List[Tuple[str, str]]: return [ (_choice_label(doc_id, name, chunks), doc_id) for doc_id, name, chunks in rows if doc_id ] def _merge_selection( selected: List[str], doc_id: str, search_mode: SearchMode, ) -> List[str]: selected = list(selected or []) if not doc_id: return selected if search_mode == "single": return [doc_id] if doc_id not in selected: selected.append(doc_id) return selected def upload_document( file_path: str | None, selected: List[str], search_mode: SearchMode, ) -> Tuple[str, List[List[str]], gr.update, gr.update, List[str], str]: rows = _doc_table_rows() choices = _choices_with_meta(rows) if not file_path: return ( "", rows, gr.update(choices=choices, value=selected), gr.update(choices=[c[1] for c in _doc_choices()]), selected, footer_hint(search_mode, selected, len(rows), "documents"), ) with open(file_path, "rb") as handle: content = handle.read() filename = file_path.rsplit("\\", 1)[-1].rsplit("/", 1)[-1] ext = filename.lower().rsplit(".", 1)[-1] ingestion = get_ingestion_service() if ext == "pdf": result = ingestion.ingest_pdf(content, filename) elif ext in ("png", "jpg", "jpeg", "tiff", "bmp", "webp"): result = ingestion.ingest_image(content, filename) else: return ( "Unsupported file type. Use PDF or image files.", rows, gr.update(choices=choices, value=selected), gr.update(choices=[c[1] for c in _doc_choices()]), selected, footer_hint(search_mode, selected, len(rows), "documents"), ) doc_id = result.get("document_id", "") rows = _doc_table_rows() choices = _choices_with_meta(rows) new_selected = _merge_selection(selected, doc_id, search_mode) msg = f"Indexed **{filename}** ({result.get('chunks_indexed', 0)} chunks)." return ( msg, rows, gr.update(choices=choices, value=new_selected), gr.update(choices=[c[1] for c in _doc_choices()]), new_selected, footer_hint(search_mode, new_selected, len(rows), "documents"), ) def delete_document( doc_id: str, selected: List[str], search_mode: SearchMode, ) -> Tuple[str, List[List[str]], gr.update, List[str], str]: rows = _doc_table_rows() choices = _choices_with_meta(rows) if not doc_id: return ( "Select a document to delete.", rows, gr.update(choices=choices, value=selected), selected, footer_hint(search_mode, selected, len(rows), "documents"), ) get_db().delete_document(doc_id) new_selected = [d for d in (selected or []) if d != doc_id] rows = _doc_table_rows() choices = _choices_with_meta(rows) return ( "Deleted document.", rows, gr.update(choices=choices, value=new_selected), new_selected, footer_hint(search_mode, new_selected, len(rows), "documents"), ) def on_doc_selection(selected: List[str], search_mode: SearchMode) -> Tuple[List[str], str, gr.update]: if search_mode == "single" and selected and len(selected) > 1: selected = [selected[-1]] rows = _doc_table_rows() choices = _choices_with_meta(rows) return ( selected, footer_hint(search_mode, selected, len(_doc_choices())), gr.update(choices=choices, value=selected), ) def on_search_mode_change(search_mode: SearchMode, selected: List[str]) -> Tuple[List[str], str]: if search_mode == "single" and selected and len(selected) > 1: selected = [selected[0]] return selected, footer_hint(search_mode, selected, len(_doc_choices())) def doc_select_update(selected: List[str] | None = None) -> gr.update: rows = _doc_table_rows() return gr.update(choices=_choices_with_meta(rows), value=selected or []) def build_documents_panel() -> dict: with gr.Column(elem_classes=["fs-modal-card", "fs-docs-card"]): with gr.Row(elem_classes=["fs-modal-header-row"]): gr.HTML( """
Upload PDFs or images, then select which to use for RAG