Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -35,7 +35,7 @@ st.set_page_config(page_title="PDF Assistant", page_icon="π", layout="wide")
|
|
| 35 |
# ---------------- CSS ----------------
|
| 36 |
st.markdown("""
|
| 37 |
<style>
|
| 38 |
-
/* 1. SCROLL FIX FOR SIDEBAR
|
| 39 |
[data-testid="stSidebar"] {
|
| 40 |
position: fixed;
|
| 41 |
overflow-y: hidden !important;
|
|
@@ -43,13 +43,29 @@ st.markdown("""
|
|
| 43 |
z-index: 99;
|
| 44 |
}
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
.main .block-container {
|
| 47 |
-
|
| 48 |
-
height: 100vh;
|
| 49 |
padding-bottom: 5rem;
|
|
|
|
| 50 |
}
|
| 51 |
|
| 52 |
-
/*
|
| 53 |
[data-testid="stSidebar"] .stButton button {
|
| 54 |
width: 100%;
|
| 55 |
border-radius: 8px;
|
|
@@ -57,25 +73,12 @@ st.markdown("""
|
|
| 57 |
margin-bottom: 6px;
|
| 58 |
}
|
| 59 |
|
| 60 |
-
/*
|
| 61 |
-
/* This specifically hides the list of file names that appears below the browse button */
|
| 62 |
section[data-testid="stFileUploader"] ul {
|
| 63 |
display: none;
|
| 64 |
}
|
| 65 |
-
/* Also hide the small 'Limit 200MB' text if desired, optional:
|
| 66 |
-
section[data-testid="stFileUploader"] small {
|
| 67 |
-
display: none;
|
| 68 |
-
}
|
| 69 |
-
*/
|
| 70 |
-
|
| 71 |
-
/* 4. CHAT & UI STYLING */
|
| 72 |
-
:root {
|
| 73 |
-
--primary-color: #1e3a8a;
|
| 74 |
-
--background-color: #0e1117;
|
| 75 |
-
--secondary-background-color: #1a1d29;
|
| 76 |
-
--text-color: #f0f2f6;
|
| 77 |
-
}
|
| 78 |
|
|
|
|
| 79 |
.chat-user {
|
| 80 |
background: #2d3748;
|
| 81 |
padding: 12px;
|
|
@@ -83,10 +86,10 @@ section[data-testid="stFileUploader"] small {
|
|
| 83 |
margin: 6px 0 6px auto;
|
| 84 |
max-width: 85%;
|
| 85 |
text-align: right;
|
| 86 |
-
color:
|
| 87 |
}
|
| 88 |
.chat-bot {
|
| 89 |
-
background:
|
| 90 |
padding: 12px;
|
| 91 |
border-radius: 10px 10px 10px 2px;
|
| 92 |
margin: 6px auto 6px 0;
|
|
@@ -94,7 +97,6 @@ section[data-testid="stFileUploader"] small {
|
|
| 94 |
text-align: left;
|
| 95 |
color: #ffffff;
|
| 96 |
}
|
| 97 |
-
|
| 98 |
.sources {
|
| 99 |
font-size: 0.8em;
|
| 100 |
opacity: 0.7;
|
|
@@ -103,20 +105,15 @@ section[data-testid="stFileUploader"] small {
|
|
| 103 |
padding-top: 5px;
|
| 104 |
}
|
| 105 |
|
| 106 |
-
/*
|
| 107 |
-
.title-container {
|
| 108 |
-
text-align: center;
|
| 109 |
-
margin-bottom: 20px;
|
| 110 |
-
}
|
| 111 |
.title-text {
|
| 112 |
-
font-size:
|
| 113 |
font-weight: 800;
|
| 114 |
margin: 0;
|
| 115 |
}
|
| 116 |
.creator-text {
|
| 117 |
-
font-size:
|
| 118 |
font-weight: 500;
|
| 119 |
-
margin-top: -5px;
|
| 120 |
color: #cccccc;
|
| 121 |
}
|
| 122 |
.creator-text a {
|
|
@@ -124,7 +121,7 @@ section[data-testid="stFileUploader"] small {
|
|
| 124 |
text-decoration: none;
|
| 125 |
}
|
| 126 |
|
| 127 |
-
/*
|
| 128 |
[data-testid="stForm"] {
|
| 129 |
border: none;
|
| 130 |
padding: 0;
|
|
@@ -132,6 +129,17 @@ section[data-testid="stFileUploader"] small {
|
|
| 132 |
</style>
|
| 133 |
""", unsafe_allow_html=True)
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
# ---------------- SESSION STATE ----------------
|
| 136 |
if "chat" not in st.session_state:
|
| 137 |
st.session_state.chat = []
|
|
@@ -162,7 +170,6 @@ def clear_memory():
|
|
| 162 |
torch.cuda.empty_cache()
|
| 163 |
|
| 164 |
def process_pdf(uploaded_file):
|
| 165 |
-
"""Process uploaded PDF and create vectorstore."""
|
| 166 |
try:
|
| 167 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
|
| 168 |
tmp.write(uploaded_file.getvalue())
|
|
@@ -171,10 +178,7 @@ def process_pdf(uploaded_file):
|
|
| 171 |
loader = PyPDFLoader(path)
|
| 172 |
docs = loader.load()
|
| 173 |
|
| 174 |
-
splitter = RecursiveCharacterTextSplitter(
|
| 175 |
-
chunk_size=800,
|
| 176 |
-
chunk_overlap=50
|
| 177 |
-
)
|
| 178 |
chunks = splitter.split_documents(docs)
|
| 179 |
|
| 180 |
embeddings = HuggingFaceEmbeddings(
|
|
@@ -193,18 +197,15 @@ def process_pdf(uploaded_file):
|
|
| 193 |
os.unlink(path)
|
| 194 |
|
| 195 |
return len(chunks)
|
| 196 |
-
|
| 197 |
except Exception as e:
|
| 198 |
st.error(f"Error processing PDF: {str(e)}")
|
| 199 |
return None
|
| 200 |
|
| 201 |
def ask_question(question):
|
| 202 |
-
"""Retrieve and generate answer for the question."""
|
| 203 |
if not client:
|
| 204 |
-
return None, 0, "Groq client is not initialized.
|
| 205 |
-
|
| 206 |
if not st.session_state.retriever:
|
| 207 |
-
return None, 0, "Upload PDF first
|
| 208 |
|
| 209 |
try:
|
| 210 |
docs = st.session_state.retriever.invoke(question)
|
|
@@ -212,87 +213,52 @@ def ask_question(question):
|
|
| 212 |
|
| 213 |
prompt = f"""
|
| 214 |
You are a strict RAG Q&A assistant.
|
| 215 |
-
Use ONLY the context provided. If the answer is not found, reply:
|
| 216 |
-
"I cannot find this in the PDF."
|
| 217 |
-
|
| 218 |
---------------- CONTEXT ----------------
|
| 219 |
{context}
|
| 220 |
-----------------------------------------
|
| 221 |
-
|
| 222 |
QUESTION: {question}
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
"""
|
| 226 |
response = client.chat.completions.create(
|
| 227 |
model=GROQ_MODEL,
|
| 228 |
messages=[
|
| 229 |
-
{"role": "system",
|
| 230 |
-
"content": "Use only the PDF content. If answer not found, say: 'I cannot find this in the PDF.'"},
|
| 231 |
{"role": "user", "content": prompt}
|
| 232 |
],
|
| 233 |
temperature=0.0
|
| 234 |
)
|
| 235 |
-
|
| 236 |
-
answer = response.choices[0].message.content.strip()
|
| 237 |
-
return answer, len(docs), None
|
| 238 |
-
|
| 239 |
-
except APIError as e:
|
| 240 |
-
return None, 0, f"Groq API Error: {str(e)}"
|
| 241 |
except Exception as e:
|
| 242 |
-
return None, 0, f"
|
| 243 |
-
|
| 244 |
-
# ---------------- CENTERED TITLE ----------------
|
| 245 |
-
st.markdown("""
|
| 246 |
-
<div class="title-container">
|
| 247 |
-
<div class="title-text">π PDF Assistant</div>
|
| 248 |
-
<div class="creator-text">
|
| 249 |
-
Created by <a href="https://www.linkedin.com/in/abhishek-iitr/" target="_blank">Abhishek Saxena</a>
|
| 250 |
-
</div>
|
| 251 |
-
</div>
|
| 252 |
-
""", unsafe_allow_html=True)
|
| 253 |
|
| 254 |
# ---------------- SIDEBAR ----------------
|
| 255 |
with st.sidebar:
|
| 256 |
st.write("")
|
| 257 |
-
|
| 258 |
-
# Action Buttons
|
| 259 |
if st.button("ποΈ Clear Chat History", use_container_width=True):
|
| 260 |
clear_chat_history()
|
| 261 |
-
|
| 262 |
if st.button("π₯ Clear PDF Memory", on_click=clear_memory, use_container_width=True):
|
| 263 |
st.success("Memory Cleared!")
|
| 264 |
|
| 265 |
st.markdown("---")
|
| 266 |
|
| 267 |
-
# Dynamic Label based on state
|
| 268 |
-
# If a file is loaded, show a success icon/text. If not, show "Upload PDF".
|
| 269 |
upload_label = "β
PDF Uploaded!" if st.session_state.uploaded_file_name else "Upload PDF"
|
| 270 |
-
|
| 271 |
uploaded = st.file_uploader(
|
| 272 |
-
upload_label,
|
| 273 |
-
type=["pdf"],
|
| 274 |
-
key=st.session_state.uploader_key,
|
| 275 |
-
label_visibility="collapsed" # Still hiding the main label text
|
| 276 |
)
|
| 277 |
|
| 278 |
-
# Status Message logic
|
| 279 |
if uploaded:
|
| 280 |
if uploaded.name != st.session_state.uploaded_file_name:
|
| 281 |
-
# Processing
|
| 282 |
st.session_state.uploaded_file_name = None
|
| 283 |
st.session_state.chat = []
|
| 284 |
-
|
| 285 |
with st.spinner(f"Processing '{uploaded.name}'..."):
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
if chunks_count is not None:
|
| 289 |
st.session_state.uploaded_file_name = uploaded.name
|
| 290 |
-
st.success(f"β
Processed! {
|
| 291 |
else:
|
| 292 |
st.error("β Failed.")
|
| 293 |
else:
|
| 294 |
-
# Active State
|
| 295 |
-
# This appears BELOW the uploader box
|
| 296 |
st.success(f"π **Ready:** `{uploaded.name}`")
|
| 297 |
else:
|
| 298 |
st.warning("β¬οΈ Upload a PDF to start chatting!")
|
|
@@ -300,9 +266,9 @@ with st.sidebar:
|
|
| 300 |
# ---------------- INPUT AREA ----------------
|
| 301 |
disabled_input = st.session_state.uploaded_file_name is None or client is None
|
| 302 |
|
|
|
|
| 303 |
with st.form(key='chat_form', clear_on_submit=True):
|
| 304 |
col_input, col_btn = st.columns([0.85, 0.15], gap="small")
|
| 305 |
-
|
| 306 |
with col_input:
|
| 307 |
user_question = st.text_input(
|
| 308 |
"Ask a question",
|
|
@@ -310,27 +276,26 @@ with st.form(key='chat_form', clear_on_submit=True):
|
|
| 310 |
label_visibility="collapsed",
|
| 311 |
disabled=disabled_input
|
| 312 |
)
|
| 313 |
-
|
| 314 |
with col_btn:
|
| 315 |
submit_btn = st.form_submit_button("β€", disabled=disabled_input, use_container_width=True)
|
| 316 |
|
| 317 |
-
# Processing the Input
|
| 318 |
if submit_btn and user_question:
|
| 319 |
st.session_state.chat.append(("user", user_question))
|
| 320 |
-
|
| 321 |
with st.spinner("Thinking..."):
|
| 322 |
answer, sources, error = ask_question(user_question)
|
| 323 |
-
|
| 324 |
if answer:
|
| 325 |
-
|
| 326 |
-
st.session_state.chat.append(("bot",
|
| 327 |
else:
|
| 328 |
st.session_state.chat.append(("bot", f"π΄ **Error:** {error}"))
|
|
|
|
| 329 |
|
| 330 |
-
# ---------------- CHAT HISTORY
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
|
|
|
|
|
|
|
|
| 35 |
# ---------------- CSS ----------------
|
| 36 |
st.markdown("""
|
| 37 |
<style>
|
| 38 |
+
/* 1. SCROLL FIX FOR SIDEBAR */
|
| 39 |
[data-testid="stSidebar"] {
|
| 40 |
position: fixed;
|
| 41 |
overflow-y: hidden !important;
|
|
|
|
| 43 |
z-index: 99;
|
| 44 |
}
|
| 45 |
|
| 46 |
+
/* 2. FIXED HEADER STYLING */
|
| 47 |
+
/* This pins the title to the top and adds a background so chat flows under it */
|
| 48 |
+
.fixed-header {
|
| 49 |
+
position: fixed;
|
| 50 |
+
top: 0;
|
| 51 |
+
left: 0;
|
| 52 |
+
width: 100%;
|
| 53 |
+
background-color: #0e1117; /* Match app background */
|
| 54 |
+
z-index: 98; /* Below sidebar, above content */
|
| 55 |
+
padding-top: 1rem;
|
| 56 |
+
padding-bottom: 1rem;
|
| 57 |
+
text-align: center;
|
| 58 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/* 3. ADJUST MAIN CONTENT TO ACCOUNT FOR FIXED HEADER */
|
| 62 |
.main .block-container {
|
| 63 |
+
margin-top: 6rem; /* Push content down so it doesn't hide behind header */
|
|
|
|
| 64 |
padding-bottom: 5rem;
|
| 65 |
+
overflow-y: auto; /* Allow scrolling */
|
| 66 |
}
|
| 67 |
|
| 68 |
+
/* 4. SIDEBAR BUTTON STYLING */
|
| 69 |
[data-testid="stSidebar"] .stButton button {
|
| 70 |
width: 100%;
|
| 71 |
border-radius: 8px;
|
|
|
|
| 73 |
margin-bottom: 6px;
|
| 74 |
}
|
| 75 |
|
| 76 |
+
/* 5. HIDE UPLOADED FILE LIST */
|
|
|
|
| 77 |
section[data-testid="stFileUploader"] ul {
|
| 78 |
display: none;
|
| 79 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
/* 6. CHAT BUBBLES */
|
| 82 |
.chat-user {
|
| 83 |
background: #2d3748;
|
| 84 |
padding: 12px;
|
|
|
|
| 86 |
margin: 6px 0 6px auto;
|
| 87 |
max-width: 85%;
|
| 88 |
text-align: right;
|
| 89 |
+
color: #f0f2f6;
|
| 90 |
}
|
| 91 |
.chat-bot {
|
| 92 |
+
background: #1e3a8a;
|
| 93 |
padding: 12px;
|
| 94 |
border-radius: 10px 10px 10px 2px;
|
| 95 |
margin: 6px auto 6px 0;
|
|
|
|
| 97 |
text-align: left;
|
| 98 |
color: #ffffff;
|
| 99 |
}
|
|
|
|
| 100 |
.sources {
|
| 101 |
font-size: 0.8em;
|
| 102 |
opacity: 0.7;
|
|
|
|
| 105 |
padding-top: 5px;
|
| 106 |
}
|
| 107 |
|
| 108 |
+
/* 7. TITLE TEXT */
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
.title-text {
|
| 110 |
+
font-size: 2.5rem;
|
| 111 |
font-weight: 800;
|
| 112 |
margin: 0;
|
| 113 |
}
|
| 114 |
.creator-text {
|
| 115 |
+
font-size: 1rem;
|
| 116 |
font-weight: 500;
|
|
|
|
| 117 |
color: #cccccc;
|
| 118 |
}
|
| 119 |
.creator-text a {
|
|
|
|
| 121 |
text-decoration: none;
|
| 122 |
}
|
| 123 |
|
| 124 |
+
/* 8. INPUT FORM STYLING */
|
| 125 |
[data-testid="stForm"] {
|
| 126 |
border: none;
|
| 127 |
padding: 0;
|
|
|
|
| 129 |
</style>
|
| 130 |
""", unsafe_allow_html=True)
|
| 131 |
|
| 132 |
+
# ---------------- FIXED HEADER ----------------
|
| 133 |
+
st.markdown("""
|
| 134 |
+
<div class="fixed-header">
|
| 135 |
+
<div class="title-text">π PDF Assistant</div>
|
| 136 |
+
<div class="creator-text">
|
| 137 |
+
<a href="https://www.linkedin.com/in/abhishek-iitr/" target="_blank">Abhishek Saxena</a>
|
| 138 |
+
</div>
|
| 139 |
+
</div>
|
| 140 |
+
""", unsafe_allow_html=True)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
# ---------------- SESSION STATE ----------------
|
| 144 |
if "chat" not in st.session_state:
|
| 145 |
st.session_state.chat = []
|
|
|
|
| 170 |
torch.cuda.empty_cache()
|
| 171 |
|
| 172 |
def process_pdf(uploaded_file):
|
|
|
|
| 173 |
try:
|
| 174 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
|
| 175 |
tmp.write(uploaded_file.getvalue())
|
|
|
|
| 178 |
loader = PyPDFLoader(path)
|
| 179 |
docs = loader.load()
|
| 180 |
|
| 181 |
+
splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=50)
|
|
|
|
|
|
|
|
|
|
| 182 |
chunks = splitter.split_documents(docs)
|
| 183 |
|
| 184 |
embeddings = HuggingFaceEmbeddings(
|
|
|
|
| 197 |
os.unlink(path)
|
| 198 |
|
| 199 |
return len(chunks)
|
|
|
|
| 200 |
except Exception as e:
|
| 201 |
st.error(f"Error processing PDF: {str(e)}")
|
| 202 |
return None
|
| 203 |
|
| 204 |
def ask_question(question):
|
|
|
|
| 205 |
if not client:
|
| 206 |
+
return None, 0, "Groq client is not initialized."
|
|
|
|
| 207 |
if not st.session_state.retriever:
|
| 208 |
+
return None, 0, "Upload PDF first."
|
| 209 |
|
| 210 |
try:
|
| 211 |
docs = st.session_state.retriever.invoke(question)
|
|
|
|
| 213 |
|
| 214 |
prompt = f"""
|
| 215 |
You are a strict RAG Q&A assistant.
|
| 216 |
+
Use ONLY the context provided. If the answer is not found, reply: "I cannot find this in the PDF."
|
|
|
|
|
|
|
| 217 |
---------------- CONTEXT ----------------
|
| 218 |
{context}
|
| 219 |
-----------------------------------------
|
|
|
|
| 220 |
QUESTION: {question}
|
| 221 |
+
FINAL ANSWER:"""
|
| 222 |
+
|
|
|
|
| 223 |
response = client.chat.completions.create(
|
| 224 |
model=GROQ_MODEL,
|
| 225 |
messages=[
|
| 226 |
+
{"role": "system", "content": "Use only the PDF content."},
|
|
|
|
| 227 |
{"role": "user", "content": prompt}
|
| 228 |
],
|
| 229 |
temperature=0.0
|
| 230 |
)
|
| 231 |
+
return response.choices[0].message.content.strip(), len(docs), None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
except Exception as e:
|
| 233 |
+
return None, 0, f"Error: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
|
| 235 |
# ---------------- SIDEBAR ----------------
|
| 236 |
with st.sidebar:
|
| 237 |
st.write("")
|
|
|
|
|
|
|
| 238 |
if st.button("ποΈ Clear Chat History", use_container_width=True):
|
| 239 |
clear_chat_history()
|
|
|
|
| 240 |
if st.button("π₯ Clear PDF Memory", on_click=clear_memory, use_container_width=True):
|
| 241 |
st.success("Memory Cleared!")
|
| 242 |
|
| 243 |
st.markdown("---")
|
| 244 |
|
|
|
|
|
|
|
| 245 |
upload_label = "β
PDF Uploaded!" if st.session_state.uploaded_file_name else "Upload PDF"
|
|
|
|
| 246 |
uploaded = st.file_uploader(
|
| 247 |
+
upload_label, type=["pdf"], key=st.session_state.uploader_key, label_visibility="collapsed"
|
|
|
|
|
|
|
|
|
|
| 248 |
)
|
| 249 |
|
|
|
|
| 250 |
if uploaded:
|
| 251 |
if uploaded.name != st.session_state.uploaded_file_name:
|
|
|
|
| 252 |
st.session_state.uploaded_file_name = None
|
| 253 |
st.session_state.chat = []
|
|
|
|
| 254 |
with st.spinner(f"Processing '{uploaded.name}'..."):
|
| 255 |
+
chunks = process_pdf(uploaded)
|
| 256 |
+
if chunks:
|
|
|
|
| 257 |
st.session_state.uploaded_file_name = uploaded.name
|
| 258 |
+
st.success(f"β
Processed! {chunks} chunks.")
|
| 259 |
else:
|
| 260 |
st.error("β Failed.")
|
| 261 |
else:
|
|
|
|
|
|
|
| 262 |
st.success(f"π **Ready:** `{uploaded.name}`")
|
| 263 |
else:
|
| 264 |
st.warning("β¬οΈ Upload a PDF to start chatting!")
|
|
|
|
| 266 |
# ---------------- INPUT AREA ----------------
|
| 267 |
disabled_input = st.session_state.uploaded_file_name is None or client is None
|
| 268 |
|
| 269 |
+
# Input Form
|
| 270 |
with st.form(key='chat_form', clear_on_submit=True):
|
| 271 |
col_input, col_btn = st.columns([0.85, 0.15], gap="small")
|
|
|
|
| 272 |
with col_input:
|
| 273 |
user_question = st.text_input(
|
| 274 |
"Ask a question",
|
|
|
|
| 276 |
label_visibility="collapsed",
|
| 277 |
disabled=disabled_input
|
| 278 |
)
|
|
|
|
| 279 |
with col_btn:
|
| 280 |
submit_btn = st.form_submit_button("β€", disabled=disabled_input, use_container_width=True)
|
| 281 |
|
|
|
|
| 282 |
if submit_btn and user_question:
|
| 283 |
st.session_state.chat.append(("user", user_question))
|
|
|
|
| 284 |
with st.spinner("Thinking..."):
|
| 285 |
answer, sources, error = ask_question(user_question)
|
|
|
|
| 286 |
if answer:
|
| 287 |
+
bot_msg = f"{answer}<div class='sources'>Context Chunks Used: {sources}</div>"
|
| 288 |
+
st.session_state.chat.append(("bot", bot_msg))
|
| 289 |
else:
|
| 290 |
st.session_state.chat.append(("bot", f"π΄ **Error:** {error}"))
|
| 291 |
+
st.rerun() # Rerun to update the display at the top immediately
|
| 292 |
|
| 293 |
+
# ---------------- CHAT HISTORY (REVERSED) ----------------
|
| 294 |
+
# We iterate through the reversed list so the newest message (last in list) comes first.
|
| 295 |
+
if st.session_state.chat:
|
| 296 |
+
st.markdown("---")
|
| 297 |
+
for role, msg in reversed(st.session_state.chat):
|
| 298 |
+
if role == "user":
|
| 299 |
+
st.markdown(f"<div class='chat-user'>{msg}</div>", unsafe_allow_html=True)
|
| 300 |
+
else:
|
| 301 |
+
st.markdown(f"<div class='chat-bot'>{msg}</div>", unsafe_allow_html=True)
|