Spaces:
Sleeping
Sleeping
File size: 7,864 Bytes
ea523d0 02134bc ea523d0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | #!/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
@st.cache_resource
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)")
|