Spaces:
Build error
Build error
File size: 8,099 Bytes
1357c97 46a28cb 1357c97 0989f72 1357c97 0989f72 1357c97 0989f72 46a28cb 1357c97 46a28cb 1357c97 8ff1aa9 1357c97 0989f72 1357c97 46a28cb 1357c97 46a28cb 1357c97 | 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 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | import os
import gradio as gr
from typing import List, Tuple
import numpy as np
from pypdf import PdfReader
from docx import Document
from sentence_transformers import SentenceTransformer
import faiss
from huggingface_hub import InferenceClient
# --- Configuration & Styling ---
# SPJIMR Branding Colors
PRIMARY_BLUE = "#004890"
ACCENT_GOLD = "#C5A059"
BG_LIGHT = "#F8F9FA"
TEXT_DARK = "#1E1E1E"
CUSTOM_CSS = f"""
.container {{
max-width: 900px;
margin: auto;
padding: 20px;
font-family: 'Inter', sans-serif;
}}
.header {{
background-color: {PRIMARY_BLUE};
padding: 20px;
border-radius: 12px 12px 0 0;
color: white;
text-align: center;
border-bottom: 4px solid {ACCENT_GOLD};
margin-bottom: 20px;
}}
.chat-container {{
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
background: white;
}}
.gr-button-primary {{
background-color: {PRIMARY_BLUE} !important;
border: none !important;
border-radius: 8px !important;
color: white !important;
}}
.gr-button-secondary {{
background-color: white !important;
border: 1px solid {PRIMARY_BLUE} !important;
border-radius: 8px !important;
color: {PRIMARY_BLUE} !important;
}}
#footer {{
text-align: center;
color: #666;
margin-top: 20px;
font-size: 0.9em;
}}
.user-bubble {{
background-color: {PRIMARY_BLUE} !important;
color: white !important;
border-radius: 15px 15px 0 15px !important;
}}
.bot-bubble {{
background-color: #F1F1F1 !important;
color: {TEXT_DARK} !important;
border-radius: 15px 15px 15px 0 !important;
}}
"""
# --- Backend Logic ---
# Initialize LLM Client (Hugging Face Inference API)
# Get token from environment variable 'HF_TOKEN'
hf_token = os.getenv("HF_TOKEN")
client = InferenceClient("mistralai/Mistral-7B-Instruct-v0.3", token=hf_token)
# Initialize Embedding Model
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
class DocumentProcessor:
def __init__(self):
self.index = None
self.chunks = []
self.file_name = ""
def extract_text(self, file_path: str) -> str:
ext = os.path.splitext(file_path)[1].lower()
text = ""
if ext == ".pdf":
reader = PdfReader(file_path)
for page in reader.pages:
text += page.extract_text() + "\n"
elif ext == ".docx":
doc = Document(file_path)
for para in doc.paragraphs:
text += para.text + "\n"
elif ext == ".txt":
with open(file_path, "r", encoding="utf-8") as f:
text = f.read()
return text
def chunk_text(self, text: str, chunk_size: int = 500, overlap: int = 50) -> List[str]:
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size - overlap):
chunk = " ".join(words[i : i + chunk_size])
chunks.append(chunk)
return chunks
def process_document(self, file):
if file is None:
return "No file uploaded."
self.file_name = os.path.basename(file.name)
text = self.extract_text(file.name)
if not text.strip():
return "Could not extract text from document."
self.chunks = self.chunk_text(text)
embeddings = embed_model.encode(self.chunks)
# Create FAISS Index
dimension = embeddings.shape[1]
self.index = faiss.IndexFlatL2(dimension)
self.index.add(np.array(embeddings).astype("float32"))
return f"Successfully processed: {self.file_name} ({len(self.chunks)} chunks)"
def retrieve(self, query: str, k: int = 3) -> str:
if self.index is None:
return ""
query_vec = embed_model.encode([query])
distances, indices = self.index.search(np.array(query_vec).astype("float32"), k)
context = ""
for idx in indices[0]:
if idx < len(self.chunks):
context += self.chunks[idx] + "\n---\n"
return context
# Global state for document processing
doc_proc = DocumentProcessor()
def chat_response(message, history):
# System Prompt
system_message = "You are a helpful assistant for SPJIMR. "
# Check if we have context from a document
context = doc_proc.retrieve(message)
if context:
prompt = f"{system_message}Answer the user question based ONLY on the provided context. If the answer is not in the context, say you don't know based on the document.\n\nContext:\n{context}\n\nQuestion: {message}\nAnswer:"
else:
prompt = f"{system_message}You are a general assistant. User: {message}\nAssistant:"
# Call LLM
response = ""
try:
# Mistral-7B format
formatted_prompt = f"<s>[INST] {prompt} [/INST]"
for msg in client.text_generation(formatted_prompt, max_new_tokens=512, stream=True):
response += msg
yield response
except Exception as e:
yield f"Error connecting to LLM: {str(e)}. Please ensure HF_TOKEN is set."
def clear_session():
global doc_proc
doc_proc = DocumentProcessor()
return None, [], "Session cleared."
# --- UI Construction ---
with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Soft(primary_hue="blue")) as demo:
with gr.Div(elem_classes="container"):
# Header
gr.HTML(f"""
<div class='header'>
<h1 style='margin:0; font-weight: 700;'>Document Assistant</h1>
<p style='margin:5px 0 0 0; opacity: 0.9;'>SPJIMR Decision Support System</p>
</div>
""")
with gr.Row():
# Left Column: Upload & Controls
with gr.Column(scale=1):
file_input = gr.File(
label="Upload Document (PDF, TXT, DOCX)",
file_types=[".pdf", ".docx", ".txt"],
elem_id="upload-box"
)
status_msg = gr.Markdown("Status: Ready", elem_id="status")
with gr.Row():
process_btn = gr.Button("Analyze Document", variant="primary")
clear_btn = gr.Button("Clear All", variant="secondary")
gr.Markdown("""
### Instructions:
1. Upload a document.
2. Click 'Analyze Document'.
3. Ask questions in the chat!
""")
# Right Column: Chat Interface
with gr.Column(scale=2):
chatbot = gr.Chatbot(
label="Chat Interface",
height=500,
bubble_full_width=False,
elem_id="chat-window"
)
msg_input = gr.Textbox(
placeholder="Type your question here...",
label=None,
container=False,
lines=1
)
with gr.Row():
submit_btn = gr.Button("Send", variant="primary")
# Footer
gr.HTML("<div id='footer'>Built with Gradio | SPJIMR Branding Applied</div>")
# --- Event Handlers ---
def handle_upload(file):
if file is None:
return "No file selected."
msg = doc_proc.process_document(file)
return msg
process_btn.click(
handle_upload,
inputs=[file_input],
outputs=[status_msg],
show_progress=True
)
submit_btn.click(
chat_response,
inputs=[msg_input, chatbot],
outputs=[chatbot],
show_progress=True
).then(
lambda: "", None, [msg_input]
)
msg_input.submit(
chat_response,
inputs=[msg_input, chatbot],
outputs=[chatbot],
show_progress=True
).then(
lambda: "", None, [msg_input]
)
clear_btn.click(
clear_session,
outputs=[file_input, chatbot, status_msg]
)
if __name__ == "__main__":
demo.launch()
|