adarsh-maurya commited on
Commit
4289e37
·
verified ·
1 Parent(s): 07d9db9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1007 -103
app.py CHANGED
@@ -1,7 +1,6 @@
1
- import os
2
  import time
 
3
  import streamlit as st
4
- import subprocess
5
  from langchain_community.vectorstores import FAISS
6
  from langchain_community.embeddings import HuggingFaceEmbeddings
7
  from langchain.prompts import PromptTemplate
@@ -9,128 +8,1033 @@ from langchain.memory import ConversationBufferWindowMemory
9
  from langchain.chains import ConversationalRetrievalChain
10
  from langchain_together import Together
11
  from footer import footer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- st.set_page_config(page_title="BharatLAW", layout="centered")
 
 
 
 
 
 
14
 
15
- # Display logo
16
  col1, col2, col3 = st.columns([1, 30, 1])
17
  with col2:
18
  st.image("images/banner.jpg", use_container_width=True)
19
 
20
- # Hide hamburger and default footer
21
- def hide_hamburger_menu():
 
 
 
 
 
 
 
 
 
 
 
 
22
  st.markdown("""
23
- <style>
24
- #MainMenu {visibility: hidden;}
25
- footer {visibility: hidden;}
26
- </style>
27
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- hide_hamburger_menu()
 
 
 
 
 
 
 
 
 
 
30
 
31
- # Session state init
32
- if "messages" not in st.session_state:
33
- st.session_state.messages = []
 
 
34
 
35
- if "memory" not in st.session_state:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  st.session_state.memory = ConversationBufferWindowMemory(k=2, memory_key="chat_history", return_messages=True)
 
 
 
 
37
 
38
- @st.cache_resource
39
- def load_embeddings():
40
- return HuggingFaceEmbeddings(model_name="law-ai/InLegalBERT")
 
 
 
 
 
 
 
41
 
42
- embeddings = load_embeddings()
43
 
44
- # ✅ Ensure FAISS index is built if missing
45
- index_dir = "ipc_embed_db"
46
- index_faiss = os.path.join(index_dir, "index.faiss")
47
- index_pkl = os.path.join(index_dir, "index.pkl")
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- if not (os.path.exists(index_faiss) and os.path.exists(index_pkl)):
50
- st.warning("⚠️ FAISS index files missing. Attempting to create them using `Ingest.py`...")
51
  try:
52
- subprocess.run(["python", "Ingest.py"], check=True)
53
- st.success("✅ FAISS index successfully created.")
54
- except subprocess.CalledProcessError as e:
55
- st.error(f" Failed to create FAISS index.\n\n**Error**: {e}")
56
- st.stop()
57
-
58
- # Load FAISS index
59
- try:
60
- db = FAISS.load_local(index_dir, embeddings, allow_dangerous_deserialization=True)
61
- except Exception as e:
62
- st.error(f" Could not load FAISS index even after attempting to build it.\n\n**Reason**: {str(e)}")
63
- st.stop()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
- db_retriever = db.as_retriever(search_type="similarity", search_kwargs={"k": 3})
 
 
66
 
67
- # Prompt template for QA
68
- prompt_template = """
 
 
 
 
 
 
69
  <s>[INST]
70
- As a legal chatbot specializing in the Indian Penal Code, you are tasked with providing highly accurate and contextually appropriate responses...
71
- CONTEXT: {context}
72
- CHAT HISTORY: {chat_history}
73
- QUESTION: {question}
74
- ANSWER:
75
- - [Detail the first key aspect of the law, ensuring it reflects general application]
76
- - [Provide a concise explanation of how the law is typically interpreted or applied]
77
- - [Correct a common misconception or clarify a frequently misunderstood aspect]
78
- - [Detail any exceptions to the general rule, if applicable]
79
- - [Include any additional relevant information that directly relates to the user's query]
80
- </s>[INST]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  """
 
82
 
83
- prompt = PromptTemplate(template=prompt_template, input_variables=['context', 'question', 'chat_history'])
 
 
 
 
 
 
84
 
85
- api_key = os.getenv('TOGETHER_API_KEY')
86
- if not api_key:
87
- st.error("API key for Together is missing. Please set the TOGETHER_API_KEY environment variable.")
 
88
 
89
- llm = Together(model="mistralai/Mixtral-8x22B-Instruct-v0.1", temperature=0.5, max_tokens=1024, together_api_key=api_key)
90
- qa = ConversationalRetrievalChain.from_llm(llm=llm, memory=st.session_state.memory, retriever=db_retriever, combine_docs_chain_kwargs={'prompt': prompt})
91
 
92
- # Helper functions
93
- def extract_answer(full_response):
94
- try:
95
- answer_start = full_response.find("Response:")
96
- if answer_start != -1:
97
- answer_start += len("Response:")
98
- return full_response[answer_start:].strip()
99
- return full_response.strip()
100
- except Exception as e:
101
- return f"Error extracting answer: {str(e)}"
102
-
103
- def reset_conversation():
104
- st.session_state.messages = []
105
- st.session_state.memory.clear()
106
-
107
- # Chat Interface
108
- for message in st.session_state.messages:
109
- with st.chat_message(message["role"]):
110
- st.write(message["content"])
111
-
112
- input_prompt = st.chat_input("Say something...")
113
- if input_prompt:
114
- with st.chat_message("user"):
115
- st.markdown(f"**You:** {input_prompt}")
116
- st.session_state.messages.append({"role": "user", "content": input_prompt})
117
-
118
- with st.chat_message("assistant"):
119
- with st.spinner("Thinking 💡..."):
120
- result = qa.invoke(input=input_prompt)
121
- message_placeholder = st.empty()
122
- answer = extract_answer(result["answer"])
123
-
124
- full_response = "⚠️ **_Gentle reminder: We generally ensure precise information, but do double-check._** \n\n\n"
125
- for chunk in answer:
126
- full_response += chunk
127
- time.sleep(0.006)
128
- message_placeholder.markdown(full_response + " |", unsafe_allow_html=True)
129
-
130
- st.session_state.messages.append({"role": "assistant", "content": answer})
131
-
132
- if st.button('🗑️ Reset All Chat', on_click=reset_conversation):
133
- st.experimental_rerun()
134
-
135
- # Footer styling
136
- footer()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import time
2
+ import os
3
  import streamlit as st
 
4
  from langchain_community.vectorstores import FAISS
5
  from langchain_community.embeddings import HuggingFaceEmbeddings
6
  from langchain.prompts import PromptTemplate
 
8
  from langchain.chains import ConversationalRetrievalChain
9
  from langchain_together import Together
10
  from footer import footer
11
+ from firebase_config import firebaseConfig
12
+ import pyrebase
13
+ # Import firebase_admin and credentials
14
+ import firebase_admin
15
+ from firebase_admin import credentials, initialize_app, db as firebase_db
16
+
17
+ # Add at the top of your file
18
+ import streamlit.components.v1 as components
19
+ import hashlib
20
+ import urllib.parse
21
+ from deep_translator import GoogleTranslator
22
+ from langdetect import detect
23
+ import textwrap
24
+ from transformers import pipeline
25
+ import soundfile as sf
26
+ import numpy as np
27
+ from io import BytesIO
28
+ import tempfile
29
+
30
+ from langchain_together import Together
31
+ from langchain.chains import ConversationalRetrievalChain
32
+ from langchain.memory import ConversationBufferWindowMemory
33
+
34
+
35
+ from requests_toolbelt._compat import gaecontrib
36
+ import json
37
+ import tempfile
38
+ import time
39
+ import requests
40
+ from dotenv import load_dotenv
41
+ import io
42
+ import base64
43
+
44
+ from datetime import datetime, timedelta
45
+ from dateutil.parser import parse
46
+ from langchain.prompts import ChatPromptTemplate
47
+ from langchain_core.output_parsers import StrOutputParser
48
+ from langchain_core.runnables import RunnablePassthrough
49
+ from langchain_together import Together
50
+ import pyrebase
51
+ import re
52
+ from langdetect import detect
53
+ from deep_translator import GoogleTranslator
54
+
55
+ load_dotenv()
56
+ llm = Together(
57
+ model="mistralai/Mixtral-8x7B-Instruct-v0.1",
58
+ temperature=0.7,
59
+ max_tokens=1024,
60
+ together_api_key=os.getenv('TOGETHER_API_KEY')
61
+ )
62
+
63
+
64
+ # Load environment variables
65
+ load_dotenv()
66
+ SPEECHMATICS_API_KEY = os.getenv('SPEECHMATICS_API_KEY')
67
+ api_key = os.getenv('TOGETHER_API_KEY') # Set this in your environment
68
+ if not api_key:
69
+ st.error("Please set TOGETHER_API_KEY environment variable")
70
+ st.stop()
71
+
72
+
73
+ # Function to translate text
74
+ def translate_text(text, target_language):
75
+ try:
76
+ # Use GoogleTranslator from deep-translator
77
+ translated_text = GoogleTranslator(source='auto', target=target_language).translate(text)
78
+ return translated_text
79
+ except Exception as e:
80
+ return f"⚠ Translation failed: {str(e)}"
81
+
82
+
83
+ supported_languages = {
84
+ "hindi": "hi",
85
+ "english": "en",
86
+ "hinglish": "hi" # special handling below
87
+ }
88
+
89
+ devanagari_regex = re.compile(r'[\u0900-\u097F]+')
90
+
91
+
92
+ def detect_target_language(prompt):
93
+ """Detect if the response should be in Hindi, Hinglish, or English only."""
94
+ prompt_lower = prompt.lower().strip()
95
+
96
+ # Block Kannada and other unsupported scripts
97
+ if re.search(r'[\u0C80-\u0CFF]', prompt): # Kannada unicode block
98
+ return "hindi"
99
+
100
+ # Check if explicitly mentioned like: 'in hindi'
101
+ for lang_name, lang_code in supported_languages.items():
102
+ if f"in {lang_name}" in prompt_lower:
103
+ return lang_name
104
+
105
+ # If it contains Devanagari, assume Hindi
106
+ if devanagari_regex.search(prompt):
107
+ return "hindi"
108
+
109
+ # Detect Hinglish by common Hindi terms in Latin script
110
+ if re.search(r'\bdhara\b|\bkanoon\b|\bnyay\b', prompt_lower) and detect(prompt) == 'en':
111
+ return "hinglish"
112
+
113
+ try:
114
+ detected = detect(prompt)
115
+ for name, code in supported_languages.items():
116
+ if code == detected:
117
+ return name
118
+ except:
119
+ pass
120
+
121
+ return "english"
122
+
123
+
124
+ # Initialize the translator
125
+ #translator = Translator()
126
+
127
+ # Initialize Firebase (you would call this at the start of your app)
128
+ def initialize_firebase():
129
+ try:
130
+ if not firebase_admin._apps:
131
+ cred = credentials.Certificate("apna-lawyer-firebase-adminsdk-fbsvc-e3bf4df175.json")
132
+ firebase_app = initialize_app(cred, {
133
+ 'databaseURL': firebaseConfig['databaseURL']
134
+ })
135
+ except Exception as e:
136
+ st.error(f"Firebase initialization error: {str(e)}")
137
 
138
+ # ----------------- Firebase Init -------------------
139
+ firebase = pyrebase.initialize_app(firebaseConfig)
140
+ auth = firebase.auth()
141
+ initialize_firebase()
142
+
143
+ # ----------------- Streamlit Config -------------------
144
+ st.set_page_config(page_title="ApnaLawyer", layout="centered")
145
 
 
146
  col1, col2, col3 = st.columns([1, 30, 1])
147
  with col2:
148
  st.image("images/banner.jpg", use_container_width=True)
149
 
150
+ st.markdown("""
151
+ <style>
152
+ #MainMenu {visibility: hidden;}
153
+ footer {visibility: hidden;}
154
+ </style>
155
+ """, unsafe_allow_html=True)
156
+
157
+ # ----------------- Login/Signup Interface -------------------
158
+ import json
159
+ import requests
160
+ from streamlit.components.v1 import html
161
+
162
+ # Custom CSS for the enhanced UI
163
+ def inject_custom_css():
164
  st.markdown("""
165
+ <style>
166
+ /* Main container styles */
167
+ .stApp {
168
+ background: radial-gradient(circle at top left, #0f0f0f, #050505);
169
+ color: white;
170
+ }
171
+
172
+ /* Sidebar styles */
173
+ [data-testid="stSidebar"] {
174
+ background: rgba(255, 255, 255, 0.05) !important;
175
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
176
+ backdrop-filter: blur(12px) !important;
177
+ box-shadow: 0 0 30px rgba(0, 255, 255, 0.2) !important;
178
+ border-radius: 20px !important;
179
+ padding: 30px !important;
180
+ margin: 20px !important;
181
+ animation: fadeIn 1s ease !important;
182
+ }
183
+
184
+ @keyframes fadeIn {
185
+ from { opacity: 0; transform: translateY(20px); }
186
+ to { opacity: 1; transform: translateY(0); }
187
+ }
188
+
189
+ /* Input field styles */
190
+ .stTextInput>div>div>input, .stPassword>div>div>input {
191
+ background: rgba(255, 255, 255, 0.1) !important;
192
+ color: white !important;
193
+ border: 1px solid transparent !important;
194
+ border-radius: 10px !important;
195
+ padding: 12px 20px !important;
196
+ transition: all 0.3s ease !important;
197
+ }
198
+
199
+ .stTextInput>div>div>input:focus, .stPassword>div>div>input:focus {
200
+ border-color: #00ffff !important;
201
+ background: rgba(255, 255, 255, 0.15) !important;
202
+ box-shadow: 0 0 10px rgba(0, 255, 255, 0.2) !important;
203
+ outline: none !important;
204
+ }
205
+
206
+ /* Button styles */
207
+ .stButton>button {
208
+ width: 100% !important;
209
+ padding: 12px 20px !important;
210
+ border-radius: 10px !important;
211
+ background: linear-gradient(45deg, #00ffff, #007fff) !important;
212
+ color: black !important;
213
+ font-weight: 600 !important;
214
+ border: none !important;
215
+ transition: all 0.3s ease !important;
216
+ box-shadow: 0 4px 15px rgba(0, 255, 255, 0.3) !important;
217
+ }
218
+
219
+ .stButton>button:hover {
220
+ transform: translateY(-2px) !important;
221
+ box-shadow: 0 6px 20px rgba(0, 255, 255, 0.4) !important;
222
+ }
223
+
224
+ /* Radio button styles */
225
+ .stRadio>div {
226
+ flex-direction: column !important;
227
+ gap: 10px !important;
228
+ }
229
+
230
+ .stRadio>div>label {
231
+ color: white !important;
232
+ font-weight: 500 !important;
233
+ padding: 8px 12px !important;
234
+ border-radius: 8px !important;
235
+ background: rgba(255, 255, 255, 0.05) !important;
236
+ transition: all 0.3s ease !important;
237
+ }
238
+
239
+ .stRadio>div>label:hover {
240
+ background: rgba(255, 255, 255, 0.1) !important;
241
+ }
242
+
243
+ .stRadio>div>label[data-baseweb="radio"]>div:first-child {
244
+ border-color: #00ffff !important;
245
+ }
246
+
247
+ /* Link styles */
248
+ a {
249
+ color: #00ffff !important;
250
+ text-decoration: none !important;
251
+ transition: all 0.3s ease !important;
252
+ }
253
+
254
+ a:hover {
255
+ text-shadow: 0 0 10px rgba(0, 255, 255, 0.5) !important;
256
+ }
257
+
258
+ /* Error message styles */
259
+ .stAlert {
260
+ border-radius: 10px !important;
261
+ background: rgba(255, 0, 0, 0.1) !important;
262
+ border: 1px solid rgba(255, 0, 0, 0.2) !important;
263
+ }
264
+
265
+ /* Success message styles */
266
+ .stSuccess {
267
+ border-radius: 10px !important;
268
+ background: rgba(0, 255, 0, 0.1) !important;
269
+ border: 1px solid rgba(0, 255, 0, 0.2) !important;
270
+ }
271
+ /* Audio recording animation */
272
+ @keyframes pulse {
273
+ 0% { transform: scale(1); }
274
+ 50% { transform: scale(1.1); }
275
+ 100% { transform: scale(1); }
276
+ }
277
+ .recording-active {
278
+ animation: pulse 1.5s infinite;
279
+ color: #ff4b4b !important;
280
+ }
281
+ </style>
282
+ """, unsafe_allow_html=True)
283
+
284
+ def store_user_data(user_id, name, email):
285
+ try:
286
+ ref = firebase_db.reference(f'users/{user_id}')
287
+ ref.set({
288
+ 'name': name,
289
+ 'email': email,
290
+ 'created_at': time.time()
291
+ })
292
+ return True
293
+ except Exception as e:
294
+ st.error(f"Error storing user data: {str(e)}")
295
+ return False
296
+
297
+ # Add these Firebase functions right after your existing Firebase functions
298
+ def save_chat_to_history(user_id, chat_title, messages):
299
+ try:
300
+ ref = firebase_db.reference(f'users/{user_id}/chats')
301
+ new_chat_ref = ref.push()
302
+ new_chat_ref.set({
303
+ 'title': chat_title,
304
+ 'messages': messages,
305
+ 'timestamp': time.time(),
306
+ 'last_updated': time.time()
307
+ })
308
+ return new_chat_ref.key
309
+ except Exception as e:
310
+ st.error(f"Error saving chat history: {str(e)}")
311
+ return None
312
+
313
+ def update_chat_history(user_id, chat_id, messages):
314
+ try:
315
+ ref = firebase_db.reference(f'users/{user_id}/chats/{chat_id}')
316
+ ref.update({
317
+ 'messages': messages,
318
+ 'last_updated': time.time()
319
+ })
320
+ return True
321
+ except Exception as e:
322
+ st.error(f"Error updating chat history: {str(e)}")
323
+ return False
324
+
325
+ def get_chat_history(user_id):
326
+ try:
327
+ ref = firebase_db.reference(f'users/{user_id}/chats')
328
+ chats = ref.get()
329
+ if chats:
330
+ return sorted(
331
+ [(chat_id, chat_data) for chat_id, chat_data in chats.items()],
332
+ key=lambda x: x[1]['last_updated'],
333
+ reverse=True
334
+ )
335
+ return []
336
+ except Exception as e:
337
+ st.error(f"Error fetching chat history: {str(e)}")
338
+ return []
339
+
340
+ def delete_chat_history(user_id, chat_id):
341
+ try:
342
+ ref = firebase_db.reference(f'users/{user_id}/chats/{chat_id}')
343
+ ref.delete()
344
+ return True
345
+ except Exception as e:
346
+ st.error(f"Error deleting chat history: {str(e)}")
347
+ return False
348
+
349
+ def generate_chat_title(messages):
350
+ """Generate a title for the chat based on messages, with fallbacks."""
351
+ try:
352
+ for message in messages:
353
+ if message.get('role') == 'user' and message.get('content'):
354
+ user_message = message['content']
355
+ return user_message[:30] + "..." if len(user_message) > 30 else user_message
356
+ except (KeyError, TypeError):
357
+ pass
358
+ return "New Chat" # Default fallback title
359
+
360
+ def get_user_name(user_id):
361
+ try:
362
+ ref = firebase_db.reference(f'users/{user_id}')
363
+ user_data = ref.get()
364
+ return user_data.get('name', 'User') if user_data else 'User'
365
+ except Exception as e:
366
+ st.error(f"Error fetching user data: {str(e)}")
367
+ return 'User'
368
+
369
+ # Use the pyrebase auth object for login/signup
370
+ def login_signup_ui():
371
+ inject_custom_css()
372
+
373
+ st.sidebar.markdown("""
374
+ <div style="text-align: center; margin-bottom: 30px;">
375
+ <h1 style="color: #00ffff; font-size: 28px; margin-bottom: 5px;">🔐 ApnaLawyer</h1>
376
+ <p style="color: rgba(255,255,255,0.7); font-size: 14px;">Secure Legal Assistance Portal</p>
377
+ </div>
378
+ """, unsafe_allow_html=True)
379
+
380
+ choice = st.sidebar.radio("Select Option", ["Login", "Signup", "Forgot Password"], label_visibility="collapsed")
381
+
382
+ if choice == "Signup":
383
+ st.sidebar.markdown("### Create New Account")
384
+ name = st.sidebar.text_input("Full Name", key="signup_name")
385
+ email = st.sidebar.text_input("Email Address", key="signup_email")
386
+ password = st.sidebar.text_input("Password", type="password", key="signup_password")
387
+ confirm_password = st.sidebar.text_input("Confirm Password", type="password", key="signup_confirm")
388
+
389
+ if st.sidebar.button("Create Account", key="signup_button"):
390
+ if not name:
391
+ st.sidebar.error("Please enter your full name!")
392
+ elif not email:
393
+ st.sidebar.error("Email address is required!")
394
+ elif not password or not confirm_password:
395
+ st.sidebar.error("Password fields cannot be empty!")
396
+ elif password != confirm_password:
397
+ st.sidebar.error("Passwords do not match!")
398
+ else:
399
+ try:
400
+ user = auth.create_user_with_email_and_password(email, password)
401
+ if store_user_data(user['localId'], name, email):
402
+ auth.send_email_verification(user['idToken'])
403
+ st.sidebar.success("✅ Account created! Please verify your email before logging in.")
404
+ except Exception as e:
405
+ error_str = str(e)
406
+ if "EMAIL_EXISTS" in error_str:
407
+ st.sidebar.warning("⚠ Email already exists. Please try a different email address.")
408
+ elif "WEAK_PASSWORD" in error_str:
409
+ st.sidebar.warning("⚠ Password should be at least 6 characters long.")
410
+ else:
411
+ st.sidebar.error(f"Error: {error_str}")
412
+
413
+ elif choice == "Login":
414
+ st.sidebar.markdown("### Welcome Back")
415
+ email = st.sidebar.text_input("Email Address", key="login_email")
416
+ password = st.sidebar.text_input("Password", type="password", key="login_password")
417
+
418
+ if st.sidebar.button("Login", key="login_button"):
419
+ if not email:
420
+ st.sidebar.error("✋ Please enter your email address")
421
+ elif not password:
422
+ st.sidebar.error("✋ Please enter your password")
423
+ else:
424
+ try:
425
+ user = auth.sign_in_with_email_and_password(email, password)
426
+ user_info = auth.get_account_info(user['idToken'])
427
+ email_verified = user_info['users'][0]['emailVerified']
428
+
429
+ if email_verified:
430
+ user_name = get_user_name(user['localId'])
431
+ st.session_state.logged_in = True
432
+ st.session_state.user_email = email
433
+ st.session_state.user_token = user['idToken']
434
+ st.session_state.user_name = user_name
435
+ st.sidebar.success(f"🎉 Welcome back, {user_name}!")
436
+ st.rerun()
437
+ else:
438
+ st.sidebar.warning("📧 Email not verified. Please check your inbox.")
439
+ if st.sidebar.button("🔁 Resend Verification Email", key="resend_verification"):
440
+ auth.send_email_verification(user['idToken'])
441
+ st.sidebar.info("📬 Verification email sent again!")
442
+ except Exception as e:
443
+ error_str = str(e)
444
+ if "EMAIL_NOT_FOUND" in error_str or "no user record" in error_str.lower():
445
+ st.sidebar.error("📭 Account not found")
446
+ st.sidebar.warning("Don't have an account? Please sign up.")
447
+ elif "INVALID_PASSWORD" in error_str or "INVALID_LOGIN_CREDENTIALS" in error_str:
448
+ st.sidebar.error("🔐 Incorrect password")
449
+ else:
450
+ st.sidebar.error("⚠ Login error. Please try again.")
451
+
452
+ elif choice == "Forgot Password":
453
+ st.sidebar.markdown("### Reset Your Password")
454
+ email = st.sidebar.text_input("Enter your email address", key="reset_email")
455
+
456
+ if st.sidebar.button("Send Reset Link", key="reset_button"):
457
+ if not email:
458
+ st.sidebar.error("Please enter your email address!")
459
+ else:
460
+ try:
461
+ auth.send_password_reset_email(email)
462
+ st.sidebar.success("📬 Password reset link sent to your email.")
463
+ except Exception as e:
464
+ error_str = str(e)
465
+ if "EMAIL_NOT_FOUND" in error_str:
466
+ st.sidebar.error("❌ No account found with this email address")
467
+ st.sidebar.warning("⚠ Don't have an account? Please sign up.")
468
+ else:
469
+ st.sidebar.error(f"Error: {error_str}")
470
+
471
+ # In your login_signup_ui() function, replace the Google login section with:
472
+ st.sidebar.markdown("---")
473
+ st.sidebar.markdown("### Or continue with")
474
+
475
+ if st.sidebar.button("Continue with Google", key="google_login"):
476
+ try:
477
+ # Generate a unique state token
478
+ state_token = hashlib.sha256(str(time.time()).encode()).hexdigest()
479
+
480
+ # Get Firebase config values
481
+ client_id = "546645596018-nvtkegm7mi8e83upfv771tv6t58c7snn.apps.googleusercontent.com" # Use actual OAuth client ID
482
+ firebase_auth_domain = firebaseConfig["authDomain"]
483
+ redirect_uri = f"https://{firebase_auth_domain}/auth/handler"
484
+
485
+ # Build the Google OAuth URL
486
+ auth_url = (
487
+ f"https://accounts.google.com/o/oauth2/v2/auth?"
488
+ f"response_type=code&"
489
+ f"client_id={client_id}&"
490
+ f"redirect_uri={urllib.parse.quote(redirect_uri)}&"
491
+ f"scope=email%20profile%20openid&"
492
+ f"state={state_token}"
493
+ )
494
+
495
+ # Store state token in session
496
+ st.session_state.oauth_state = state_token
497
+
498
+ # Open OAuth flow in new tab
499
+ components.html(
500
+ f"""
501
+ <script>
502
+ window.open('{auth_url}', '_blank').focus();
503
+ </script>
504
+ """,
505
+ height=0
506
+ )
507
+
508
+ st.sidebar.info("Google login window should open. Please allow popups if it doesn't appear.")
509
+
510
+ except Exception as e:
511
+ st.sidebar.error(f"Failed to start Google login: {str(e)}")
512
+
513
+ def check_google_callback():
514
+ try:
515
+ if 'code' in st.query_params and 'state' in st.query_params:
516
+ # Verify state token matches
517
+ if st.query_params['state'] != st.session_state.get('oauth_state'):
518
+ st.error("Security verification failed. Please try logging in again.")
519
+ return
520
+
521
+ auth_code = st.query_params['code']
522
+
523
+ # Initialize the Google Auth Provider
524
+ provider = firebase.auth.GoogleAuthProvider()
525
+
526
+ # Sign in with the auth code
527
+ credential = provider.credential(
528
+ None, # No ID token needed for code flow
529
+ auth_code
530
+ )
531
+
532
+ # Sign in with credential
533
+ user = auth.sign_in_with_credential(credential)
534
+
535
+ # Store user in session
536
+ st.session_state.logged_in = True
537
+ st.session_state.user_email = user['email']
538
+ st.session_state.user_name = user.get('displayName', 'Google User')
539
+ st.session_state.user_token = user['idToken']
540
+
541
+ # Store user data if new
542
+ if not user_exists(user['localId']):
543
+ store_user_data(
544
+ user['localId'],
545
+ st.session_state.user_name,
546
+ user['email']
547
+ )
548
+
549
+ # Clear the OAuth code from URL
550
+ st.query_params.clear()
551
+ st.rerun()
552
+
553
+ except Exception as e:
554
+ st.error(f"Google login failed: {str(e)}")
555
+
556
+ # Initialize speech-to-text model (cached to avoid reloading)
557
+ # @st.cache_resource
558
+ # def load_speech_to_text_model():
559
+ # return pipeline("automatic-speech-recognition", model="openai/whisper-base")
560
+
561
+ # Function to translate text
562
+ def transcribe_audio(audio_bytes, auto_detect=False):
563
+ """Transcribe audio with auto language detection (English/Hindi)"""
564
+ if not SPEECHMATICS_API_KEY:
565
+ st.error("API key not configured!")
566
+ return None
567
+
568
+ try:
569
+ API_BASE_URL = "https://asr.api.speechmatics.com/v2"
570
+ MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB free tier limit
571
+ TIMEOUT = 30 # seconds
572
+
573
+ # 1. Validate audio size
574
+ if len(audio_bytes) > MAX_FILE_SIZE:
575
+ st.error(f"Audio exceeds {MAX_FILE_SIZE/1024/1024}MB free tier limit")
576
+ return None
577
+
578
+ # 2. Create temp WAV file
579
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpfile:
580
+ tmpfile.write(audio_bytes)
581
+ tmp_path = tmpfile.name
582
+
583
+ # 3. Configure language settings
584
+ language_config = {
585
+ "language": "auto" if auto_detect else st.session_state.get('language', 'en')
586
+ }
587
+
588
+ # For auto-detect, specify possible languages (improves accuracy)
589
+ if auto_detect:
590
+ language_config["language_options"] = ["en", "hi"] # English/Hindi only
591
+
592
+ job_config = {
593
+ "type": "transcription",
594
+ "transcription_config": {
595
+ **language_config,
596
+ "operating_point": "standard",
597
+ "enable_entities": False
598
+ }
599
+ }
600
+
601
+ headers = {"Authorization": f"Bearer {SPEECHMATICS_API_KEY}"}
602
 
603
+ # 4. Create job
604
+ with open(tmp_path, 'rb') as audio_file:
605
+ response = requests.post(
606
+ f"{API_BASE_URL}/jobs",
607
+ headers=headers,
608
+ files={
609
+ 'config': (None, json.dumps(job_config)),
610
+ 'data_file': ('audio.wav', audio_file)
611
+ },
612
+ timeout=TIMEOUT
613
+ )
614
 
615
+ # 5. Handle response
616
+ if response.status_code != 201:
617
+ error_msg = response.json().get('error', {}).get('message', response.text)
618
+ st.error(f"Job creation failed: {error_msg}")
619
+ return None
620
 
621
+ job_id = response.json()['id']
622
+ st.session_state.current_job_id = job_id
623
+
624
+ # 6. Poll for completion
625
+ start_time = time.time()
626
+ detected_language = None
627
+
628
+ while True:
629
+ if time.time() - start_time > TIMEOUT:
630
+ raise Exception("Timeout waiting for transcription")
631
+
632
+ status_response = requests.get(
633
+ f"{API_BASE_URL}/jobs/{job_id}",
634
+ headers=headers,
635
+ timeout=TIMEOUT
636
+ )
637
+ status_data = status_response.json()
638
+
639
+ # Capture detected language if auto mode
640
+ if auto_detect and not detected_language:
641
+ detected_language = status_data['job'].get('detected_language')
642
+ if detected_language:
643
+ st.info(f"🔍 Detected language: {detected_language.upper()}")
644
+
645
+ if status_data['job']['status'] == 'done':
646
+ break
647
+ elif status_data['job']['status'] == 'failed':
648
+ raise Exception(f"Transcription failed: {status_data.get('error')}")
649
+
650
+ time.sleep(2)
651
+
652
+ # 7. Get transcript
653
+ transcript_response = requests.get(
654
+ f"{API_BASE_URL}/jobs/{job_id}/transcript",
655
+ headers=headers,
656
+ params={'format': 'txt'},
657
+ timeout=TIMEOUT
658
+ )
659
+
660
+ if transcript_response.status_code != 200:
661
+ st.error(f"Failed to fetch transcript: {transcript_response.text}")
662
+ return None
663
+
664
+ return transcript_response.text
665
+
666
+ except Exception as e:
667
+ st.error(f"Transcription error: {str(e)}")
668
+ return None
669
+ finally:
670
+ if 'tmp_path' in locals() and os.path.exists(tmp_path):
671
+ try:
672
+ os.unlink(tmp_path)
673
+ except:
674
+ pass
675
+
676
+
677
+ def check_rate_limit():
678
+ """Simple rate limiting for audio transcription"""
679
+ if 'last_transcription_time' not in st.session_state:
680
+ st.session_state.last_transcription_time = time.time()
681
+ return True
682
+
683
+ current_time = time.time()
684
+ time_since_last = current_time - st.session_state.last_transcription_time
685
+
686
+ if time_since_last < 10: # 10 second cooldown between transcriptions
687
+ return False
688
+
689
+ st.session_state.last_transcription_time = current_time
690
+ return True
691
+
692
+
693
+ def create_new_chat():
694
+ """Properly reset the chat state and start a new chat session."""
695
+ # Save current chat if it has messages
696
+ if len(st.session_state.get('messages', [])) > 1: # More than just welcome message
697
+ save_current_chat()
698
+
699
+ # Reset conversation state
700
+ st.session_state.messages = [{
701
+ "role": "assistant",
702
+ "content": f"🎉✨ Welcome {st.session_state.user_name}! ✨🎉\n\nI'm ApnaLawyer, your AI legal assistant. "
703
+ "I can help explain Indian laws in simple terms. What would you like to know?"
704
+ }]
705
+ st.session_state.current_chat_id = None
706
  st.session_state.memory = ConversationBufferWindowMemory(k=2, memory_key="chat_history", return_messages=True)
707
+ # Clear any audio processing flags
708
+ if 'audio_processed' in st.session_state:
709
+ del st.session_state.audio_processed
710
+ st.rerun()
711
 
712
+ def save_current_chat():
713
+ """Save the current chat to history before starting a new one."""
714
+ if len(st.session_state.get('messages', [])) > 1: # More than just welcome message
715
+ chat_title = generate_chat_title(st.session_state.messages)
716
+ if hasattr(st.session_state, 'current_chat_id') and st.session_state.current_chat_id:
717
+ update_chat_history(st.session_state.user_id, st.session_state.current_chat_id, st.session_state.messages)
718
+ else:
719
+ chat_id = save_chat_to_history(st.session_state.user_id, chat_title, st.session_state.messages)
720
+ st.session_state.current_chat_id = chat_id
721
+ st.session_state.chat_history = get_chat_history(st.session_state.user_id)
722
 
 
723
 
724
+ def load_chat(chat_id):
725
+ """Load a specific chat from history."""
726
+ # Save current chat if it has messages
727
+ if len(st.session_state.get('messages', [])) > 1: # More than just welcome message
728
+ save_current_chat()
729
+
730
+ # Load the selected chat
731
+ chat_data = next((chat for chat in st.session_state.chat_history if chat[0] == chat_id), None)
732
+ if chat_data:
733
+ st.session_state.messages = chat_data[1]['messages']
734
+ st.session_state.current_chat_id = chat_id
735
+ # Reinitialize memory with loaded messages
736
+ st.session_state.memory = ConversationBufferWindowMemory(k=2, memory_key="chat_history", return_messages=True)
737
+ for msg in chat_data[1]['messages'][:-2]: # Skip last 2 messages to maintain window size
738
+ if msg['role'] == 'user':
739
+ st.session_state.memory.save_context({"question": msg['content']}, {"answer": ""})
740
+ st.rerun()
741
 
742
+ def delete_chat(chat_id):
743
+ """Delete a specific chat from the user's chat history."""
744
  try:
745
+ user_id = st.session_state.user_id
746
+ if delete_chat_history(user_id, chat_id):
747
+ # Show toast notification instead of message in chat history
748
+ st.toast("Chat deleted successfully!", icon="✅")
749
+ # Refresh chat history after deletion
750
+ st.session_state.chat_history = get_chat_history(user_id)
751
+ st.rerun() # Force UI refresh
752
+ else:
753
+ st.toast("Failed to delete chat.", icon="❌")
754
+ except Exception as e:
755
+ st.toast(f"Error deleting chat: {str(e)}", icon="❌")
756
+
757
+ def chatbot_ui():
758
+ # Initialize language state
759
+ target_lang_code = "en" # Default language is English
760
+ if "language" not in st.session_state:
761
+ st.session_state.language = target_lang_code # Default language is English
762
+
763
+ # Initialize with personalized welcome message if first time
764
+ if "messages" not in st.session_state:
765
+ st.session_state.messages = [{
766
+ "role": "assistant",
767
+ "content": f"🎉✨ Welcome {st.session_state.user_name}! ✨🎉\n\nI'm ApnaLawyer, your AI legal assistant. "
768
+ "I can help explain Indian laws in simple terms. What would you like to know?"
769
+ }]
770
+
771
+ if "memory" not in st.session_state:
772
+ st.session_state.memory = ConversationBufferWindowMemory(k=5, memory_key="chat_history", return_messages=True)
773
+
774
+ # Get user ID
775
+ if "user_id" not in st.session_state:
776
+ try:
777
+ user_info = auth.get_account_info(st.session_state.user_token)
778
+ st.session_state.user_id = user_info['users'][0]['localId']
779
+ st.session_state.chat_history = get_chat_history(st.session_state.user_id)
780
+ except Exception as e:
781
+ st.error(f"Error getting user info: {str(e)}")
782
 
783
+ @st.cache_resource
784
+ def load_embeddings():
785
+ return HuggingFaceEmbeddings(model_name="law-ai/InLegalBERT")
786
 
787
+ embeddings = load_embeddings()
788
+ db = FAISS.load_local("ipc_embed_db", embeddings, allow_dangerous_deserialization=True)
789
+ db_retriever = db.as_retriever(search_type="similarity", search_kwargs={"k": 3})
790
+
791
+ # Define the prompt template
792
+ prompt_template = PromptTemplate(
793
+ input_variables=["context", "question", "chat_history"],
794
+ template="""
795
  <s>[INST]
796
+ You are ApnaLawyer, a trusted and knowledgeable AI assistant for Indian citizens. You provide legally accurate help related to Indian laws — including the Indian Penal Code (IPC), CrPC, POCSO Act, Domestic Violence Act, and others.
797
+
798
+ ### Your responsibilities:
799
+ - Use clear, simple, respectful language
800
+ - Accurately cite laws (IPC sections, CrPC, Acts) **only when asked for legal explanation**
801
+ - If the user asks you to "write", "draft", "create", or "format" a legal document or application, you must write a **formal legal draft**
802
+ - Do not mix legal explanations with the draft unless asked keep your response focused
803
+ - You may write drafts such as:
804
+ - FIR applications
805
+ - Police complaints
806
+ - Legal notices
807
+ - Affidavits
808
+ - Consent forms
809
+ - When drafting, use correct legal formatting, salutation, subject lines, and placeholders (name, address, date)
810
+
811
+ ### CONTEXT:
812
+ {context}
813
+
814
+ ### CHAT HISTORY:
815
+ {chat_history}
816
+
817
+ ### QUESTION:
818
+ {question}
819
+
820
+ ---
821
+
822
+ Based on the user's intent, choose **one of the following** response types:
823
+
824
+ ---
825
+ 📘 If the user is asking about the law, respond with:
826
+
827
+ ✅ **Answer**:
828
+ [Summary of the situation and legal explanation]
829
+
830
+ 📘 **Relevant Law(s)**:
831
+ [List exact IPC sections, Acts, clause-wise punishment, and applicable exceptions]
832
+
833
+ 🧾 **Other Related Laws**:
834
+ [Include CrPC, POCSO, DV Act, or procedural laws if relevant]
835
+
836
+ 📝 **Suggested Action**:
837
+ [Practical next steps — where to file, what to prepare]
838
+
839
+ 🧾 **Summary**:
840
+ [Short recap in plain language]
841
+
842
+ ---
843
+
844
+
845
+ ✍️ If the user wants you to write or draft something, respond ONLY with:
846
+
847
+ 📄 **Legal Draft/Application**:
848
+ [Write the complete legal document in clean, formal format using Indian legal norms. Use placeholders where needed.]
849
+
850
+ </s>[/INST]
851
  """
852
+ )
853
 
854
+ # Initialize the ConversationalRetrievalChain
855
+ qa = ConversationalRetrievalChain.from_llm(
856
+ llm=llm,
857
+ memory=st.session_state.memory,
858
+ retriever=db_retriever,
859
+ combine_docs_chain_kwargs={'prompt': prompt_template}
860
+ )
861
 
862
+ # Display all previous messages
863
+ for message in st.session_state.messages:
864
+ with st.chat_message(message["role"]):
865
+ st.markdown(message["content"])
866
 
867
+ # Text input box - always shown at the bottom
868
+ text_input = st.chat_input("Ask your legal question or record audio...")
869
 
870
+ # Handle text input
871
+ if text_input and 'audio_processed' not in st.session_state:
872
+ # Display user message immediately
873
+ with st.chat_message("user"):
874
+ st.markdown(text_input)
875
+
876
+ # Add to message history
877
+ st.session_state.messages.append({"role": "user", "content": text_input})
878
+
879
+ # Generate and display response
880
+ with st.chat_message("assistant"):
881
+ with st.spinner("Thinking..."):
882
+
883
+ # Auto-detect language from user prompt
884
+ # Auto-detect language from prompt
885
+ target_lang_name = detect_target_language(text_input)
886
+ if target_lang_name == "unsupported":
887
+ st.warning("⚠ Currently only Hindi, English, and Hinglish are supported.")
888
+ return
889
+
890
+ target_lang_code = supported_languages.get(target_lang_name, "en")
891
+
892
+ # Translate if necessary
893
+
894
+ mod_input = f"""Please answer the following question in {target_lang_name} language, using clear legal terms:
895
+ {text_input}"""
896
+ result = qa.invoke(input=mod_input)
897
+ answer = result["answer"]
898
+
899
+
900
+ message_placeholder = st.empty()
901
+ full_response = "⚠ Gentle reminder: We generally ensure precise information, but do double-check. \n\n\n"
902
+ for chunk in answer:
903
+ full_response += chunk
904
+ time.sleep(0.006)
905
+ message_placeholder.markdown(full_response + " |", unsafe_allow_html=True)
906
+
907
+ st.session_state.messages.append({"role": "assistant", "content": full_response})
908
+
909
+ # Update chat history
910
+ update_chat_history_function()
911
+
912
+ # Audio input - shown below the text input
913
+ audio_file = st.audio_input("", key="audio_recorder")
914
+
915
+ # Handle audio input (only if no text input was processed in this cycle)
916
+ if audio_file and 'audio_processed' not in st.session_state and not text_input:
917
+ audio_bytes = audio_file.getvalue()
918
+
919
+ # Set flag to prevent duplicate processing
920
+ st.session_state.audio_processed = True
921
+
922
+ with st.spinner("Transcribing..."):
923
+ try:
924
+ transcribed_text = transcribe_audio(audio_bytes)
925
+
926
+ # Display transcribed text immediately
927
+ with st.chat_message("user"):
928
+ st.markdown(transcribed_text)
929
+
930
+ st.session_state.messages.append({"role": "user", "content": transcribed_text})
931
+
932
+ # Generate and display response
933
+ with st.chat_message("assistant"):
934
+ with st.spinner("Thinking..."):
935
+
936
+
937
+ # Detect desired language from user input
938
+ # Detect desired language from user input
939
+ target_lang_name = detect_target_language(transcribed_text)
940
+ if target_lang_name == "unsupported":
941
+ st.warning("⚠ Currently only Hindi, English, and Hinglish are supported.")
942
+ return
943
+
944
+ target_lang_code = supported_languages.get(target_lang_name, "en")
945
+
946
+ # Handle Hinglish separately (keep original)
947
+
948
+ mod_input = f"""Please answer the following question in {target_lang_name} language, using clear legal terms:
949
+ {transcribed_text}"""
950
+ result = qa.invoke(input=mod_input)
951
+ answer = result["answer"]
952
+
953
+
954
+ # else: leave in English or handle more languages later
955
+ message_placeholder = st.empty()
956
+ full_response = "⚠ Gentle reminder: We generally ensure precise information, but do double-check. \n\n\n"
957
+ for chunk in answer:
958
+ full_response += chunk
959
+ time.sleep(0.006)
960
+ message_placeholder.markdown(full_response + " |", unsafe_allow_html=True)
961
+
962
+ st.session_state.messages.append({"role": "assistant", "content": full_response})
963
+
964
+ # Update chat history
965
+ update_chat_history_function()
966
+
967
+ except Exception as e:
968
+ st.error(f"Error: {str(e)}")
969
+ finally:
970
+ # Clear the flag after processing
971
+ if 'audio_processed' in st.session_state:
972
+ del st.session_state.audio_processed
973
+
974
+ # Sidebar UI (unchanged from your original)
975
+ with st.sidebar:
976
+ st.markdown(f"""
977
+ <div style="margin-bottom: 20px;">
978
+ <h3 style="color: #00ffff; margin-bottom: 5px;">👤 {st.session_state.user_name}</h3>
979
+ <p style="color: rgba(255,255,255,0.7); font-size: 12px; margin-top: 0;">{st.session_state.user_email}</p>
980
+ </div>
981
+ """, unsafe_allow_html=True)
982
+
983
+ # Button for creating a new chat
984
+ if st.button("➕ New Chat", key="new_chat_button", use_container_width=True):
985
+ create_new_chat()
986
+
987
+ # Chat history section
988
+ if hasattr(st.session_state, 'chat_history') and st.session_state.chat_history:
989
+ for chat_id, chat_data in st.session_state.chat_history:
990
+ # Safely get title with default
991
+ chat_title = chat_data.get('title', 'Untitled Chat')
992
+ timestamp = time.strftime('%d %b %Y, %I:%M %p',
993
+ time.localtime(chat_data.get('last_updated', time.time())))
994
+
995
+ col1, col2 = st.columns([0.8, 0.2])
996
+ with col1:
997
+ if st.button(
998
+ f"{chat_title}",
999
+ key=f"chat_{chat_id}", # Unique key for each chat button
1000
+ help=f"Last updated: {timestamp}",
1001
+ use_container_width=True
1002
+ ):
1003
+ load_chat(chat_id)
1004
+ with col2:
1005
+ if st.button("🗑", key=f"delete_{chat_id}"): # Unique key for each delete button
1006
+ delete_chat(chat_id)
1007
+ else:
1008
+ st.markdown("<p style='color: rgba(255,255,255,0.5);'>No chat history yet</p>", unsafe_allow_html=True)
1009
+
1010
+ # Logout button
1011
+ if st.button("🚪 Logout", key="logout_button", use_container_width=True):
1012
+ st.session_state.logged_in = False
1013
+ st.session_state.user_email = None
1014
+ st.session_state.user_name = None
1015
+ st.session_state.user_id = None
1016
+ st.rerun()
1017
+
1018
+
1019
+ def update_chat_history_function():
1020
+ """Helper function to update chat history"""
1021
+ if len(st.session_state.messages) > 2:
1022
+ chat_title = generate_chat_title(st.session_state.messages)
1023
+ if hasattr(st.session_state, 'current_chat_id') and st.session_state.current_chat_id:
1024
+ update_chat_history(st.session_state.user_id, st.session_state.current_chat_id, st.session_state.messages)
1025
+ else:
1026
+ chat_id = save_chat_to_history(st.session_state.user_id, chat_title, st.session_state.messages)
1027
+ st.session_state.current_chat_id = chat_id
1028
+ st.session_state.chat_history = get_chat_history(st.session_state.user_id)
1029
+
1030
+ # ----------------- Main App -------------------
1031
+ if "logged_in" not in st.session_state:
1032
+ st.session_state.logged_in = False
1033
+
1034
+ if not st.session_state.logged_in:
1035
+ login_signup_ui()
1036
+ else:
1037
+ # Main chat interface
1038
+ chatbot_ui()
1039
+ # Footer
1040
+ footer()