File size: 7,465 Bytes
9cb6cfa
ec6ac20
 
9cb6cfa
ec6ac20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9cb6cfa
ec6ac20
 
 
 
 
 
 
 
 
9cb6cfa
ec6ac20
 
 
 
 
 
9cb6cfa
ec6ac20
9cb6cfa
ec6ac20
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
import streamlit as st
import requests
import os

# Backend URL (change if deployed)
BACKEND_URL = "http://localhost:8000"

st.set_page_config(page_title="PDF Assistant", page_icon="πŸ“˜", layout="wide")

# ---------------- CSS (Dark Theme) ----------------
# FIX: Added CSS for the footer
st.markdown("""
<style>
/* Streamlit standard setup for dark theme adherence */
:root {
    --primary-color: #1e3a8a; /* Blue for highlights */
    --background-color: #0e1117;
    --secondary-background-color: #1a1d29;
    --text-color: #f0f2f6;
}

/* Custom Chat Bubbles */
.chat-user {
    background: #2d3748; /* Dark gray */
    padding: 12px;
    border-radius: 10px 10px 2px 10px; /* Rounded corners for chat bubble */
    margin: 6px 0 6px auto;
    max-width: 85%;
    text-align: right;
    color: var(--text-color);
}
.chat-bot {
    background: var(--primary-color); /* Primary blue */
    padding: 12px;
    border-radius: 10px 10px 10px 2px;
    margin: 6px auto 6px 0;
    max-width: 85%;
    text-align: left;
    color: #ffffff; /* White text for contrast */
}

/* Sources section styling */
.sources {
    font-size: 0.8em;
    opacity: 0.7;
    margin-top: 10px;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    padding-top: 5px;
}

/* Footer styling */
.footer {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    background-color: var(--secondary-background-color);
    color: var(--text-color);
    text-align: center;
    padding: 10px;
    font-size: 0.85em;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer a {
    color: var(--primary-color);
    text-decoration: none;
    font-weight: bold;
}
.footer a:hover {
    text-decoration: underline;
}
</style>
""", unsafe_allow_html=True)

# ---------------- SESSION STATE ----------------
if "chat" not in st.session_state:
    st.session_state.chat = []

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

# Add a key to the file uploader to allow it to be reset.
if "uploader_key" not in st.session_state:
    st.session_state.uploader_key = 0

# FIX 1: Change application name
st.title("πŸ“˜ PDF Assistant")


# ---------------- FUNCTIONS ----------------
def clear_chat_history():
    """Clears the chat history in the session state."""
    st.session_state.chat = []


def clear_memory():
    """Calls the backend endpoint to clear loaded PDF data and resets UI state."""
    res = requests.post(f"{BACKEND_URL}/clear")
    if res.status_code == 200:
        st.session_state.uploaded_file_name = None
        # Increment the key of the file uploader to clear its value
        st.session_state.uploader_key += 1
        st.success("Memory cleared. Please upload a new PDF.")
    else:
        st.error(f"Failed to clear memory: {res.json().get('detail', 'Unknown error')}")
    # Removed st.rerun() to prevent "no-op" warning


# ---------------- SIDEBAR CONTROLS ----------------
with st.sidebar:
    st.header("Controls")
    st.button("πŸ—‘οΈ Clear Chat History", on_click=clear_chat_history, use_container_width=True)
    st.button("πŸ”₯ Clear PDF Memory", on_click=clear_memory, use_container_width=True)

    st.markdown("---")
    if st.session_state.uploaded_file_name:
        st.success(f"βœ… **Active PDF:**\n `{st.session_state.uploaded_file_name}`")
    else:
        st.warning("⬆️ Upload a PDF to start chatting!")

# ---------------- UPLOAD PDF ----------------
# Use the dynamic key for the file uploader.
uploaded = st.file_uploader(
    "Upload your PDF",
    type=["pdf"],
    key=st.session_state.uploader_key # Use the dynamic key
)

# Only process if a file is uploaded AND it's a NEW file
if uploaded and uploaded.name != st.session_state.uploaded_file_name:
    st.session_state.uploaded_file_name = None  # Clear status while processing
    st.session_state.chat = []  # Clear chat for a new document

    with st.spinner(f"Processing '{uploaded.name}'..."):
        try:
            files = {"file": (uploaded.name, uploaded.getvalue(), "application/pdf")}
            res = requests.post(f"{BACKEND_URL}/upload", files=files)

            if res.status_code == 200:
                chunks = res.json().get("chunks", 0)
                st.success(f"PDF processed successfully! {chunks} chunks created.")
                st.session_state.uploaded_file_name = uploaded.name
            else:
                error_msg = res.json().get("detail", "Unknown error during processing.")
                st.error(f"Upload failed: {error_msg}")
                st.session_state.uploaded_file_name = None

        except requests.exceptions.ConnectionError:
            st.error(f"Could not connect to the backend server at {BACKEND_URL}. Ensure it is running.")
            st.session_state.uploaded_file_name = None
        except Exception as e:
            st.error(f"An unexpected error occurred: {e}")
            st.session_state.uploaded_file_name = None

    # Rerun the app to update the UI status immediately
    st.rerun()

# ---------------- CHAT INPUT ----------------
# Disable input field if no PDF is loaded
disabled_input = st.session_state.uploaded_file_name is None
question = st.text_input(
    "Ask a question about the loaded PDF:",
    key="question_input",
    disabled=disabled_input
)

if st.button("Send", disabled=disabled_input) and question:
    # 1. Add user query to chat history
    st.session_state.chat.append(("user", question))

    # 2. Call backend
    with st.spinner("Thinking..."):
        try:
            res = requests.post(f"{BACKEND_URL}/ask", json={"question": question})

            if res.status_code == 200:
                data = res.json()
                answer = data.get("answer", "No answer provided.")
                sources = data.get("sources", 0)

                # Format the bot's response to include source count
                bot_message = f"{answer}<div class='sources'>Context Chunks Used: {sources}</div>"
                st.session_state.chat.append(("bot", bot_message))

            else:
                error_detail = res.json().get("detail", "Error while generating answer.")
                st.session_state.chat.append(("bot", f"πŸ”΄ **Error:** {error_detail}"))

        except requests.exceptions.ConnectionError:
            st.session_state.chat.append(("bot",
                                          f"πŸ”΄ **Error:** Could not connect to the backend server at {BACKEND_URL}. Ensure it is running."))
        except Exception as e:
            st.session_state.chat.append(("bot", f"πŸ”΄ **An unexpected error occurred:** {e}"))

    # Rerun to display the updated chat history
    st.rerun()

# ---------------- SHOW CHAT HISTORY ----------------
st.markdown("## Chat History")
# Reverse the list to show the latest messages at the bottom
for role, msg in st.session_state.chat:
    if role == "user":
        st.markdown(f"<div class='chat-user'>{msg}</div>", unsafe_allow_html=True)
    else:
        # Bot message includes the source count, so use the HTML content
        st.markdown(f"<div class='chat-bot'>{msg}</div>", unsafe_allow_html=True)

# ---------------- FOOTER (Creator Credit) ----------------
# FIX 2: Add creator credit with LinkedIn link
footer_html = """
<div class="footer">
    Created by <a href="https://www.linkedin.com/in/abhishek-iitr/" target="_blank">Abhishek Saxena</a>
</div>
"""
st.markdown(footer_html, unsafe_allow_html=True)

# ---------------- END ----------------