Rabbit-Innotech commited on
Commit
da1c657
·
verified ·
1 Parent(s): 4d7cda7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +412 -315
app.py CHANGED
@@ -23,11 +23,142 @@ from langchain_core.prompts import ChatPromptTemplate
23
  import gradio as gr
24
  from PyPDF2 import PdfReader
25
 
26
- groq_api_key= os.environ.get('grop_API_KEY')
27
 
28
- template = """
29
- **Role**: Compassionate Regal Assistance and GBV Support Specialist with Emotional Awareness.
30
- You are a friendly and empathetic chatbot designed to assist users in a conversational and human-like manner. Your goal is to provide accurate, helpful, and emotionally supportive responses based on the provided context: {context}. Follow these guidelines:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  1. **Emotional Awareness**
33
  - Acknowledge the user's emotions and respond with empathy.
@@ -35,41 +166,23 @@ template = """
35
  - If the user expresses negative emotions, offer comfort and reassurance.
36
 
37
  2. **Contextual Interaction**
38
- - Begin with a warm and empathetic welcome message.
39
  - Extract precise details from the provided context: {context}.
40
  - Respond directly to the user's question: {question}.
41
  - Only provide detailed information if user requests it.
42
  - Remember the user's name is {first_name}.
43
 
44
  3. **Communication Guidelines**
45
- - Maintain a warm, conversational tone (avoid over-familiarity).
46
  - Use occasional emojis for engagement (e.g., 😊, 🤗, ❤️).
47
  - Provide clear, concise, and emotionally supportive information.
48
 
49
- 4. **Response Strategies**
50
- - Greet users naturally and ask about their wellbeing (e.g., "Welcome, {first_name}! 😊 How are you feeling today?", "Hello {first_name}! 🤗 What's on your mind?").
51
- - Always start with a check-in about the user's wellbeing or current situation.
52
- - Provide a concise summary with only relevant information.
53
- - Avoid generating content beyond the context.
54
- - Handle missing information transparently.
55
-
56
- 5. **No Extra Content**
57
- - If no information in {context} matches the user's request {question} :
58
  * Respond politely: "I don't have that information at the moment, {first_name}. 😊"
59
  * Offer alternative assistance options.
60
  - Strictly avoid generating unsupported content.
61
- - Prevent information padding or speculation.
62
-
63
- 6. **Extracting Relevant Links**
64
- - If the user asks for a link related to their request `{question}`, extract the most relevant URL from `{context}` and provide it directly.
65
- - Example response:
66
- - "Here is the link you requested, [URL]"
67
 
68
- 7. **Real-Time Awareness**
69
- - Acknowledge the current context when appropriate.
70
- - Stay focused on the user's immediate needs.
71
-
72
- 8. **Previous Conversation Context**
73
  - Consider the conversation history: {conversation_history}
74
  - Maintain continuity with previous exchanges.
75
 
@@ -77,304 +190,202 @@ template = """
77
  **User's Question:** {question}
78
  **Your Response:**
79
  """
80
-
81
- # Set up embedding model
82
- embed_model = HuggingFaceEmbeddings(model_name="mixedbread-ai/mxbai-embed-large-v1")
83
-
84
- # Process data from Drive
85
- def process_data_files():
86
- folder_path = "./"
87
- context_data = []
88
 
89
- # Get list of data files
90
- all_files = os.listdir(folder_path)
91
- data_files = [f for f in all_files if f.lower().endswith(('.csv', '.xlsx', '.xls'))]
92
 
93
- # Process each file
94
- for index, file_name in enumerate(data_files, 1):
95
- file_path = os.path.join(folder_path, file_name)
 
 
96
 
97
  try:
98
- # Read file
99
- if file_name.lower().endswith('.csv'):
100
- df = pd.read_csv(file_path)
101
- else:
102
- df = pd.read_excel(file_path)
103
-
104
- # Check if column 3 exists
105
- if df.shape[1] > 2:
106
- column_data = df.iloc[:, 2].dropna().astype(str).tolist()
107
-
108
- # Each row becomes one chunk
109
- for i, text in enumerate(column_data):
110
- context_data.append({"page_content": text, "metadata": {"source": file_name, "row": i+1}})
111
- else:
112
- print(f"Warning: File {file_name} has fewer than 3 columns.")
113
-
114
  except Exception as e:
115
- print(f"Error processing file {file_name}: {e}")
 
 
 
 
 
 
 
 
 
116
 
117
- return context_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- # Create vectorstore
120
- def create_vectorstore(data):
121
- # Extract just the text content from each Document object in the list
122
- cleaned_texts = [doc["page_content"] for doc in data]
123
- metadatas = [doc["metadata"] for doc in data]
124
-
125
- # Create vector store
126
- vectorstore = Chroma(
127
- collection_name="GBVRS",
128
- embedding_function=embed_model,
129
- )
130
 
131
- # Add data to vector store
132
- vectorstore.add_texts(cleaned_texts, metadatas=metadatas)
133
- return vectorstore
134
-
135
- # User session management
136
- class UserSession:
137
- def __init__(self, llm):
138
- self.current_user = None
139
- self.welcome_message = None
140
- self.conversation_history = []
141
- self.llm = llm
142
-
143
- def set_user(self, user_info):
144
- self.current_user = user_info
145
- self.set_welcome_message(user_info.get("Nickname", "Guest"))
146
- # Initialize conversation history with welcome message
147
- welcome = self.get_welcome_message()
148
- self.conversation_history = [
149
- {"role": "assistant", "content": welcome},
150
- ]
151
-
152
- def get_user(self):
153
- return self.current_user
154
-
155
- def set_welcome_message(self, nickname):
156
- """Set a dynamic welcome message using the LLM."""
157
- # Define a prompt for the LLM to generate a welcome message
158
  prompt = (
159
- f"Create a very brief welcome message for {nickname} that fits in 3 lines. "
160
- f"The message should: "
161
- f"1. Welcome {nickname} warmly and professionally. "
162
- f"2. Emphasize that this is a safe and trusted space. "
163
- f"3. Highlight specialized support for gender-based violence (GBV) and legal assistance. "
164
- f"4. Use a tone that is warm, reassuring, and professional. "
165
- f"5. Keep the message concise and impactful, ensuring it fits within the character limit."
166
  )
167
-
168
- # Use the LLM to generate the message
169
- response = self.llm.invoke(prompt)
170
- welcome = response.content
171
-
172
  # Format the message with HTML styling
173
- self.welcome_message = (
174
- f"<div style='font-size: 24px; font-weight: bold; color: #2E86C1;'>"
175
- f"<div style='font-size: 20px;'>"
176
  f"{welcome}"
177
  f"</div>"
178
  )
 
 
 
 
 
 
 
 
179
 
180
- def get_welcome_message(self):
181
- return self.welcome_message
182
-
183
- def add_to_history(self, role, message):
184
- """Add a message to the conversation history"""
185
- self.conversation_history.append({"role": role, "content": message})
186
-
187
- def get_conversation_history(self):
188
- """Get the full conversation history"""
189
- return self.conversation_history
190
-
191
- def get_formatted_history(self):
192
- """Get conversation history formatted as a string for the LLM"""
193
- formatted_history = ""
194
- for entry in self.conversation_history:
195
- role = "User" if entry["role"] == "user" else "Assistant"
196
- formatted_history += f"{role}: {entry['content']}\n\n"
197
- return formatted_history
198
-
199
- # Format context from documents
200
- def format_context(retrieved_docs):
201
- return "\n".join([doc.page_content for doc in retrieved_docs])
202
-
203
- # RAG Chain creation with updated approach
204
- def create_rag_chain(retriever, template, api_key):
205
- llm = ChatGroq(model="llama-3.3-70b-versatile", api_key=api_key)
206
- rag_prompt = PromptTemplate.from_template(template)
207
 
208
- # Define the RAG chain using the recommended approach
209
- def get_context_and_question(query):
210
- # Get user info from the session
211
- user_info = user_session.get_user() or {}
212
- first_name = user_info.get("Nickname", "User")
213
-
214
- # Get conversation history
215
- conversation_history = user_session.get_formatted_history()
 
 
 
 
 
 
 
 
 
216
 
217
- # Retrieve documents
218
- retrieved_docs = retriever.invoke(query)
219
- context_str = format_context(retrieved_docs)
 
 
 
 
 
220
 
221
- # Return the combined inputs for the prompt
222
- return {
223
- "context": context_str,
224
- "question": query,
225
- "first_name": first_name,
226
- "conversation_history": conversation_history
227
- }
228
-
229
- # Build the chain
230
- rag_chain = (
231
- RunnablePassthrough()
232
- | get_context_and_question
233
- | rag_prompt
234
- | llm
235
- | StrOutputParser()
236
- )
237
-
238
- return rag_chain
239
 
240
- # RAG memory function for user interaction (without translation)
241
  def rag_memory_stream(message, history):
 
242
  # Add user message to history
243
- user_session.add_to_history("user", message)
244
-
245
- # Get response from RAG chain
246
- response = rag_chain.invoke(message)
247
 
248
- # Add assistant response to history
249
- user_session.add_to_history("assistant", response)
250
-
251
- # Yield the response
252
- yield response
253
-
254
- # Add initial message to start the conversation
255
- def add_initial_message(chatbot):
256
- return chatbot
 
 
 
 
 
 
 
 
 
 
 
257
 
258
- # Store user details and handle session
259
  def collect_user_info(nickname):
260
- if not nickname:
 
261
  return "Nickname is required to proceed.", gr.update(visible=False), gr.update(visible=True), []
262
 
263
  # Store user info for chat session
264
  user_info = {
265
- "Nickname": nickname,
266
  "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
267
  }
268
 
269
  # Set user in session
270
- user_session.set_user(user_info)
271
 
272
  # Generate welcome message
273
- welcome_message = user_session.get_welcome_message()
274
-
275
- # Add initial message to start the conversation
276
- chat_history = add_initial_message([(None, welcome_message)])
277
 
278
  # Return welcome message and update UI
279
- return welcome_message, gr.update(visible=True), gr.update(visible=False), chat_history
280
 
281
- # Gradio Interface Setup with improved UX
282
- def chatbot_interface():
283
- global template, rag_chain
284
-
285
- template = """
286
- **Role**: Compassionate Regal Assistance and GBV Support Specialist with Emotional Awareness.
287
- You are a friendly and empathetic chatbot designed to assist users in a conversational and human-like manner. Your goal is to provide accurate, helpful, and emotionally supportive responses based on the provided context: {context}. Follow these guidelines:
288
-
289
- 1. **Emotional Awareness**
290
- - Acknowledge the user's emotions and respond with empathy.
291
- - Use phrases like "I understand how you feel," "That sounds challenging," or "I'm here to support you."
292
- - If the user expresses negative emotions, offer comfort and reassurance.
293
-
294
- 2. **Contextual Interaction**
295
- - Begin with a warm and empathetic welcome message.
296
- - Extract precise details from the provided context: {context}.
297
- - Respond directly to the user's question: {question}.
298
- - Only provide detailed information if user requests it.
299
- - Remember the user's name is {first_name}.
300
-
301
- 3. **Communication Guidelines**
302
- - Maintain a warm, conversational tone (avoid over-familiarity).
303
- - Use occasional emojis for engagement (e.g., 😊, 🤗, ❤️).
304
- - Provide clear, concise, and emotionally supportive information.
305
-
306
- 4. **Response Strategies**
307
- - Greet users naturally and ask about their wellbeing (e.g., "Welcome, {first_name}! 😊 How are you feeling today?", "Hello {first_name}! 🤗 What's on your mind?").
308
- - Always start with a check-in about the user's wellbeing or current situation.
309
- - Provide a concise summary with only relevant information.
310
- - Avoid generating content beyond the context.
311
- - Handle missing information transparently.
312
-
313
- 5. **No Extra Content**
314
- - If no information in {context} matches the user's request {question} :
315
- * Respond politely: "I don't have that information at the moment, {first_name}. 😊"
316
- * Offer alternative assistance options.
317
- - Strictly avoid generating unsupported content.
318
- - Prevent information padding or speculation.
319
-
320
- 6. **Extracting Relevant Links**
321
- - If the user asks for a link related to their request `{question}`, extract the most relevant URL from `{context}` and provide it directly.
322
- - Example response:
323
- - "Here is the link you requested, [URL]"
324
-
325
- 7. **Real-Time Awareness**
326
- - Acknowledge the current context when appropriate.
327
- - Stay focused on the user's immediate needs.
328
-
329
- 8. **Previous Conversation Context**
330
- - Consider the conversation history: {conversation_history}
331
- - Maintain continuity with previous exchanges.
332
-
333
- **Context:** {context}
334
- **User's Question:** {question}
335
- **Your Response:**
336
- """
337
-
338
- with gr.Blocks() as demo:
339
- # User registration section
340
- with gr.Column(visible=True, elem_id="registration_container") as registration_container:
341
- gr.Markdown("### Your privacy is our concern, please provide your nickname.")
342
-
343
- with gr.Row():
344
- first_name = gr.Textbox(
345
- label="Nickname",
346
- placeholder="Enter your Nickname",
347
- scale=1,
348
- elem_id="input_nickname"
349
- )
350
-
351
- with gr.Row():
352
- submit_btn = gr.Button("Start Chatting", variant="primary", scale=2)
353
-
354
- response_message = gr.Markdown()
355
-
356
- # Chatbot section (initially hidden)
357
- with gr.Column(visible=False, elem_id="chatbot_container") as chatbot_container:
358
- chat_interface = gr.ChatInterface(
359
- fn=rag_memory_stream,
360
- title="Chat with GBVR",
361
- fill_height=True
362
- )
363
-
364
- # Footer with version info
365
- gr.Markdown("Ijwi ry'Ubufasha v1.0.0 © 2025")
366
-
367
- # Handle user registration
368
- submit_btn.click(
369
- collect_user_info,
370
- inputs=[first_name],
371
- outputs=[response_message, chatbot_container, registration_container, chat_interface.chatbot]
372
- )
373
-
374
- demo.css = """
375
  :root {
376
- --background: #f0f0f0;
377
- --text: #000000;
 
 
 
 
 
 
378
  }
379
 
380
  body, .gradio-container {
@@ -387,7 +398,8 @@ def chatbot_interface():
387
  justify-content: center;
388
  align-items: center;
389
  background: var(--background);
390
- color: var(--text);
 
391
  }
392
 
393
  .gradio-container {
@@ -396,61 +408,146 @@ def chatbot_interface():
396
  }
397
 
398
  .gr-box {
399
- background: var(--background);
400
- color: var(--text);
401
  border-radius: 12px;
402
  padding: 2rem;
403
- border: 1px solid rgba(0, 0, 0, 0.1);
404
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
405
  }
406
 
407
  .gr-button-primary {
408
- background: var(--background);
409
- color: var(--text);
410
  padding: 12px 24px;
411
  border-radius: 8px;
412
  transition: all 0.3s ease;
413
- border: 1px solid rgba(0, 0, 0, 0.1);
 
414
  }
415
 
416
  .gr-button-primary:hover {
417
  transform: translateY(-1px);
418
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
 
419
  }
420
 
421
  footer {
422
  text-align: center;
423
- color: var(--text);
424
- opacity: 0.7;
425
  padding: 1rem;
426
  font-size: 0.9em;
427
  }
428
 
 
 
 
 
 
 
429
  .gr-markdown h3 {
430
- color: var(--text);
431
- margin-bottom: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  }
433
 
434
- .registration-markdown, .chat-title h1 {
435
- color: var(--text);
 
 
 
 
 
 
436
  }
437
  """
438
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  return demo
440
 
 
 
 
 
 
441
  # Main execution
442
  if __name__ == "__main__":
443
- # Process data and create vectorstore
444
- data = process_data_files()
445
- vectorstore = create_vectorstore(data)
446
- retriever = vectorstore.as_retriever()
447
-
448
- # Initialize LLM for the user session
449
- llm = ChatGroq(model="llama-3.3-70b-versatile", api_key=groq_api_key)
450
- user_session = UserSession(llm)
451
-
452
- # Create RAG chain with the new approach
453
- rag_chain = create_rag_chain(retriever, template, groq_api_key)
454
-
455
- # Launch the interface
456
- chatbot_interface().launch(share=True)
 
 
 
23
  import gradio as gr
24
  from PyPDF2 import PdfReader
25
 
 
26
 
27
+ # Configuration constants
28
+ COLLECTION_NAME = "GBVRs"
29
+ DATA_FOLDER = "./"
30
+ APP_VERSION = "v1.0.0"
31
+ APP_NAME = "Ijwi ry'Ubufasha Chatbot"
32
+ MAX_HISTORY_MESSAGES = 10
33
+
34
+ # Global state
35
+ current_user = None
36
+ welcome_message = None
37
+ conversation_history = []
38
+ llm = None
39
+ embed_model = None
40
+ vectorstore = None
41
+ retriever = None
42
+ rag_chain = None
43
+
44
+ def initialize_assistant():
45
+ """Initialize the assistant with necessary components and configurations."""
46
+ global llm, embed_model, vectorstore, retriever, rag_chain
47
+
48
+ # Initialize API key - try both possible key names
49
+ groq_api_key = os.environ.get('GBV')
50
+ if not groq_api_key:
51
+ print("WARNING: No GROQ API key found in userdata.")
52
+
53
+ # Initialize LLM - Default to Llama model which is more widely available
54
+ llm = ChatGroq(
55
+ model="llama-3.3-70b-versatile", # More reliable than whisper model
56
+ api_key=groq_api_key
57
+ )
58
+
59
+ # Set up embedding model
60
+ try:
61
+ embed_model = HuggingFaceEmbeddings(model_name="mixedbread-ai/mxbai-embed-large-v1")
62
+ except Exception as e:
63
+ # Fallback to smaller model
64
+ embed_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
65
+
66
+ # Process data and create vector store
67
+ data = process_data_files()
68
+
69
+ vectorstore = create_vectorstore(data)
70
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
71
+
72
+ # Create RAG chain
73
+ rag_chain = create_rag_chain()
74
+
75
+
76
+ def process_data_files():
77
+ """Process all data files from the specified folder."""
78
+ context_data = []
79
+
80
+ try:
81
+ if not os.path.exists(DATA_FOLDER):
82
+ print(f"WARNING: Data folder does not exist: {DATA_FOLDER}")
83
+ return context_data
84
+
85
+ # Get list of data files
86
+ all_files = os.listdir(DATA_FOLDER)
87
+ data_files = [f for f in all_files if f.lower().endswith(('.csv', '.xlsx', '.xls'))]
88
+
89
+ if not data_files:
90
+ print(f"WARNING: No data files found in: {DATA_FOLDER}")
91
+ return context_data
92
+
93
+ # Process each file
94
+ for index, file_name in enumerate(data_files, 1):
95
+ print(f"Processing file {index}/{len(data_files)}: {file_name}")
96
+ file_path = os.path.join(DATA_FOLDER, file_name)
97
+
98
+ try:
99
+ # Read file based on extension
100
+ if file_name.lower().endswith('.csv'):
101
+ df = pd.read_csv(file_path)
102
+ else:
103
+ df = pd.read_excel(file_path)
104
+
105
+ # Check if column 3 exists (source data is in third column)
106
+ if df.shape[1] > 2:
107
+ column_data = df.iloc[:, 2].dropna().astype(str).tolist()
108
+
109
+ # Each row becomes one chunk with metadata
110
+ for i, text in enumerate(column_data):
111
+ if text and len(text.strip()) > 0:
112
+ context_data.append({
113
+ "page_content": text,
114
+ "metadata": {
115
+ "source": file_name,
116
+ "row": i+1
117
+ }
118
+ })
119
+ else:
120
+ print(f"WARNING: File {file_name} has fewer than 3 columns.")
121
+
122
+ except Exception as e:
123
+ print(f"ERROR processing file {file_name}: {e}")
124
+
125
+ print(f"✅ Created {len(context_data)} chunks from {len(data_files)} files.")
126
+
127
+ except Exception as e:
128
+ print(f"ERROR accessing data folder: {e}")
129
+
130
+ return context_data
131
+
132
+ def create_vectorstore(data):
133
+ """Create a vector store from the processed data."""
134
+ vs = Chroma(
135
+ collection_name=COLLECTION_NAME,
136
+ embedding_function=embed_model,
137
+ )
138
+
139
+ if not data:
140
+ print("WARNING: No data available to create vector store.")
141
+ return vs
142
+
143
+ # Extract text content and metadata
144
+ texts = [doc["page_content"] for doc in data]
145
+ metadatas = [doc["metadata"] for doc in data]
146
+
147
+ try:
148
+ # Add data to vector store
149
+ vs.add_texts(texts, metadatas=metadatas)
150
+ print(f"✅ Added {len(texts)} documents to vector store.")
151
+ except Exception as e:
152
+ print(f"ERROR adding texts to vector store: {e}")
153
+
154
+ return vs
155
+
156
+ def create_rag_chain():
157
+ """Create the RAG chain for processing user queries."""
158
+ # Define the prompt template
159
+ template = """
160
+ **Role**: Compassionate GBV Support Specialist.
161
+ You are a friendly and empathetic chatbot designed to assist users with gender-based violence support. Your goal is to provide accurate, helpful, and emotionally supportive responses based on the provided context: {context}. Follow these guidelines:
162
 
163
  1. **Emotional Awareness**
164
  - Acknowledge the user's emotions and respond with empathy.
 
166
  - If the user expresses negative emotions, offer comfort and reassurance.
167
 
168
  2. **Contextual Interaction**
 
169
  - Extract precise details from the provided context: {context}.
170
  - Respond directly to the user's question: {question}.
171
  - Only provide detailed information if user requests it.
172
  - Remember the user's name is {first_name}.
173
 
174
  3. **Communication Guidelines**
175
+ - Maintain a warm, conversational tone.
176
  - Use occasional emojis for engagement (e.g., 😊, 🤗, ❤️).
177
  - Provide clear, concise, and emotionally supportive information.
178
 
179
+ 4. **No Extra Content**
180
+ - If no information in {context} matches the user's request {question}:
 
 
 
 
 
 
 
181
  * Respond politely: "I don't have that information at the moment, {first_name}. 😊"
182
  * Offer alternative assistance options.
183
  - Strictly avoid generating unsupported content.
 
 
 
 
 
 
184
 
185
+ 5. **Previous Conversation Context**
 
 
 
 
186
  - Consider the conversation history: {conversation_history}
187
  - Maintain continuity with previous exchanges.
188
 
 
190
  **User's Question:** {question}
191
  **Your Response:**
192
  """
 
 
 
 
 
 
 
 
193
 
194
+ rag_prompt = PromptTemplate.from_template(template)
 
 
195
 
196
+ def get_context_and_question(query):
197
+ # Get user info and conversation history
198
+ user_info = get_user() or {}
199
+ first_name = user_info.get("Nickname", "User")
200
+ conversation_hist = get_formatted_history()
201
 
202
  try:
203
+ # Retrieve relevant documents
204
+ retrieved_docs = retriever.invoke(query)
205
+ context_str = format_context(retrieved_docs)
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  except Exception as e:
207
+ print(f"ERROR retrieving documents: {e}")
208
+ context_str = "No relevant information found."
209
+
210
+ # Return the combined inputs for the prompt
211
+ return {
212
+ "context": context_str,
213
+ "question": query,
214
+ "first_name": first_name,
215
+ "conversation_history": conversation_hist
216
+ }
217
 
218
+ # Build the chain
219
+ try:
220
+ chain = (
221
+ RunnablePassthrough()
222
+ | get_context_and_question
223
+ | rag_prompt
224
+ | llm
225
+ | StrOutputParser()
226
+ )
227
+ return chain
228
+ except Exception as e:
229
+ print(f"ERROR creating RAG chain: {e}")
230
+
231
+ # Return a simple function as fallback
232
+ def fallback_chain(query):
233
+ return f"I'm here to help you, but I'm experiencing some technical difficulties right now. Please try again shortly."
234
+
235
+ return fallback_chain
236
 
237
+ def format_context(retrieved_docs):
238
+ """Format retrieved documents into a string context."""
239
+ if not retrieved_docs:
240
+ return "No relevant information available."
241
+ return "\n\n".join([doc.page_content for doc in retrieved_docs])
242
+
243
+ def set_user(user_info):
244
+ """Set current user and initialize welcome message."""
245
+ global current_user, conversation_history
246
+ current_user = user_info
247
+ generate_welcome_message(user_info.get("Nickname", "Guest"))
248
 
249
+ # Initialize conversation history with welcome message
250
+ welcome = get_welcome_message()
251
+ conversation_history = [
252
+ {"role": "assistant", "content": welcome},
253
+ ]
254
+
255
+ def get_user():
256
+ """Get current user information."""
257
+ global current_user
258
+ return current_user or {"Nickname": "Guest"}
259
+
260
+ def generate_welcome_message(nickname):
261
+ """Generate a dynamic welcome message using the LLM."""
262
+ global welcome_message
263
+ try:
264
+ # Use the LLM to generate the message
 
 
 
 
 
 
 
 
 
 
 
265
  prompt = (
266
+ f"Create a brief and warm welcome message for {nickname} that's about 1-2 sentences. "
267
+ f"Emphasize this is a safe space for discussing gender-based violence issues "
268
+ f"and that we provide support and resources. Keep it warm and reassuring."
 
 
 
 
269
  )
270
+
271
+ response = llm.invoke(prompt)
272
+ welcome = response.content.strip()
273
+
 
274
  # Format the message with HTML styling
275
+ welcome_message = (
276
+ f"<div style='font-size: 18px; color: #4E6BBF;'>"
 
277
  f"{welcome}"
278
  f"</div>"
279
  )
280
+ except Exception as e:
281
+ # Fallback welcome message
282
+ welcome_message = (
283
+ f"<div style='font-size: 18px; color: #4E6BBF;'>"
284
+ f"Welcome, {nickname}! You're in a safe space. We're here to provide support with "
285
+ f"gender-based violence issues and connect you with resources that can help."
286
+ f"</div>"
287
+ )
288
 
289
+ def get_welcome_message():
290
+ """Get the formatted welcome message."""
291
+ global welcome_message
292
+ if not welcome_message:
293
+ nickname = get_user().get("Nickname", "Guest")
294
+ generate_welcome_message(nickname)
295
+ return welcome_message
296
+
297
+ def add_to_history(role, message):
298
+ """Add a message to the conversation history."""
299
+ global conversation_history
300
+ conversation_history.append({"role": role, "content": message})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
+ # Trim history if it gets too long
303
+ if len(conversation_history) > MAX_HISTORY_MESSAGES * 2: # Keep pairs of messages
304
+ # Keep the first message (welcome) and the most recent messages
305
+ conversation_history = [conversation_history[0]] + conversation_history[-MAX_HISTORY_MESSAGES*2+1:]
306
+
307
+ def get_conversation_history():
308
+ """Get the full conversation history."""
309
+ return conversation_history
310
+
311
+ def get_formatted_history():
312
+ """Get conversation history formatted as a string for the LLM."""
313
+ # Skip the welcome message and only include the last few exchanges
314
+ recent_history = conversation_history[1:] if len(conversation_history) > 1 else []
315
+
316
+ # Limit to last MAX_HISTORY_MESSAGES exchanges
317
+ if len(recent_history) > MAX_HISTORY_MESSAGES * 2:
318
+ recent_history = recent_history[-MAX_HISTORY_MESSAGES*2:]
319
 
320
+ formatted_history = ""
321
+ for entry in recent_history:
322
+ role = "User" if entry["role"] == "user" else "Assistant"
323
+ # Truncate very long messages to avoid token limits
324
+ content = entry["content"]
325
+ if len(content) > 500: # Limit message length
326
+ content = content[:500] + "..."
327
+ formatted_history += f"{role}: {content}\n\n"
328
 
329
+ return formatted_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
 
331
  def rag_memory_stream(message, history):
332
+ """Process user message and generate response with memory."""
333
  # Add user message to history
334
+ add_to_history("user", message)
 
 
 
335
 
336
+ try:
337
+ # Get response from RAG chain
338
+ print(f"Processing message: {message[:50]}...")
339
+ response = rag_chain.invoke(message)
340
+ print(f"Generated response: {response[:50]}...")
341
+
342
+ # Add assistant response to history
343
+ add_to_history("assistant", response)
344
+
345
+ # Yield the response
346
+ yield response
347
+
348
+ except Exception as e:
349
+ import traceback
350
+ print(f"ERROR in rag_memory_stream: {e}")
351
+ print(f"Detailed error: {traceback.format_exc()}")
352
+
353
+ error_msg = f"I'm sorry, {get_user().get('Nickname', 'there')}. I encountered an error processing your request. Let's try a different question."
354
+ add_to_history("assistant", error_msg)
355
+ yield error_msg
356
 
 
357
  def collect_user_info(nickname):
358
+ """Store user details and initialize session."""
359
+ if not nickname or nickname.strip() == "":
360
  return "Nickname is required to proceed.", gr.update(visible=False), gr.update(visible=True), []
361
 
362
  # Store user info for chat session
363
  user_info = {
364
+ "Nickname": nickname.strip(),
365
  "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
366
  }
367
 
368
  # Set user in session
369
+ set_user(user_info)
370
 
371
  # Generate welcome message
372
+ welcome_message = get_welcome_message()
 
 
 
373
 
374
  # Return welcome message and update UI
375
+ return welcome_message, gr.update(visible=True), gr.update(visible=False), [(None, welcome_message)]
376
 
377
+ def get_css():
378
+ """Define CSS for the UI."""
379
+ return """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  :root {
381
+ --primary: #4E6BBF;
382
+ --primary-light: #697BBF;
383
+ --text-primary: #333333;
384
+ --text-secondary: #666666;
385
+ --background: #F9FAFC;
386
+ --card-bg: #FFFFFF;
387
+ --border: #E1E5F0;
388
+ --shadow: rgba(0, 0, 0, 0.05);
389
  }
390
 
391
  body, .gradio-container {
 
398
  justify-content: center;
399
  align-items: center;
400
  background: var(--background);
401
+ color: var(--text-primary);
402
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
403
  }
404
 
405
  .gradio-container {
 
408
  }
409
 
410
  .gr-box {
411
+ background: var(--card-bg);
412
+ color: var(--text-primary);
413
  border-radius: 12px;
414
  padding: 2rem;
415
+ border: 1px solid var(--border);
416
+ box-shadow: 0 4px 12px var(--shadow);
417
  }
418
 
419
  .gr-button-primary {
420
+ background: var(--primary);
421
+ color: white;
422
  padding: 12px 24px;
423
  border-radius: 8px;
424
  transition: all 0.3s ease;
425
+ border: none;
426
+ font-weight: bold;
427
  }
428
 
429
  .gr-button-primary:hover {
430
  transform: translateY(-1px);
431
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
432
+ background: var(--primary-light);
433
  }
434
 
435
  footer {
436
  text-align: center;
437
+ color: var(--text-secondary);
 
438
  padding: 1rem;
439
  font-size: 0.9em;
440
  }
441
 
442
+ .gr-markdown h2 {
443
+ color: var(--primary);
444
+ margin-bottom: 0.5rem;
445
+ font-size: 1.8em;
446
+ }
447
+
448
  .gr-markdown h3 {
449
+ color: var(--text-secondary);
450
+ margin-bottom: 1.5rem;
451
+ font-weight: normal;
452
+ }
453
+
454
+ #chatbot_container .chat-title h1,
455
+ #chatbot_container .empty-chatbot {
456
+ color: var(--primary);
457
+ }
458
+
459
+ #input_nickname {
460
+ padding: 12px;
461
+ border-radius: 8px;
462
+ border: 1px solid var(--border);
463
+ background: var(--card-bg);
464
+ transition: all 0.3s ease;
465
+ }
466
+
467
+ #input_nickname:focus {
468
+ border-color: var(--primary);
469
+ box-shadow: 0 0 0 2px rgba(78, 107, 191, 0.2);
470
+ outline: none;
471
  }
472
 
473
+ .chatbot-container .message.user {
474
+ background: #E8F0FE;
475
+ border-radius: 12px 12px 0 12px;
476
+ }
477
+
478
+ .chatbot-container .message.bot {
479
+ background: #F5F7FF;
480
+ border-radius: 12px 12px 12px 0;
481
  }
482
  """
483
 
484
+ def create_ui():
485
+ """Create and configure the Gradio UI."""
486
+ with gr.Blocks(css=get_css(), theme=gr.themes.Soft()) as demo:
487
+ # Registration section
488
+ with gr.Column(visible=True, elem_id="registration_container") as registration_container:
489
+ gr.Markdown(f"## Welcome to {APP_NAME}")
490
+ gr.Markdown("### Your privacy is important to us. Please provide a nickname to continue.")
491
+
492
+ with gr.Row():
493
+ first_name = gr.Textbox(
494
+ label="Nickname",
495
+ placeholder="Enter your nickname",
496
+ scale=1,
497
+ elem_id="input_nickname"
498
+ )
499
+
500
+ with gr.Row():
501
+ submit_btn = gr.Button("Start Chatting", variant="primary", scale=2)
502
+
503
+ response_message = gr.Markdown()
504
+
505
+ # Chatbot section (initially hidden)
506
+ with gr.Column(visible=False, elem_id="chatbot_container") as chatbot_container:
507
+ chat_interface = gr.ChatInterface(
508
+ fn=rag_memory_stream,
509
+ title=f"{APP_NAME} - GBV Support Assistant",
510
+ fill_height=True,
511
+ examples=[
512
+ "What resources are available for GBV victims?",
513
+ "How can I report an incident?",
514
+ "What are my legal rights?",
515
+ "I need help, what should I do first?"
516
+ ]
517
+ )
518
+
519
+ # Footer with version info
520
+ gr.Markdown(f"{APP_NAME} {APP_VERSION} © 2025")
521
+
522
+ # Handle user registration
523
+ submit_btn.click(
524
+ collect_user_info,
525
+ inputs=[first_name],
526
+ outputs=[response_message, chatbot_container, registration_container, chat_interface.chatbot]
527
+ )
528
+
529
  return demo
530
 
531
+ def launch_app():
532
+ """Launch the Gradio interface."""
533
+ ui = create_ui()
534
+ ui.launch(share=True)
535
+
536
  # Main execution
537
  if __name__ == "__main__":
538
+ try:
539
+ # Initialize and launch the assistant
540
+ initialize_assistant()
541
+ launch_app()
542
+ except Exception as e:
543
+ import traceback
544
+ print(f"❌ Fatal error initializing GBV Assistant: {e}")
545
+ print(traceback.format_exc())
546
+
547
+ # Create a minimal emergency UI to display the error
548
+ with gr.Blocks() as error_demo:
549
+ gr.Markdown("## System Error")
550
+ gr.Markdown(f"An error occurred while initializing the application: {str(e)}")
551
+ gr.Markdown("Please check your configuration and try again.")
552
+
553
+ error_demo.launch(share=True)