Download app.py from internkk/assistant: direct link, hf CLI and curl.
- Browser
- Download file 15.9 kB
-
https://huggingface.co/spaces/internkk/assistant/resolve/main/app.py
- Command line
-
hf download hf://spaces/internkk/assistant/app.py
-
curl -L -o app.py https://huggingface.co/spaces/internkk/assistant/resolve/main/app.py
15.9 kB
| import os | |
| import tempfile | |
| import gradio as gr | |
| import fitz # PyMuPDF | |
| import pandas as pd | |
| from docx import Document | |
| from langchain_core.documents import Document as LCDocument | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| from sentence_transformers import SentenceTransformer | |
| from langchain.embeddings.base import Embeddings | |
| from langchain_chroma import Chroma | |
| from groq import Groq | |
| # ---------- Embedding Model ---------- | |
| _embedding_model = None | |
| def get_embedding_model(): | |
| global _embedding_model | |
| if _embedding_model is None: | |
| cache_dir = "/tmp/sentence_transformers" | |
| os.makedirs(cache_dir, exist_ok=True) | |
| _embedding_model = SentenceTransformer("all-MiniLM-L6-v2", cache_folder=cache_dir) | |
| return _embedding_model | |
| class BGEEmbeddings(Embeddings): | |
| def __init__(self, model): | |
| self.model = model | |
| def embed_documents(self, texts): | |
| return self.model.encode(texts, normalize_embeddings=True).tolist() | |
| def embed_query(self, text): | |
| return self.model.encode(text, normalize_embeddings=True).tolist() | |
| # ---------- Document Parsers ---------- | |
| def parse_pdf(file_path): | |
| documents = [] | |
| pdf = fitz.open(file_path) | |
| for page_num in range(len(pdf)): | |
| page = pdf.load_page(page_num) | |
| text = page.get_text().strip() | |
| if text: | |
| documents.append(LCDocument( | |
| page_content=text, | |
| metadata={"source": os.path.basename(file_path), "page": page_num + 1} | |
| )) | |
| return documents | |
| def parse_docx(file_path): | |
| doc = Document(file_path) | |
| text = "\n".join(p.text for p in doc.paragraphs if p.text.strip()) | |
| return [LCDocument(page_content=text, metadata={"source": os.path.basename(file_path), "page": 1})] | |
| def parse_excel(file_path): | |
| documents = [] | |
| excel = pd.ExcelFile(file_path) | |
| for sheet in excel.sheet_names: | |
| df = pd.read_excel(file_path, sheet_name=sheet) | |
| text = df.astype(str).to_string(index=False) | |
| documents.append(LCDocument( | |
| page_content=text, | |
| metadata={"source": os.path.basename(file_path), "sheet": sheet} | |
| )) | |
| return documents | |
| def parse_csv(file_path): | |
| df = pd.read_csv(file_path) | |
| text = df.astype(str).to_string(index=False) | |
| return [LCDocument(page_content=text, metadata={"source": os.path.basename(file_path), "page": 1})] | |
| def parse_txt(file_path): | |
| with open(file_path, "r", encoding="utf-8", errors="ignore") as f: | |
| text = f.read() | |
| return [LCDocument(page_content=text, metadata={"source": os.path.basename(file_path), "page": 1})] | |
| def parse_file(file_path): | |
| ext = os.path.splitext(file_path)[1].lower() | |
| if ext == ".pdf": | |
| return parse_pdf(file_path) | |
| elif ext == ".docx": | |
| return parse_docx(file_path) | |
| elif ext in [".xlsx", ".xls"]: | |
| return parse_excel(file_path) | |
| elif ext == ".csv": | |
| return parse_csv(file_path) | |
| elif ext == ".txt": | |
| return parse_txt(file_path) | |
| return [] | |
| # ---------- Global State ---------- | |
| vector_store = None | |
| processed_files = [] | |
| # ---------- Process Documents ---------- | |
| def process_documents(files): | |
| global vector_store, processed_files | |
| if not files: | |
| return "β οΈ Please upload at least one document.", "" | |
| all_documents = [] | |
| file_names = [] | |
| status_lines = [] | |
| for file in files: | |
| try: | |
| docs = parse_file(file.name) | |
| all_documents.extend(docs) | |
| name = os.path.basename(file.name) | |
| file_names.append(name) | |
| status_lines.append(f"β {name} β {len(docs)} section(s)") | |
| except Exception as e: | |
| status_lines.append(f"β {os.path.basename(file.name)} β Error: {e}") | |
| if not all_documents: | |
| return "β No text could be extracted from the uploaded files.", "" | |
| # Chunk | |
| splitter = RecursiveCharacterTextSplitter( | |
| chunk_size=1000, chunk_overlap=200, | |
| separators=["\n\n", "\n", ". ", " ", ""] | |
| ) | |
| chunks = [] | |
| for doc in all_documents: | |
| if len(doc.page_content.strip()) > 50: | |
| chunks.extend(splitter.create_documents([doc.page_content], metadatas=[doc.metadata])) | |
| else: | |
| chunks.append(doc) | |
| # Build vector store | |
| model = get_embedding_model() | |
| embedding_fn = BGEEmbeddings(model) | |
| vector_store = Chroma.from_documents( | |
| documents=chunks, | |
| embedding=embedding_fn, | |
| collection_name="rag_docs" | |
| ) | |
| processed_files.extend(file_names) | |
| status_lines.append(f"\nπ¦ {len(chunks)} chunks indexed into vector store") | |
| status_lines.append(f"π’ Ready to answer questions!") | |
| files_list = "\n".join([f"β’ {f}" for f in processed_files]) | |
| return "\n".join(status_lines), files_list | |
| def add_documents(files): | |
| global vector_store, processed_files | |
| if not files: | |
| return "β οΈ Please upload files to add.", "\n".join([f"β’ {f}" for f in processed_files]) | |
| if vector_store is None: | |
| return process_documents(files) | |
| all_documents = [] | |
| file_names = [] | |
| status_lines = [] | |
| for file in files: | |
| try: | |
| docs = parse_file(file.name) | |
| all_documents.extend(docs) | |
| name = os.path.basename(file.name) | |
| file_names.append(name) | |
| status_lines.append(f"β {name} β {len(docs)} section(s) added") | |
| except Exception as e: | |
| status_lines.append(f"β {os.path.basename(file.name)} β {e}") | |
| if all_documents: | |
| splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) | |
| chunks = [] | |
| for doc in all_documents: | |
| if len(doc.page_content.strip()) > 50: | |
| chunks.extend(splitter.create_documents([doc.page_content], metadatas=[doc.metadata])) | |
| else: | |
| chunks.append(doc) | |
| model = get_embedding_model() | |
| vector_store.add_documents(chunks) | |
| processed_files.extend(file_names) | |
| status_lines.append(f"π¦ {len(chunks)} new chunks added!") | |
| files_list = "\n".join([f"β’ {f}" for f in processed_files]) | |
| return "\n".join(status_lines), files_list | |
| # ---------- Chat ---------- | |
| def chat(message, history, groq_api_key): | |
| global vector_store | |
| if not groq_api_key or groq_api_key.strip() == "": | |
| groq_api_key = os.getenv("GROQ_API_KEY", "") | |
| if not groq_api_key: | |
| history.append((message, "β οΈ Please enter your Groq API key in the settings panel.")) | |
| return "", history | |
| if vector_store is None: | |
| history.append((message, "π Please upload and process documents first before asking questions.")) | |
| return "", history | |
| try: | |
| retriever = vector_store.as_retriever(search_type="similarity", search_kwargs={"k": 5}) | |
| relevant_docs = retriever.invoke(message) | |
| context = "\n\n---\n\n".join([ | |
| f"[Source: {doc.metadata.get('source', 'unknown')}, Page: {doc.metadata.get('page', 'N/A')}]\n{doc.page_content}" | |
| for doc in relevant_docs | |
| ]) | |
| client = Groq(api_key=groq_api_key) | |
| response = client.chat.completions.create( | |
| model="llama-3.1-8b-instant", | |
| messages=[ | |
| { | |
| "role": "system", | |
| "content": ( | |
| "You are an intelligent document assistant. Answer questions based on the provided context. " | |
| "Be concise, accurate, and cite the source document when possible. " | |
| "If the answer is not in the context, say so clearly.\n\n" | |
| f"Context:\n{context}" | |
| ) | |
| }, | |
| {"role": "user", "content": message} | |
| ], | |
| temperature=0.3, | |
| max_tokens=1024 | |
| ) | |
| answer = response.choices[0].message.content | |
| sources = list(set([ | |
| f"{doc.metadata.get('source', 'unknown')} (p.{doc.metadata.get('page', 'N/A')})" | |
| for doc in relevant_docs | |
| ])) | |
| sources_text = "\n\nπ **Sources:** " + " | ".join(sources) | |
| history.append((message, answer + sources_text)) | |
| except Exception as e: | |
| history.append((message, f"β Error: {str(e)}")) | |
| return "", history | |
| def clear_all(): | |
| global vector_store, processed_files | |
| vector_store = None | |
| processed_files = [] | |
| return [], "", "", "ποΈ Knowledge base cleared." | |
| # ---------- UI ---------- | |
| CSS = """ | |
| /* Overall theme */ | |
| .gradio-container { | |
| font-family: 'Inter', sans-serif !important; | |
| background: linear-gradient(135deg, #0f0c29, #302b63, #24243e) !important; | |
| min-height: 100vh; | |
| } | |
| /* Header */ | |
| .header-box { | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 16px; | |
| padding: 24px 32px; | |
| margin-bottom: 20px; | |
| text-align: center; | |
| box-shadow: 0 8px 32px rgba(102, 126, 234, 0.4); | |
| } | |
| /* Panels */ | |
| .panel { | |
| background: rgba(255,255,255,0.05) !important; | |
| border: 1px solid rgba(255,255,255,0.1) !important; | |
| border-radius: 16px !important; | |
| backdrop-filter: blur(10px) !important; | |
| } | |
| /* Buttons */ | |
| .btn-primary { | |
| background: linear-gradient(90deg, #667eea, #764ba2) !important; | |
| border: none !important; | |
| border-radius: 10px !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 8px 20px rgba(102, 126, 234, 0.5) !important; | |
| } | |
| .btn-secondary { | |
| background: rgba(255,255,255,0.1) !important; | |
| border: 1px solid rgba(255,255,255,0.2) !important; | |
| border-radius: 10px !important; | |
| color: white !important; | |
| } | |
| /* Chatbot */ | |
| .chatbot-container { | |
| border-radius: 16px !important; | |
| border: 1px solid rgba(255,255,255,0.1) !important; | |
| } | |
| /* Status boxes */ | |
| .status-box { | |
| background: rgba(16, 185, 129, 0.1) !important; | |
| border: 1px solid rgba(16, 185, 129, 0.3) !important; | |
| border-radius: 10px !important; | |
| } | |
| /* Input box */ | |
| input[type="text"], input[type="password"], textarea { | |
| background: rgba(255,255,255,0.08) !important; | |
| border: 1px solid rgba(255,255,255,0.15) !important; | |
| border-radius: 10px !important; | |
| color: white !important; | |
| } | |
| /* File upload */ | |
| .upload-area { | |
| border: 2px dashed rgba(102, 126, 234, 0.5) !important; | |
| border-radius: 12px !important; | |
| background: rgba(102, 126, 234, 0.05) !important; | |
| } | |
| """ | |
| with gr.Blocks( | |
| css=CSS, | |
| title="RAG Document Assistant", | |
| theme=gr.themes.Soft( | |
| primary_hue="violet", | |
| secondary_hue="purple", | |
| neutral_hue="slate", | |
| font=gr.themes.GoogleFont("Inter") | |
| ) | |
| ) as demo: | |
| # ---- Header ---- | |
| gr.HTML(""" | |
| <div class="header-box"> | |
| <h1 style="color:white; font-size:2.2em; margin:0; font-weight:700;"> | |
| π RAG Document Assistant | |
| </h1> | |
| <p style="color:rgba(255,255,255,0.85); margin:8px 0 0 0; font-size:1.1em;"> | |
| Upload documents Β· Ask questions Β· Get AI-powered answers | |
| </p> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| # ---- Left Panel ---- | |
| with gr.Column(scale=1, elem_classes="panel"): | |
| gr.HTML("<h3 style='color:#a78bfa; margin:0 0 12px 0;'>βοΈ Configuration</h3>") | |
| groq_key = gr.Textbox( | |
| label="π Groq API Key", | |
| placeholder="Enter your Groq API key...", | |
| type="password", | |
| info="Get yours free at console.groq.com" | |
| ) | |
| gr.HTML("<hr style='border-color:rgba(255,255,255,0.1); margin:16px 0;'>") | |
| gr.HTML("<h3 style='color:#a78bfa; margin:0 0 12px 0;'>π Upload Documents</h3>") | |
| file_upload = gr.File( | |
| label="Drop files here", | |
| file_count="multiple", | |
| file_types=[".pdf", ".docx", ".xlsx", ".csv", ".txt"], | |
| elem_classes="upload-area" | |
| ) | |
| with gr.Row(): | |
| process_btn = gr.Button("π Process", variant="primary", elem_classes="btn-primary") | |
| add_btn = gr.Button("β Add More", elem_classes="btn-secondary") | |
| process_status = gr.Textbox( | |
| label="π Status", | |
| lines=6, | |
| interactive=False, | |
| elem_classes="status-box" | |
| ) | |
| gr.HTML("<hr style='border-color:rgba(255,255,255,0.1); margin:16px 0;'>") | |
| gr.HTML("<h3 style='color:#a78bfa; margin:0 0 8px 0;'>π Knowledge Base</h3>") | |
| files_list = gr.Textbox( | |
| label="Indexed Documents", | |
| lines=4, | |
| interactive=False, | |
| placeholder="No documents loaded yet..." | |
| ) | |
| clear_btn = gr.Button("ποΈ Clear All", variant="stop", elem_classes="btn-secondary") | |
| gr.HTML(""" | |
| <div style='margin-top:16px; padding:12px; background:rgba(167,139,250,0.1); | |
| border-radius:10px; border:1px solid rgba(167,139,250,0.2);'> | |
| <p style='color:rgba(255,255,255,0.6); font-size:0.8em; margin:0;'> | |
| <b style='color:#a78bfa;'>Supported:</b> PDF Β· DOCX Β· XLSX Β· CSV Β· TXT<br> | |
| <b style='color:#a78bfa;'>Powered by:</b> LangChain Β· ChromaDB Β· Groq LLaMA 3.1 | |
| </p> | |
| </div> | |
| """) | |
| # ---- Right Panel (Chat) ---- | |
| with gr.Column(scale=2, elem_classes="panel"): | |
| gr.HTML("<h3 style='color:#a78bfa; margin:0 0 12px 0;'>π¬ Ask Questions</h3>") | |
| chatbot = gr.Chatbot( | |
| label="", | |
| height=520, | |
| show_label=False, | |
| avatar_images=("π€", "π€"), | |
| bubble_full_width=False, | |
| elem_classes="chatbot-container" | |
| ) | |
| with gr.Row(): | |
| msg_input = gr.Textbox( | |
| placeholder="Ask anything about your documents...", | |
| label="", | |
| show_label=False, | |
| scale=5, | |
| container=False | |
| ) | |
| send_btn = gr.Button("Send β€", variant="primary", scale=1, elem_classes="btn-primary") | |
| gr.HTML(""" | |
| <div style='text-align:center; margin-top:8px;'> | |
| <span style='color:rgba(255,255,255,0.3); font-size:0.75em;'> | |
| Press Enter or click Send Β· Powered by Groq LLaMA 3.1 8B | |
| </span> | |
| </div> | |
| """) | |
| with gr.Accordion("π‘ Example Questions", open=False): | |
| gr.Examples( | |
| examples=[ | |
| ["What is this document about?"], | |
| ["Summarize the key points"], | |
| ["What are the main conclusions?"], | |
| ["List all important dates mentioned"], | |
| ["Who are the key people mentioned?"], | |
| ], | |
| inputs=msg_input, | |
| label="" | |
| ) | |
| # ---- Events ---- | |
| process_btn.click( | |
| fn=process_documents, | |
| inputs=[file_upload], | |
| outputs=[process_status, files_list] | |
| ) | |
| add_btn.click( | |
| fn=add_documents, | |
| inputs=[file_upload], | |
| outputs=[process_status, files_list] | |
| ) | |
| send_btn.click( | |
| fn=chat, | |
| inputs=[msg_input, chatbot, groq_key], | |
| outputs=[msg_input, chatbot] | |
| ) | |
| msg_input.submit( | |
| fn=chat, | |
| inputs=[msg_input, chatbot, groq_key], | |
| outputs=[msg_input, chatbot] | |
| ) | |
| clear_btn.click( | |
| fn=clear_all, | |
| outputs=[chatbot, files_list, process_status, process_status] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |