File size: 8,882 Bytes
f36c047
 
 
 
7c11c25
f36c047
 
 
 
 
 
7c11c25
 
 
 
 
 
 
 
 
 
 
 
 
 
f36c047
 
 
 
 
 
 
 
 
 
 
 
 
7c11c25
f36c047
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1675639
f36c047
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b2a2d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f36c047
630354a
 
8765505
 
 
 
 
1675639
8765505
 
 
f36c047
 
 
 
8765505
a652cb6
f36c047
 
 
 
 
67c6d15
f36c047
67c6d15
 
 
 
 
f36c047
 
 
 
 
8765505
f36c047
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67c6d15
f36c047
 
 
 
 
 
 
 
 
 
67c6d15
 
 
 
 
 
 
 
f36c047
 
 
 
67c6d15
 
f36c047
 
 
 
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
import streamlit as st
import uuid
import os
import base64
import weakref 
from langchain_community.retrievers import BM25Retriever
from langchain_core.messages import HumanMessage, AIMessage

from data_processing import process_and_ingest, SessionDocStore, cleanup_session_index
from rag_engine import run_advanced_rag

# ──────────────────────────────────────────────────────────────────
# AUTOMATIC CLEANUP HANDLER (The Janitor)
# ──────────────────────────────────────────────────────────────────

class SessionJanitor:
    """
    This object lives in the session state.
    When the session ends (Refresh/Close), this object is destroyed.
    The 'weakref.finalize' automatically calls the cleanup function.
    """
    def __init__(self, session_id):
        self.session_id = session_id
        # Register the cleanup function to run when this object dies
        weakref.finalize(self, cleanup_session_index, session_id)
# ──────────────────────────────────────────────────────────────────
# 1. PAGE CONFIG & STATE INITIALIZATION
# ──────────────────────────────────────────────────────────────────

st.set_page_config(
    page_title="Deep RAG Analyzer",
    page_icon="πŸ€–",
    layout="wide"
)

# Initialize Session State
if "session_id" not in st.session_state:
    st.session_state.session_id = str(uuid.uuid4())
    st.session_state.janitor = SessionJanitor(st.session_state.session_id)

if "doc_store" not in st.session_state:
    st.session_state.doc_store = SessionDocStore()

if "messages" not in st.session_state:
    st.session_state.messages = [] # Format: {"role": "user/assistant", "content": "text", "images": []}

if "bm25" not in st.session_state:
    st.session_state.bm25 = None

if "processed_file" not in st.session_state:
    st.session_state.processed_file = None
    
if "uploader_key" not in st.session_state:
    st.session_state.uploader_key = str(uuid.uuid4())

# ──────────────────────────────────────────────────────────────────
# 2. SIDEBAR (Upload & Reset)
# ──────────────────────────────────────────────────────────────────

with st.sidebar:
    st.title("πŸ“ Document Upload")
    uploaded_file = st.file_uploader("Upload PDF", type=["pdf"],key=st.session_state.uploader_key)
    
    if uploaded_file and uploaded_file.name != st.session_state.processed_file:
        with st.spinner("Partitioning & Embedding Generation (This may take around few seconds to few minutes depending upon file size & content)..."):
            # Save to temp file for processing
            temp_path = f"temp_{uploaded_file.name}"
            with open(temp_path, "wb") as f:
                f.write(uploaded_file.getbuffer())
                
            try:
                # Run Pipeline
                documents = process_and_ingest(
                    temp_path, 
                    st.session_state.session_id, 
                    st.session_state.doc_store
                )
                
                # Setup Retriever
                bm25 = BM25Retriever.from_documents(documents)
                bm25.k = 3
                st.session_state.bm25 = bm25
                st.session_state.processed_file = uploaded_file.name
                
                st.success(f"Processed {len(documents)} chunks!")
            except Exception as e:
                st.error(f"Error: {e}")
            finally:
                # Cleanup temp file
                if os.path.exists(temp_path):
                    os.remove(temp_path)

    st.markdown("---")
    if st.button("πŸ—‘οΈ Clear Chat & Reset"):
        try:
            # 1. Attempt Cleanup with visual feedback
            with st.spinner(f"Deleting vector data for {st.session_state.session_id}..."):
                cleanup_session_index(st.session_state.session_id)
            
            # 2. Reset Local State (Only if cleanup succeeded)
            st.session_state.session_id = str(uuid.uuid4())
            st.session_state.doc_store = SessionDocStore()
            st.session_state.messages = []
            st.session_state.bm25 = None
            st.session_state.processed_file = None
            st.session_state.uploader_key = str(uuid.uuid4())
            
            # 3. Re-attach Janitor for new session
            st.session_state.janitor = SessionJanitor(st.session_state.session_id)
            
            st.success("Cache cleared successfully!")
            st.rerun()
            
        except Exception as e:
            st.error(f"Cleanup failed! Check Pinecone Console.\nError: {e}")

    st.markdown("### ℹ️ How to Use")
    st.info(
        """
        1. **Upload a Single PDF only and only a single file type. Multiple PDF's are not accepted**.
        2. Wait for the **"Processed"** success message.
        3. Ask questions in the chat.
        4. The system uses **Hybrid Search** (Keyword + Vector) and **Re-ranking** for accuracy.
        5. Always Click on **Clear Chat** to start a fresh session with a new pdf.
        """
    )

# ──────────────────────────────────────────────────────────────────
# 3. CHAT INTERFACE
# ──────────────────────────────────────────────────────────────────

st.title("πŸ€– Advanced Multimodal RAG (with chat history)")
st.caption("Using GPT-4.1, IntFloat-e5-basev2 as Embedding model and Pinecone as Vector Database (Do not upload extremly large PDFs!)")

# Display History
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])
        #if "images" in msg and msg["images"]:
            # Display images in a row
            #cols = st.columns(len(msg["images"]))
            #for idx, img_b64 in enumerate(msg["images"]):
                #with cols[idx]:
                    #if "," in img_b64: img_b64 = img_b64.split(",")[1]
                    #st.image(base64.b64decode(img_b64), use_container_width=True)

# Chat Input
if prompt := st.chat_input("Ask about your document..."):
    
    if not st.session_state.bm25:
        st.error("Please upload and process a PDF first !")
        st.stop()

    # 1. Display User Message
    st.chat_message("user").markdown(prompt)
    st.session_state.messages.append({"role": "user", "content": prompt})
    
    # 2. Prepare History for RAG
    lc_history = []
    for m in st.session_state.messages:
        if m["role"] == "user":
            lc_history.append(HumanMessage(content=m["content"]))
        else:
            lc_history.append(AIMessage(content=m["content"]))

    # 3. Generate Response
    with st.chat_message("assistant"):
        with st.spinner("Thinking (retrieving the context)..."):
            try:
                answer = run_advanced_rag(
                    prompt,
                    st.session_state.session_id,
                    st.session_state.bm25,
                    st.session_state.doc_store,
                    lc_history
                )
                
                st.markdown(answer)
                
                # Display Images if found
                #if images:
                    #st.write("---")
                    #st.caption("πŸ“Έ Retrieved Visual Context:")
                    #cols = st.columns(min(3, len(images)))
                    #for idx, img_b64 in enumerate(images[:3]):
                        #with cols[idx]:
                            #if "," in img_b64: img_b64 = img_b64.split(",")[1]
                            #st.image(base64.b64decode(img_b64), use_container_width=True)
                
                # Save to history
                st.session_state.messages.append({
                    "role": "assistant", 
                    "content": answer
                    #"images": images[:3] if images else []
                })
                
            except Exception as e:
                st.error(f"Error generating response: {e}")