Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| FAR Chatbot - Hugging Face Spaces Version | |
| Federal Acquisition Regulation Assistant with Clickable Citations | |
| """ | |
| import streamlit as st | |
| import time | |
| import logging | |
| import os | |
| import sys | |
| import re | |
| from datetime import datetime | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Import the chatbot | |
| from far_chatbot import FARChatbot | |
| try: | |
| import markdown | |
| except ImportError: | |
| markdown = None | |
| # Configure page | |
| st.set_page_config( | |
| page_title="FAR Chatbot - Federal Acquisition Regulation Assistant", | |
| page_icon="ποΈ", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # Authentication | |
| VALID_CREDENTIALS = {"testuser": "farbot2025"} | |
| def check_password(): | |
| def password_entered(): | |
| username = st.session_state.get("username", "") | |
| password = st.session_state.get("password", "") | |
| if username in VALID_CREDENTIALS and VALID_CREDENTIALS[username] == password: | |
| st.session_state["authenticated"] = True | |
| del st.session_state["password"] | |
| else: | |
| st.session_state["authenticated"] = False | |
| if "authenticated" not in st.session_state: | |
| st.session_state["authenticated"] = False | |
| if not st.session_state["authenticated"]: | |
| col1, col2, col3 = st.columns([1, 2, 1]) | |
| with col2: | |
| st.markdown("## ποΈ FAR Chatbot Login") | |
| st.text_input("Username", key="username") | |
| st.text_input("Password", type="password", key="password") | |
| st.button("π Log In", on_click=password_entered, type="primary", use_container_width=True) | |
| if st.session_state.get("authenticated") == False and "username" in st.session_state and st.session_state["username"]: | |
| st.error("β Invalid credentials") | |
| return False | |
| return True | |
| if not check_password(): | |
| st.stop() | |
| # CSS Styling | |
| st.markdown(""" | |
| <style> | |
| .main-header { | |
| text-align: center; padding: 1.5rem; | |
| background: linear-gradient(135deg, #1a365d 0%, #2c5282 50%, #2b6cb0 100%); | |
| color: white; border-radius: 16px; margin-bottom: 2rem; | |
| } | |
| .main-header h1 { margin: 0; font-size: 2.5rem; } | |
| .main-header p { margin: 0.5rem 0 0 0; opacity: 0.9; } | |
| .user-message { | |
| background: #f7fafc; padding: 1rem; border-radius: 16px; | |
| margin: 1rem 0; border-left: 4px solid #e53e3e; | |
| } | |
| .bot-message { | |
| background: linear-gradient(135deg, #ebf8ff 0%, #e6fffa 100%); | |
| padding: 1.25rem; border-radius: 16px; margin: 1rem 0; | |
| border-left: 4px solid #2b6cb0; | |
| } | |
| .citation-link { | |
| background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); | |
| padding: 2px 8px; border-radius: 6px; font-family: monospace; | |
| font-weight: 600; color: #92400e; text-decoration: none; | |
| border: 1px solid #f59e0b; | |
| } | |
| .citation-link:hover { background: #fde68a; } | |
| .source-card { | |
| background: white; border: 1px solid #e2e8f0; | |
| border-radius: 12px; padding: 1rem; margin: 0.75rem 0; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Session state | |
| if 'chatbot' not in st.session_state: | |
| st.session_state.chatbot = None | |
| if 'chat_history' not in st.session_state: | |
| st.session_state.chat_history = [] | |
| if 'show_sources' not in st.session_state: | |
| st.session_state.show_sources = True | |
| def load_chatbot(): | |
| """Load the FAR chatbot""" | |
| try: | |
| logger.info("Loading FAR Chatbot...") | |
| chatbot = FARChatbot( | |
| faiss_index_path="data/faiss_index.index", | |
| texts_path="data/texts.txt", | |
| use_gpt5=True | |
| ) | |
| return chatbot | |
| except Exception as e: | |
| logger.error(f"Error loading chatbot: {e}") | |
| st.error(f"Error loading chatbot: {e}") | |
| return None | |
| def get_acquisition_gov_url(citation: str) -> str: | |
| base_citation = re.sub(r'\([a-z]\)$', '', citation) | |
| return f"https://www.acquisition.gov/far/{base_citation}" | |
| def make_citations_clickable(response: str, search_results: list) -> str: | |
| if not markdown: | |
| return response | |
| text = response | |
| def make_link(citation): | |
| url = get_acquisition_gov_url(citation) | |
| return f'<a href="{url}" target="_blank" class="citation-link">' | |
| # Replace citation patterns | |
| text = re.sub(r'\[FAR\s+(\d+\.\d+(?:-\d+)?(?:\([a-z]\))?)\]', | |
| lambda m: f'{make_link(m.group(1))}[{m.group(1)}]</a>', text) | |
| text = re.sub(r'\[(\d+\.\d+(?:-\d+)?(?:\([a-z]\))?)\]', | |
| lambda m: f'{make_link(m.group(1))}[{m.group(1)}]</a>', text) | |
| text = re.sub(r'(?<!View )FAR\s+(\d+\.\d+(?:-\d+)?(?:\([a-z]\))?)', | |
| lambda m: f'{make_link(m.group(1))}FAR {m.group(1)}</a>', text) | |
| return markdown.markdown(text, extensions=['tables', 'fenced_code', 'nl2br']) | |
| # Header | |
| st.markdown(""" | |
| <div class="main-header"> | |
| <h1>ποΈ FAR Chatbot</h1> | |
| <p>Federal Acquisition Regulation Assistant</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Sidebar | |
| with st.sidebar: | |
| st.markdown("## βοΈ Settings") | |
| if st.session_state.chatbot is None: | |
| st.session_state.chatbot = load_chatbot() | |
| if st.session_state.chatbot: | |
| st.success("β Chatbot Ready!") | |
| else: | |
| st.error("β Failed to load") | |
| st.session_state.show_sources = st.checkbox("Show sources", value=True) | |
| st.markdown("### π‘ Sample Questions") | |
| samples = [ | |
| "What are small business set-asides?", | |
| "Explain the simplified acquisition threshold", | |
| "What is the micro-purchase threshold?", | |
| "When can I use sole source?" | |
| ] | |
| for q in samples: | |
| if st.button(f"π¬ {q}", key=f"s_{hash(q)}"): | |
| st.session_state.current_question = q | |
| st.rerun() | |
| if st.button("ποΈ Clear Chat"): | |
| st.session_state.chat_history = [] | |
| st.rerun() | |
| if st.button("πͺ Logout"): | |
| st.session_state["authenticated"] = False | |
| st.rerun() | |
| # Main content | |
| if st.session_state.chatbot is None: | |
| st.error("Chatbot not loaded") | |
| st.stop() | |
| # Display chat history | |
| for entry in st.session_state.chat_history: | |
| question, answer, search_results, timestamp = entry[:4] | |
| st.markdown(f'<div class="user-message">π€ <b>You:</b> {question}</div>', unsafe_allow_html=True) | |
| formatted = make_citations_clickable(answer, search_results) | |
| st.markdown(f'<div class="bot-message">π€ <b>FAR Bot:</b><br>{formatted}</div>', unsafe_allow_html=True) | |
| if st.session_state.show_sources and search_results: | |
| with st.expander("π Sources"): | |
| for cit, txt in search_results[:5]: | |
| url = get_acquisition_gov_url(cit) | |
| st.markdown(f"**[FAR {cit}]({url})**: {txt[:300]}...") | |
| # Input | |
| st.markdown("## π¬ Ask a Question") | |
| question = "" | |
| if 'current_question' in st.session_state: | |
| question = st.session_state.current_question | |
| del st.session_state.current_question | |
| question = st.text_input("Your question:", value=question, placeholder="Ask about FAR regulations...") | |
| if st.button("π Ask", type="primary") and question.strip(): | |
| with st.spinner("Processing..."): | |
| try: | |
| result = st.session_state.chatbot.chat(question, top_k=None) | |
| timestamp = datetime.now().strftime("%H:%M") | |
| st.session_state.chat_history.append(( | |
| question, | |
| result['response'], | |
| result.get('search_results', []), | |
| timestamp | |
| )) | |
| st.rerun() | |
| except Exception as e: | |
| st.error(f"Error: {e}") | |
| st.markdown("---") | |
| st.markdown("ποΈ FAR Chatbot | Powered by GPT-4 Turbo | [acquisition.gov](https://acquisition.gov)") | |