Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import os | |
| import shutil | |
| from langchain_community.document_loaders import PyPDFLoader | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| from langchain_huggingface import HuggingFaceEmbeddings | |
| from langchain_community.vectorstores import FAISS | |
| from langchain_classic.retrievers import EnsembleRetriever | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from langchain_core.output_parsers import StrOutputParser | |
| from langchain_core.runnables import RunnablePassthrough | |
| import pandas as pd | |
| from datetime import datetime | |
| # if "page" not in st.session_state: | |
| # st.session_state.page = "reader" | |
| # if st.session_state.page == "reader": | |
| # document_reader_page() | |
| # elif st.session_state.page == "loader": | |
| # pdf_loader_page() | |
| # elif st.session_state.page == "iso": | |
| # chatbot_page("Chatbuddy Application (ISO26262)", r"C:\Users\RVX2KOR\Desktop\LLMs\faissdb", "messages_iso") | |
| # elif st.session_state.page == "vda360": | |
| # chatbot_page("Chatbuddy Application (VDA360)", r"C:\Users\RVX2KOR\Desktop\Olllama\vda-360", "messages_vda360") | |
| # elif st.session_state.page == "vda305": | |
| # chatbot_page("Chatbuddy Application (VDA305)", r"C:\Users\RVX2KOR\Desktop\Olllama\vda_305", "messages_vda305") | |
| # elif st.session_state.page == "obd": | |
| # chatbot_page("Chatbuddy Application (OBD_J1979-2_202104)", r"C:\Users\RVX2KOR\Desktop\Olllama\obd_store", "messages_obd") | |
| # elif st.session_state.page == "standards": | |
| # standards_page() | |
| # ------------------------- | |
| # Configuration / paths (all overridable via environment variables) | |
| try: | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| except ImportError: | |
| pass | |
| PDF_STORE_DIR = os.environ.get("PDF_STORE_DIR", "/data/pdfs") | |
| EMBEDDINGS_STORE_DIR = os.environ.get("EMBEDDINGS_STORE_DIR", "/data/embeddings") | |
| USER_XL_PATH = os.environ.get("USER_XL_PATH", "/data/users/users.xlsx") | |
| # LLM backend: "ollama" or "huggingface" | |
| LLM_BACKEND = os.environ.get("LLM_BACKEND", "huggingface") | |
| OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434") | |
| OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "mistral") | |
| HF_MODEL_ID = os.environ.get("HF_MODEL_ID", "mistralai/Mistral-7B-Instruct-v0.3") | |
| HF_TOKEN = os.environ.get("HF_TOKEN", "") | |
| def get_llm(): | |
| """Return the configured LLM instance.""" | |
| if LLM_BACKEND == "ollama": | |
| from langchain_community.llms import Ollama | |
| return Ollama(model=OLLAMA_MODEL, base_url=OLLAMA_BASE_URL) | |
| else: | |
| from langchain_community.llms import HuggingFaceEndpoint | |
| return HuggingFaceEndpoint( | |
| repo_id=HF_MODEL_ID, | |
| huggingfacehub_api_token=HF_TOKEN, | |
| temperature=0.3, | |
| max_new_tokens=512, | |
| ) | |
| def run_rag(retriever, query): | |
| """Run a RAG chain using the modern LCEL approach.""" | |
| prompt = ChatPromptTemplate.from_template( | |
| "Answer the question based only on the context below.\n\n" | |
| "Context:\n{context}\n\nQuestion: {question}" | |
| ) | |
| def format_docs(docs): | |
| return "\n\n".join(doc.page_content for doc in docs) | |
| chain = ( | |
| {"context": retriever | format_docs, "question": RunnablePassthrough()} | |
| | prompt | |
| | get_llm() | |
| | StrOutputParser() | |
| ) | |
| return chain.invoke(query) | |
| # Pre-built FAISS index paths (default to subdirs inside EMBEDDINGS_STORE_DIR) | |
| ISO_FAISS_PATH = os.environ.get("ISO_FAISS_PATH", os.path.join(EMBEDDINGS_STORE_DIR, "iso26262")) | |
| VDA360_FAISS_PATH = os.environ.get("VDA360_FAISS_PATH", os.path.join(EMBEDDINGS_STORE_DIR, "vda360")) | |
| VDA305_FAISS_PATH = os.environ.get("VDA305_FAISS_PATH", os.path.join(EMBEDDINGS_STORE_DIR, "vda305")) | |
| OBD_FAISS_PATH = os.environ.get("OBD_FAISS_PATH", os.path.join(EMBEDDINGS_STORE_DIR, "obd_store")) | |
| STANDARDS_EMBEDDINGS_FOLDER = "standards_all" | |
| STANDARDS_EMBEDDINGS_PATH = os.path.join(EMBEDDINGS_STORE_DIR, STANDARDS_EMBEDDINGS_FOLDER) | |
| os.makedirs(os.path.dirname(USER_XL_PATH), exist_ok=True) | |
| if not os.path.exists(USER_XL_PATH): | |
| df = pd.DataFrame(columns=["name", "ntid", "login_time"]) | |
| df.to_excel(USER_XL_PATH, index=False) | |
| os.makedirs(PDF_STORE_DIR, exist_ok=True) | |
| os.makedirs(EMBEDDINGS_STORE_DIR, exist_ok=True) | |
| # Set page config once | |
| st.set_page_config(page_title="Document Reader System", layout="wide") | |
| # ------------------------- | |
| # Helper: create a combined FAISS from all PDFs in PDF_STORE_DIR | |
| def build_standards_combined(save_path=STANDARDS_EMBEDDINGS_PATH): | |
| try: | |
| pdf_files = [os.path.join(PDF_STORE_DIR, f) for f in os.listdir(PDF_STORE_DIR) if f.lower().endswith(".pdf")] | |
| if not pdf_files: | |
| st.warning("No PDFs found in PDF store folder. Place PDFs in PDF_STORE_DIR and try again.") | |
| return False | |
| all_docs = [] | |
| splitter = RecursiveCharacterTextSplitter(chunk_size=10000, chunk_overlap=100) | |
| for pdf in pdf_files: | |
| st.write(f"Loading and splitting: {os.path.basename(pdf)}") | |
| loader = PyPDFLoader(pdf) | |
| pages = loader.load() | |
| docs = splitter.split_documents(pages) | |
| all_docs.extend(docs) | |
| if not all_docs: | |
| st.error("No text extracted from PDFs (empty documents).") | |
| return False | |
| st.write("Creating embeddings (this may take a while for many/large PDFs)...") | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") | |
| combined_vs = FAISS.from_documents(all_docs, embedding=embeddings) | |
| # remove old combined folder if present to avoid conflicts | |
| if os.path.exists(save_path): | |
| try: | |
| shutil.rmtree(save_path) | |
| except Exception as e: | |
| st.warning(f"Couldn't remove old standards folder: {e}") | |
| os.makedirs(save_path, exist_ok=True) | |
| combined_vs.save_local(save_path) | |
| st.session_state["standards_vs"] = combined_vs | |
| st.success(f"Combined standards embeddings created and saved to:\n{save_path}") | |
| return True | |
| except Exception as e: | |
| st.error(f"Error while building standards embeddings: {e}") | |
| return False | |
| # ------------------------- | |
| # Process a single PDF | |
| def process_pdf(file_path, save_folder): | |
| try: | |
| st.write("Step 1: Initializing PyPDFLoader...") | |
| loader = PyPDFLoader(file_path) | |
| st.write("Step 2: Loading data...") | |
| data = loader.load() | |
| st.write("Step 3: Splitting text...") | |
| text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) | |
| text_chunks = text_splitter.split_documents(data) | |
| st.write("Step 4: Initializing embeddings...") | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") | |
| st.write("Step 5: Creating FAISS vector store...") | |
| vector_store = FAISS.from_documents(text_chunks, embedding=embeddings) | |
| save_path = os.path.join(EMBEDDINGS_STORE_DIR, save_folder) | |
| os.makedirs(save_path, exist_ok=True) | |
| st.write("Step 6: Saving vector store locally...") | |
| vector_store.save_local(save_path) | |
| st.success(f"Process completed successfully! Stored in {save_path}") | |
| return True | |
| except Exception as e: | |
| st.error(f"An error occurred: {e}") | |
| return False | |
| def login_page(): | |
| st.title("🔐 Login") | |
| st.write("Please enter your details to continue") | |
| name = st.text_input("Name") | |
| ntid = st.text_input("NTID") | |
| if st.button("Login"): | |
| if name.strip() == "" or ntid.strip() == "": | |
| st.warning("Both Name and NTID are required") | |
| return | |
| df = pd.read_excel(USER_XL_PATH) | |
| existing = df[ | |
| (df["name"].str.lower() == name.lower()) & | |
| (df["ntid"].str.lower() == ntid.lower()) | |
| ] | |
| if existing.empty: | |
| df = pd.concat( | |
| [df, pd.DataFrame([{ | |
| "name": name, | |
| "ntid": ntid, | |
| "login_time": datetime.now() | |
| }])], | |
| ignore_index=True | |
| ) | |
| df.to_excel(USER_XL_PATH, index=False) | |
| st.session_state.logged_in = True | |
| st.session_state.user_name = name | |
| st.session_state.user_ntid = ntid | |
| st.success(f"Welcome {name} 👋") | |
| st.session_state.page = "reader" | |
| st.rerun() | |
| # ------------------------- | |
| # Page 1: Document Reader (home) | |
| def document_reader_page(): | |
| st.title("GENAI for EBS") | |
| st.write("Click a button below to proceed.") | |
| if st.button("Document Loader"): | |
| st.session_state.page = "loader" | |
| st.rerun() | |
| if st.button("ISO26262"): | |
| st.session_state.page = "iso" | |
| st.rerun() | |
| if st.button("VDA360"): | |
| st.session_state.page = "vda360" | |
| st.rerun() | |
| if st.button("VDA305"): | |
| st.session_state.page = "vda305" | |
| st.rerun() | |
| if st.button("OBD_J1979-2_202104"): | |
| st.session_state.page = "obd" | |
| st.rerun() | |
| if st.button("Standards"): | |
| st.session_state.page = "standards" | |
| st.rerun() | |
| # ------------------------- | |
| # Page 2: PDF Loader + QA Chat | |
| def pdf_loader_page(): | |
| st.title("PDF Loader") | |
| col1, col2 = st.columns([3, 1]) | |
| # List existing projects | |
| with col2: | |
| st.markdown("### 📂 Uploaded PDFs") | |
| existing_folders = [d for d in os.listdir(EMBEDDINGS_STORE_DIR) | |
| if os.path.isdir(os.path.join(EMBEDDINGS_STORE_DIR, d))] | |
| if existing_folders: | |
| for folder in existing_folders: | |
| st.markdown(f"- {folder}") | |
| else: | |
| st.info("No PDFs found") | |
| # Upload or load | |
| with col1: | |
| st.write("Answer the question below to proceed.") | |
| if "prev_choice" not in st.session_state: | |
| st.session_state.prev_choice = None | |
| choice = st.radio("Is PDF already uploaded?", ["Select an option", "Yes", "No"]) | |
| if choice != st.session_state.prev_choice: | |
| st.session_state.pdf_processed = False | |
| st.session_state.messages_loader = [] | |
| st.session_state.current_project = None | |
| st.session_state.prev_choice = choice | |
| if choice == "Yes": | |
| if existing_folders: | |
| selected_folder = st.selectbox("Select an existing PDF/project:", existing_folders) | |
| if st.button("Load Selected PDF"): | |
| st.session_state.current_project = selected_folder | |
| st.session_state.pdf_processed = True | |
| st.success(f"Loaded embeddings from '{selected_folder}' successfully!") | |
| else: | |
| st.info("No uploaded PDFs found. Please select 'No' to upload a new one.") | |
| elif choice == "No": | |
| st.write("Upload a PDF file to create embeddings and ask questions.") | |
| project_name = st.text_input("Enter a name for this PDF/project:") | |
| uploaded_file = st.file_uploader("Browse PDF", type="pdf") | |
| if uploaded_file is not None and project_name.strip() != "": | |
| file_path = os.path.join(PDF_STORE_DIR, uploaded_file.name) | |
| with open(file_path, "wb") as f: | |
| f.write(uploaded_file.getbuffer()) | |
| st.success("File uploaded successfully! Click 'Load' to process it.") | |
| if st.button("Load") or st.session_state.pdf_processed is False: | |
| with st.spinner("Processing PDF and creating embeddings..."): | |
| if process_pdf(file_path, project_name): | |
| st.session_state.pdf_processed = True | |
| st.session_state.current_project = project_name | |
| else: | |
| st.error("Failed to process the PDF.") | |
| return | |
| elif uploaded_file is not None and project_name.strip() == "": | |
| st.warning("Please enter a project name before loading the PDF.") | |
| else: | |
| st.info("Please select Yes or No to continue.") | |
| # QA interface | |
| if st.session_state.get("pdf_processed") and st.session_state.get("current_project"): | |
| st.markdown("---") | |
| st.subheader("Ask questions about your PDF") | |
| query = st.text_input("Type your question here:", key="qa_query") | |
| if query: | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") | |
| vector_store_path = os.path.join(EMBEDDINGS_STORE_DIR, st.session_state.current_project) | |
| try: | |
| vector_store = FAISS.load_local(vector_store_path, embeddings, allow_dangerous_deserialization=True) | |
| retriever = vector_store.as_retriever() | |
| response = run_rag(retriever, query) | |
| if 'messages_loader' not in st.session_state: | |
| st.session_state.messages_loader = [] | |
| st.session_state.messages_loader.append({"query": query, "response": response}) | |
| except Exception as e: | |
| st.error(f"Failed to load embeddings: {e}") | |
| if 'messages_loader' in st.session_state: | |
| for msg in st.session_state.messages_loader: | |
| with st.chat_message("user"): | |
| st.markdown(f"**You:** {msg['query']}") | |
| with st.chat_message("assistant"): | |
| st.markdown(f"**Bot:** {msg['response']}") | |
| st.markdown("---") | |
| if st.button("Back"): | |
| st.session_state.page = "reader" | |
| st.session_state.pdf_processed = False | |
| st.session_state.messages_loader = [] | |
| st.session_state.current_project = None | |
| st.rerun() | |
| # ------------------------- | |
| # Generic Chatbot Page Template | |
| def chatbot_page(title, faiss_path, msg_key): | |
| st.title(title) | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") | |
| try: | |
| faiss_index = FAISS.load_local(faiss_path, embeddings, allow_dangerous_deserialization=True) | |
| retriever = faiss_index.as_retriever() | |
| except Exception as e: | |
| st.error(f"Failed to load FAISS index from {faiss_path}: {e}") | |
| retriever = None | |
| if retriever is None: | |
| st.error("Retriever not available.") | |
| if st.button("Back"): | |
| st.session_state.page = "reader" | |
| st.rerun() | |
| return | |
| if msg_key not in st.session_state: | |
| st.session_state[msg_key] = [] | |
| query = st.text_input("Ask a question about your documents:", "") | |
| if query: | |
| response = run_rag(retriever, query) | |
| st.session_state[msg_key].append({"query": query, "response": response}) | |
| for msg in st.session_state[msg_key]: | |
| with st.chat_message("user"): | |
| st.markdown(f"**Hello Robot**\n{msg['query']}") | |
| with st.chat_message("assistant"): | |
| st.markdown(f"**Hello Human**\n{msg['response']}") | |
| st.markdown("---") | |
| if st.button("Back"): | |
| st.session_state.page = "reader" | |
| st.rerun() | |
| # Page 4: Standards (creates/loads combined embeddings from all PDFs) | |
| def standards_page(): | |
| st.title("Standards Chatbot") | |
| if not st.session_state.get("standards_loaded", False): | |
| st.session_state.messages_standards = [] | |
| st.session_state.selected_standards = [] | |
| # RIGHT PANEL | |
| col1, col2 = st.columns([3, 1]) | |
| with col2: | |
| st.markdown("### 📂 Available Embeddings (Projects)") | |
| existing_folders = [d for d in os.listdir(EMBEDDINGS_STORE_DIR) | |
| if os.path.isdir(os.path.join(EMBEDDINGS_STORE_DIR, d))] | |
| selected_folders = st.multiselect( | |
| "Select one or more projects:", | |
| existing_folders | |
| ) | |
| if st.button("Load Selected Embeddings"): | |
| if selected_folders: | |
| st.session_state.standards_loaded = True | |
| st.session_state.selected_standards = selected_folders | |
| st.success(f"Loaded embeddings for: {', '.join(selected_folders)}") | |
| else: | |
| st.warning("Please select at least one project.") | |
| # MAIN QA INTERFACE | |
| with col1: | |
| if st.session_state.get("standards_loaded", False): | |
| query = st.text_input("Ask a question about selected PDFs:") | |
| if query: | |
| # Load all selected FAISS indexes and merge | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") | |
| retrievers = [] | |
| for folder in st.session_state.selected_standards: | |
| path = os.path.join(EMBEDDINGS_STORE_DIR, folder) | |
| try: | |
| vs = FAISS.load_local(path, embeddings, allow_dangerous_deserialization=True) | |
| retrievers.append(vs.as_retriever()) | |
| except Exception as e: | |
| st.error(f"Failed to load {folder}: {e}") | |
| if retrievers: | |
| # Merge retrievers into one | |
| combined_retriever = EnsembleRetriever(retrievers=retrievers, weights=[1]*len(retrievers)) | |
| response = run_rag(combined_retriever, query) | |
| # Store chat history | |
| if "messages_standards" not in st.session_state: | |
| st.session_state.messages_standards = [] | |
| st.session_state.messages_standards.append({"query": query, "response": response}) | |
| # Show chat history | |
| if "messages_standards" in st.session_state: | |
| for msg in st.session_state.messages_standards: | |
| with st.chat_message("user"): | |
| st.markdown(f"**You:** {msg['query']}") | |
| with st.chat_message("assistant"): | |
| st.markdown(f"**Bot:** {msg['response']}") | |
| st.markdown("---") | |
| if st.button("Back"): | |
| st.session_state.page = "reader" | |
| st.session_state.standards_loaded = False | |
| st.session_state.messages_standards = [] | |
| st.session_state.selected_standards = [] | |
| st.rerun() | |
| # ------------------------- | |
| # Page routing | |
| # if "page" not in st.session_state: | |
| # st.session_state.page = "reader" | |
| # Auto-login via environment variables (for containerised / dev deployments) | |
| _AUTO_LOGIN_NAME = os.environ.get("AUTO_LOGIN_NAME", "").strip() | |
| _AUTO_LOGIN_NTID = os.environ.get("AUTO_LOGIN_NTID", "").strip() | |
| if "logged_in" not in st.session_state: | |
| st.session_state.logged_in = False | |
| if not st.session_state.logged_in and _AUTO_LOGIN_NAME and _AUTO_LOGIN_NTID: | |
| st.session_state.logged_in = True | |
| st.session_state.user_name = _AUTO_LOGIN_NAME | |
| st.session_state.user_ntid = _AUTO_LOGIN_NTID | |
| if not st.session_state.logged_in: | |
| login_page() | |
| st.stop() | |
| if "page" not in st.session_state: | |
| st.session_state.page = "reader" | |
| if st.session_state.page == "reader": | |
| document_reader_page() | |
| elif st.session_state.page == "loader": | |
| pdf_loader_page() | |
| elif st.session_state.page == "iso": | |
| chatbot_page("Chatbuddy Application (ISO26262)", ISO_FAISS_PATH, "messages_iso") | |
| elif st.session_state.page == "vda360": | |
| chatbot_page("Chatbuddy Application (VDA360)", VDA360_FAISS_PATH, "messages_vda360") | |
| elif st.session_state.page == "vda305": | |
| chatbot_page("Chatbuddy Application (VDA305)", VDA305_FAISS_PATH, "messages_vda305") | |
| elif st.session_state.page == "obd": | |
| chatbot_page("Chatbuddy Application (OBD_J1979-2_202104)", OBD_FAISS_PATH, "messages_obd") | |
| elif st.session_state.page == "standards": | |
| standards_page() |