Spaces:
Build error
Build error
| import os | |
| import re | |
| import streamlit as st | |
| import streamlit.components.v1 as components | |
| from datetime import datetime | |
| from werkzeug.utils import secure_filename | |
| from src.gpp import GPP, GPPConfig | |
| from src.qa import AnswerGenerator | |
| class ContextAwareAnswerGenerator: | |
| def __init__(self, chunks): | |
| self.chunks = chunks | |
| self.original_generator = AnswerGenerator(chunks) | |
| def answer(self, question, conversation_context=None): | |
| if not conversation_context or len(conversation_context) <= 1: | |
| return self.original_generator.answer(question) | |
| context_prompt = "Based on our conversation so far:\n" | |
| max_history = min(len(conversation_context) - 1, 4) | |
| for i in range(max(0, len(conversation_context) - max_history - 1), len(conversation_context) - 1, 2): | |
| user_q = conversation_context[i]["content"] | |
| assistant_a = conversation_context[i+1]["content"] | |
| context_prompt += f"You were asked: '{user_q}'\n" | |
| context_prompt += f"You answered: '{assistant_a}'\n" | |
| context_prompt += f"\nNow answer this follow-up question: {question}" | |
| return self.original_generator.answer(context_prompt) | |
| # --- Page Config --- | |
| st.set_page_config( | |
| page_title="Document Q&A", | |
| page_icon="📄", | |
| layout="wide" | |
| ) | |
| # --- Session State --- | |
| if 'chat_history' not in st.session_state: | |
| st.session_state.chat_history = [] | |
| if 'parsed' not in st.session_state: | |
| st.session_state.parsed = None | |
| if 'selected_chunks' not in st.session_state: | |
| st.session_state.selected_chunks = [] | |
| if 'conversation_context' not in st.session_state: | |
| st.session_state.conversation_context = [] | |
| # --- Global CSS --- | |
| st.markdown(r""" | |
| <style> | |
| body { background-color: #ffffff; font-family: 'Helvetica Neue', sans-serif; } | |
| /* Chat */ | |
| .chat-container { display: flex; flex-direction: column; gap: 12px; margin: 20px 0; } | |
| .chat-message { display: flex; } | |
| .user-message { justify-content: flex-end; } | |
| .assistant-message { justify-content: flex-start; } | |
| .message-content { padding: 12px 16px; border-radius: 18px; max-width: 100%; overflow-wrap: break-word; } | |
| .user-message .message-content { background-color: #4A90E2; color: white; border-bottom-right-radius: 4px; } | |
| .assistant-message .message-content { background-color: #f1f1f1; color: #333; border-bottom-left-radius: 4px; } | |
| /* Input */ | |
| .stTextInput>div>div>input { border-radius: 20px; border: 1px solid #ccc; padding: 8px 12px; } | |
| .stButton>button { background-color: #4A90E2; color: white; border-radius: 20px; padding: 8px 16px; } | |
| .stButton>button:hover { background-color: #357ABD; } | |
| /* Evidence */ | |
| .evidence-content { overflow-wrap: break-word; margin-bottom: 1rem; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # --- Sidebar Upload --- | |
| with st.sidebar: | |
| st.title("Document Intelligence") | |
| st.image("https://img.icons8.com/ios-filled/50/4A90E2/document.png", width=40) | |
| st.caption(f"Last updated: {datetime.now():%Y-%m-%d}") | |
| st.markdown("---") | |
| st.subheader("Upload Document") | |
| uploaded_file = st.file_uploader("Select a PDF", type=["pdf"], help="Upload a PDF to analyze") | |
| if uploaded_file: | |
| filename = secure_filename(uploaded_file.name) | |
| if not re.match(r'^[\w\-. ]+$', filename): | |
| st.error("Invalid file name. Please rename your file.") | |
| else: | |
| if st.button("Parse PDF", use_container_width=True): | |
| output_dir = os.path.join("./parsed", filename) | |
| os.makedirs(output_dir, exist_ok=True) | |
| pdf_path = os.path.join(output_dir, filename) | |
| with open(pdf_path, "wb") as f: | |
| f.write(uploaded_file.getbuffer()) | |
| with st.spinner("Parsing document..."): | |
| try: | |
| gpp = GPP(GPPConfig()) | |
| parsed = gpp.run(pdf_path, output_dir) | |
| st.session_state.parsed = parsed | |
| st.session_state.chat_history.clear() | |
| st.session_state.conversation_context.clear() | |
| st.session_state.selected_chunks.clear() | |
| st.success("Document parsed successfully!") | |
| except Exception as e: | |
| st.error(f"Parsing failed: {e}") | |
| # removed content preview | |
| # --- Main Area --- | |
| main_col, evidence_col = st.columns([3, 1]) | |
| with main_col: | |
| st.title("Document Q&A") | |
| if not st.session_state.parsed: | |
| st.info("👈 Upload and parse a document to start") | |
| else: | |
| parsed = st.session_state.parsed | |
| layout_pdf = parsed.get("layout_pdf") | |
| if layout_pdf and os.path.exists(layout_pdf): | |
| st.subheader("Layout Preview") | |
| components.iframe(layout_pdf, height=300, width=400) | |
| # Chat display | |
| st.markdown("<div class='chat-container'>", unsafe_allow_html=True) | |
| if not st.session_state.chat_history: | |
| st.markdown("<p style='color:#888;'>No messages yet. Start the conversation below.</p>", unsafe_allow_html=True) | |
| else: | |
| for msg in st.session_state.chat_history: | |
| cls = 'user-message' if msg['role']=='user' else 'assistant-message' | |
| st.markdown(f"<div class='chat-message {cls}'><div class='message-content'>{msg['content']}</div></div>", unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Input | |
| question = st.text_input("", key="question_input", placeholder="Type your question...", on_change=None) | |
| col_btn1, col_btn2 = st.columns([4, 1]) | |
| with col_btn1: | |
| submit = st.button("Send", use_container_width=True) | |
| with col_btn2: | |
| clear = st.button("Clear", use_container_width=True) | |
| if clear: | |
| st.session_state.chat_history.clear() | |
| st.session_state.conversation_context.clear() | |
| st.session_state.selected_chunks.clear() | |
| st.experimental_rerun() | |
| if submit and question: | |
| st.session_state.chat_history.append({"role":"user","content":question}) | |
| gen = ContextAwareAnswerGenerator(parsed['chunks']) | |
| answer, chunks = gen.answer(question, conversation_context=st.session_state.chat_history) | |
| st.session_state.chat_history.append({"role":"assistant","content":answer}) | |
| st.session_state.selected_chunks = chunks | |
| with evidence_col: | |
| if st.session_state.parsed: | |
| st.markdown("### Evidence") | |
| if not st.session_state.selected_chunks: | |
| st.info("Evidence appears here after asking a question.") | |
| else: | |
| for i, chunk in enumerate(st.session_state.selected_chunks,1): | |
| with st.expander(f"#{i}", expanded=False): | |
| st.markdown(f"**Type:** {chunk.get('type','')}") | |
| st.markdown(f"<div class='evidence-content'>{chunk.get('narration','')}</div>", unsafe_allow_html=True) | |
| if 'table_structure' in chunk: | |
| st.write(chunk['table_structure']) | |
| for blk in chunk.get('blocks',[]): | |
| if blk.get('type')=='img_path': | |
| img_path = os.path.join(parsed['images_dir'], blk['img_path']) | |
| if os.path.exists(img_path): | |
| st.image(img_path, use_column_width=True) | |