poemsforaphrodite commited on
Commit
30b1715
β€’
1 Parent(s): 0048e66

Upload 2 files

Browse files
Files changed (2) hide show
  1. operator.py +477 -0
  2. requirements.txt +6 -0
operator.py ADDED
@@ -0,0 +1,477 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ import streamlit as st
4
+ from streamlit.runtime.scriptrunner import RerunException, StopException, RerunData
5
+ from openai import OpenAI
6
+ from pymongo import MongoClient
7
+ from datetime import datetime, timedelta
8
+ import time
9
+ from streamlit_autorefresh import st_autorefresh
10
+ from streamlit.runtime.caching import cache_data
11
+ import logging
12
+
13
+ # Set up logging
14
+ logging.basicConfig(level=logging.INFO)
15
+
16
+ # Load environment variables
17
+ load_dotenv()
18
+
19
+ # Configuration
20
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
21
+ MONGODB_URI = os.getenv("MONGODB_URI")
22
+
23
+ # Initialize clients
24
+ openai_client = OpenAI(api_key=OPENAI_API_KEY)
25
+ mongo_client = MongoClient(MONGODB_URI)
26
+ db = mongo_client["Wall_Street"]
27
+ conversation_history = db["conversation_history"]
28
+ trainer_feedback = db["trainer_feedback"]
29
+ trainer_instructions = db["trainer_instructions"]
30
+ global_common_memory = db["global_common_memory"] # New global common memory collection
31
+
32
+ # Define a unique identifier for global memory
33
+ GLOBAL_MEMORY_ID = "global_common_memory_id"
34
+
35
+ # Set up Streamlit page configuration
36
+ st.set_page_config(page_title="GPT-Driven Chat System - Operator", page_icon="πŸ› οΈ", layout="wide")
37
+
38
+ # Custom CSS to improve the UI
39
+ st.markdown("""
40
+ <style>
41
+ /* Your custom CSS styles */
42
+ </style>
43
+ """, unsafe_allow_html=True)
44
+
45
+ # --- Common Memory Functions ---
46
+
47
+ @cache_data(ttl=300) # Cache for 5 minutes
48
+ def get_global_common_memory():
49
+ """Retrieve the global common memory."""
50
+ memory_doc = global_common_memory.find_one({"memory_id": "global_common_memory_id"})
51
+ return memory_doc.get('memory', []) if memory_doc else []
52
+ def append_to_global_common_memory(content):
53
+ """Append content to the global common memory."""
54
+ try:
55
+ # First, ensure the document exists with an initialized memory array
56
+ global_common_memory.update_one(
57
+ {"memory_id": GLOBAL_MEMORY_ID},
58
+ {"$setOnInsert": {"memory": []}},
59
+ upsert=True
60
+ )
61
+
62
+ # Then, add the new content to the memory array
63
+ result = global_common_memory.update_one(
64
+ {"memory_id": GLOBAL_MEMORY_ID},
65
+ {"$addToSet": {"memory": content}}
66
+ )
67
+
68
+ # Invalidate the cache after updating
69
+ get_global_common_memory.clear()
70
+
71
+ if result.modified_count > 0:
72
+ st.success("Memory appended successfully!")
73
+ else:
74
+ st.info("This memory item already exists or no changes were made.")
75
+
76
+ raise RerunException(RerunData(page_script_hash=None))
77
+ except RerunException:
78
+ raise
79
+ except Exception as e:
80
+ st.error(f"Failed to append to global common memory: {str(e)}")
81
+
82
+ def clear_global_common_memory():
83
+ """Clear all items from the global common memory."""
84
+ try:
85
+ global_common_memory.update_one(
86
+ {"memory_id": GLOBAL_MEMORY_ID},
87
+ {"$set": {"memory": []}},
88
+ upsert=True
89
+ )
90
+ # Invalidate the cache after clearing
91
+ get_global_common_memory.clear()
92
+ st.success("Global common memory cleared successfully!")
93
+ except Exception as e:
94
+ st.error(f"Failed to clear global common memory: {str(e)}")
95
+
96
+ # --- Takeover Functions ---
97
+
98
+ def activate_takeover(session_id):
99
+ """
100
+ Activates takeover mode for the given session.
101
+ """
102
+ try:
103
+ db.takeover_status.update_one(
104
+ {"session_id": session_id},
105
+ {"$set": {"active": True, "activated_at": datetime.utcnow()}},
106
+ upsert=True
107
+ )
108
+ st.success(f"Takeover activated for session {session_id[:8]}...")
109
+ except Exception as e:
110
+ st.error(f"Failed to activate takeover: {str(e)}")
111
+
112
+ def deactivate_takeover(session_id):
113
+ """
114
+ Deactivates takeover mode for the given session.
115
+ """
116
+ try:
117
+ db.takeover_status.update_one(
118
+ {"session_id": session_id},
119
+ {"$set": {"active": False}},
120
+ )
121
+ st.success(f"Takeover deactivated for session {session_id[:8]}...")
122
+ except Exception as e:
123
+ st.error(f"Failed to deactivate takeover: {str(e)}")
124
+
125
+ def send_admin_message(session_id, message):
126
+ """
127
+ Sends an admin message directly to the user during a takeover.
128
+ """
129
+ admin_message = {
130
+ "role": "admin",
131
+ "content": message,
132
+ "timestamp": datetime.utcnow(),
133
+ "status": "approved"
134
+ }
135
+
136
+ try:
137
+ # Upsert the admin message
138
+ result = conversation_history.update_one(
139
+ {"session_id": session_id},
140
+ {
141
+ "$push": {"messages": admin_message},
142
+ "$set": {"last_updated": datetime.utcnow()}
143
+ }
144
+ )
145
+ if result.modified_count > 0:
146
+ st.success("Admin message sent successfully!")
147
+ else:
148
+ st.error("Failed to send admin message.")
149
+ except Exception as e:
150
+ st.error(f"Failed to send admin message: {str(e)}")
151
+
152
+ # --- Admin Dashboard Functions ---
153
+
154
+ def handle_admin_intervention():
155
+ st.subheader("Admin Intervention")
156
+
157
+ st.subheader("Review Pending Responses")
158
+ pending_responses = conversation_history.find(
159
+ {"messages.role": "assistant", "messages.status": "pending"}
160
+ )
161
+
162
+ for conversation in pending_responses:
163
+ st.write(f"Session ID: {conversation['session_id'][:8]}...")
164
+
165
+ # Display global common memory
166
+ st.subheader("Global Common Memory")
167
+ common_memory = get_global_common_memory()
168
+ if common_memory:
169
+ for idx, item in enumerate(common_memory, 1):
170
+ st.text(f"{idx}. {item}")
171
+ else:
172
+ st.info("Global common memory is currently empty.")
173
+
174
+ for i, message in enumerate(conversation['messages']):
175
+ if message['role'] == 'assistant' and message.get('status') == 'pending':
176
+ user_message = conversation['messages'][i-1]['content'] if i > 0 else "N/A"
177
+ st.write(f"**User:** {user_message}")
178
+ st.write(f"**GPT:** {message['content']}")
179
+
180
+ col1, col2, col3 = st.columns(3)
181
+ with col1:
182
+ if st.button("Approve", key=f"approve_{conversation['session_id']}_{i}"):
183
+ if approve_response(conversation['session_id'], i):
184
+ st.success("Response approved")
185
+ time.sleep(0.5) # Short delay to ensure the success message is visible
186
+ st.rerun()
187
+ with col2:
188
+ if st.button("Modify", key=f"modify_{conversation['session_id']}_{i}"):
189
+ st.session_state['modifying'] = (conversation['session_id'], i)
190
+ st.rerun()
191
+ with col3:
192
+ if st.button("Regenerate", key=f"regenerate_{conversation['session_id']}_{i}"):
193
+ st.session_state['regenerating'] = (conversation['session_id'], i)
194
+ st.rerun()
195
+
196
+ st.divider()
197
+
198
+ if 'regenerating' in st.session_state:
199
+ try:
200
+ session_id, message_index = st.session_state['regenerating']
201
+
202
+ with st.form(key="regenerate_form"):
203
+ operator_input = st.text_input("Enter additional instructions for regeneration:")
204
+ submit_button = st.form_submit_button("Submit")
205
+
206
+ if submit_button:
207
+ del st.session_state['regenerating'] # Remove the key after submission
208
+ regenerate_response(session_id, message_index, operator_input)
209
+ st.success("Response regenerated with operator input.")
210
+ st.rerun()
211
+ except ValueError:
212
+ st.error("Invalid regenerating state. Please try again.")
213
+
214
+ if 'modifying' in st.session_state:
215
+ session_id, message_index = st.session_state['modifying']
216
+ conversation = conversation_history.find_one({"session_id": session_id})
217
+ message = conversation['messages'][message_index]
218
+
219
+ modified_content = st.text_area("Modify the response:", value=message['content'])
220
+ if st.button("Save Modification"):
221
+ save_modified_response(session_id, message_index, modified_content)
222
+ st.success("Response modified and approved")
223
+ del st.session_state['modifying']
224
+ st.rerun()
225
+
226
+ def approve_response(session_id, message_index):
227
+ try:
228
+ result = conversation_history.update_one(
229
+ {"session_id": session_id},
230
+ {"$set": {f"messages.{message_index}.status": "approved"}}
231
+ )
232
+ return result.modified_count > 0
233
+ except Exception as e:
234
+ st.error(f"Failed to approve response: {str(e)}")
235
+ return False
236
+
237
+ def save_modified_response(session_id, message_index, modified_content):
238
+ try:
239
+ conversation_history.update_one(
240
+ {"session_id": session_id},
241
+ {
242
+ "$set": {
243
+ f"messages.{message_index}.content": modified_content,
244
+ f"messages.{message_index}.status": "approved"
245
+ }
246
+ }
247
+ )
248
+ except Exception as e:
249
+ st.error(f"Failed to save modified response: {str(e)}")
250
+
251
+ def regenerate_response(session_id, message_index, operator_input):
252
+ try:
253
+ conversation = conversation_history.find_one({"session_id": session_id})
254
+ user_message = conversation['messages'][message_index - 1]['content'] if message_index > 0 else ""
255
+ new_response, is_uncertain = get_gpt_response(user_message, system_message=operator_input)
256
+
257
+ if is_uncertain:
258
+ status = "pending"
259
+ else:
260
+ status = "approved"
261
+
262
+ conversation_history.update_one(
263
+ {"session_id": session_id},
264
+ {
265
+ "$set": {
266
+ f"messages.{message_index}.content": new_response,
267
+ f"messages.{message_index}.status": status
268
+ }
269
+ }
270
+ )
271
+ except Exception as e:
272
+ st.error(f"Failed to regenerate response: {str(e)}")
273
+
274
+ # --- Admin Page ---
275
+
276
+ def admin_page():
277
+ st.title("πŸ› οΈ Operator Dashboard")
278
+
279
+ # Add auto-refresh every 10 seconds (10000 milliseconds)
280
+ st_autorefresh(interval=10000, limit=None, key="operator_autorefresh")
281
+
282
+ if st.button("πŸ”„ Reload Dashboard"):
283
+ st.rerun()
284
+
285
+ try:
286
+ deleted_count = cleanup_old_chats()
287
+ if deleted_count is not None:
288
+ if deleted_count > 0:
289
+ st.success(f"Cleaned up {deleted_count} inactive chat(s).")
290
+ else:
291
+ st.info("No inactive chats to clean up.")
292
+ else:
293
+ st.warning("Unable to perform cleanup. Please check the database connection.")
294
+
295
+ tab1, tab2 = st.tabs([
296
+ "πŸ“Š Current Chats",
297
+ "πŸ”§ Admin Intervention",
298
+ ])
299
+
300
+ with tab1:
301
+ st.header("Current Chats")
302
+ recent_chats = fetch_recent_chats()
303
+ if not recent_chats:
304
+ st.info("No recent chats found.")
305
+ else:
306
+ cols_per_row = 3
307
+ for i in range(0, len(recent_chats), cols_per_row):
308
+ cols = st.columns(cols_per_row)
309
+ for j, chat in enumerate(recent_chats[i:i + cols_per_row]):
310
+ with cols[j]:
311
+ with st.expander(f"Session: {chat['session_id'][:8]}...", expanded=False):
312
+ display_chat_preview(chat)
313
+ col1, col2 = st.columns(2)
314
+ with col1:
315
+ if st.button("View Full Chat", key=f"view_{chat['session_id']}"):
316
+ st.session_state['selected_chat'] = chat['session_id']
317
+ st.rerun()
318
+ with col2:
319
+ if st.button("Delete Chat", key=f"delete_{chat['session_id']}"):
320
+ delete_chat(chat['session_id'])
321
+ st.rerun()
322
+
323
+ with tab2:
324
+ handle_admin_intervention()
325
+
326
+ st.caption(f"Last refreshed: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
327
+ except (RerunException, StopException):
328
+ raise
329
+ except Exception as e:
330
+ st.error(f"An error occurred: {str(e)}")
331
+
332
+ # --- Fetch Recent Chats ---
333
+
334
+ def fetch_recent_chats():
335
+ return list(conversation_history.find({},
336
+ {"session_id": 1, "last_updated": 1, "messages": {"$slice": 3}})
337
+ .sort("last_updated", -1)
338
+ .limit(10))
339
+
340
+ # --- Display Chat Preview ---
341
+
342
+ def display_chat_preview(chat):
343
+ st.subheader(f"Session: {chat['session_id'][:8]}...")
344
+ last_updated = chat.get('last_updated', datetime.utcnow())
345
+ st.caption(f"Last updated: {last_updated.strftime('%Y-%m-%d %H:%M:%S')}")
346
+
347
+ for message in chat.get('messages', [])[:3]:
348
+ with st.chat_message(message['role']):
349
+ st.markdown(f"**{message['role'].capitalize()}**: {message['content'][:100]}...")
350
+
351
+ st.divider()
352
+
353
+ # --- Delete Chat ---
354
+
355
+ def delete_chat(session_id):
356
+ try:
357
+ result = conversation_history.delete_one({"session_id": session_id})
358
+ if result.deleted_count > 0:
359
+ st.success(f"Chat {session_id[:8]}... deleted successfully.")
360
+ else:
361
+ st.error("Failed to delete chat. Please try again.")
362
+ except Exception as e:
363
+ st.error(f"Error deleting chat: {str(e)}")
364
+
365
+ # --- Cleanup Old Chats ---
366
+
367
+ def cleanup_old_chats():
368
+ try:
369
+ cutoff_time = datetime.utcnow() - timedelta(minutes=5)
370
+ result = conversation_history.delete_many({"last_updated": {"$lt": cutoff_time}})
371
+ return result.deleted_count
372
+ except Exception as e:
373
+ print(f"Error during chat cleanup: {str(e)}")
374
+ return None
375
+
376
+ # --- GPT Response Function ---
377
+
378
+ def get_gpt_response(prompt, context="", system_message=None):
379
+ """
380
+ Generates a response from the GPT model based on the user prompt and retrieved context.
381
+ Incorporates the global common memory and optional system message.
382
+ Returns a tuple of (response, is_uncertain).
383
+ """
384
+ try:
385
+ common_memory = get_global_common_memory()
386
+ system_msg = (
387
+ "You are a helpful assistant. Use the following context and global common memory "
388
+ "to inform your responses, but don't mention them explicitly unless directly relevant to the user's question."
389
+ )
390
+
391
+ if system_message:
392
+ system_msg += f"\n\nOperator Instructions:\n{system_message}"
393
+
394
+ if common_memory:
395
+ memory_str = "\n".join(common_memory)
396
+ system_msg += f"\n\nGlobal Common Memory:\n{memory_str}"
397
+
398
+ messages = [
399
+ {"role": "system", "content": system_msg},
400
+ {"role": "user", "content": f"Context: {context}\n\nUser query: {prompt}"}
401
+ ]
402
+
403
+ completion = openai_client.chat.completions.create(
404
+ model="gpt-4o-mini",
405
+ messages=messages
406
+ )
407
+ print(completion)
408
+ response = completion.choices[0].message.content.strip()
409
+
410
+ # TODO: Implement your logic to determine if the response is uncertain
411
+ is_uncertain = False # Example placeholder
412
+
413
+ return response, is_uncertain
414
+ except Exception as e:
415
+ st.error(f"Error generating response: {str(e)}")
416
+ return None, True # Indicates uncertainty due to error
417
+
418
+ # --- View Full Chat Function ---
419
+ def view_full_chat(session_id):
420
+ """Display the full chat and provide takeover functionality."""
421
+ # Add a "Go to Dashboard" button at the top
422
+ if st.button("🏠 Go to Dashboard"):
423
+ st.session_state.pop('selected_chat', None)
424
+ st.rerun()
425
+
426
+ conversation = conversation_history.find_one({"session_id": session_id})
427
+ if not conversation:
428
+ st.error("Chat not found.")
429
+ return
430
+
431
+ st.header(f"Full Chat - Session ID: {conversation['session_id'][:8]}...")
432
+ st.caption(f"Last updated: {conversation.get('last_updated', datetime.utcnow()).strftime('%Y-%m-%d %H:%M:%S')}")
433
+
434
+ for message in conversation.get('messages', []):
435
+ with st.chat_message(message['role']):
436
+ st.markdown(f"**{message['role'].capitalize()}**: {message['content']}")
437
+
438
+ # Takeover functionality
439
+ takeover_doc = db.takeover_status.find_one({"session_id": session_id})
440
+ takeover_active = takeover_doc.get("active", False) if takeover_doc else False
441
+
442
+ if takeover_active:
443
+ if st.button("Deactivate Takeover"):
444
+ deactivate_takeover(session_id)
445
+ st.success("Takeover deactivated.")
446
+ st.rerun()
447
+ else:
448
+ if st.button("Activate Takeover"):
449
+ activate_takeover(session_id)
450
+ st.success("Takeover activated.")
451
+ st.rerun()
452
+
453
+ # If takeover is active, allow operator to send messages
454
+ if takeover_active:
455
+ with st.form(key=f"admin_message_form_{session_id}"):
456
+ admin_message = st.text_input("Enter message to send to the user:")
457
+ submit_button = st.form_submit_button("Send Message")
458
+
459
+ if submit_button and admin_message:
460
+ send_admin_message(session_id, admin_message)
461
+ st.success("Admin message sent.")
462
+ st.rerun()
463
+
464
+ # --- Main Function ---
465
+ def main():
466
+ try:
467
+ if 'selected_chat' in st.session_state:
468
+ view_full_chat(st.session_state['selected_chat'])
469
+ else:
470
+ admin_page()
471
+ except (RerunException, StopException):
472
+ raise
473
+ except Exception as e:
474
+ st.error(f"An unexpected error occurred: {str(e)}")
475
+
476
+ if __name__ == "__main__":
477
+ main()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ python-dotenv
2
+ streamlit
3
+ openai
4
+ pymongo
5
+ pinecone-client
6
+ uuid