rdune71 commited on
Commit
fc3fdb8
·
1 Parent(s): 8cfe660

Implement comprehensive analytics system for UI/UX improvements

Browse files
app.py CHANGED
@@ -14,14 +14,14 @@ from core.session import session_manager
14
  from core.memory import check_redis_health
15
  from core.errors import translate_error
16
  from core.personality import personality
 
 
17
  import logging
18
 
19
  # Set up logging
20
  logging.basicConfig(level=logging.INFO)
21
  logger = logging.getLogger(__name__)
22
 
23
- st.set_page_config(page_title="CosmicCat AI Assistant", page_icon="🐱", layout="wide")
24
-
25
  # Initialize session state
26
  if "messages" not in st.session_state:
27
  st.session_state.messages = []
@@ -33,6 +33,18 @@ if "cosmic_mode" not in st.session_state:
33
  st.session_state.cosmic_mode = True
34
  if "show_welcome" not in st.session_state:
35
  st.session_state.show_welcome = True
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  # Sidebar
38
  with st.sidebar:
@@ -48,12 +60,23 @@ with st.sidebar:
48
  selected_model_name = st.selectbox(
49
  "Select Model",
50
  options=list(model_options.keys()),
51
- index=0
 
 
 
52
  )
53
  st.session_state.selected_model = model_options[selected_model_name]
54
 
 
 
 
 
 
55
  # Cosmic mode toggle
56
- st.session_state.cosmic_mode = st.checkbox("Enable Cosmic Mode", value=st.session_state.cosmic_mode)
 
 
 
57
 
58
  st.divider()
59
 
@@ -62,30 +85,55 @@ with st.sidebar:
62
  ngrok_url_input = st.text_input(
63
  "Ollama Server URL",
64
  value=st.session_state.ngrok_url_temp,
65
- help="Enter your ngrok URL"
 
 
 
66
  )
67
 
68
  if ngrok_url_input != st.session_state.ngrok_url_temp:
69
  st.session_state.ngrok_url_temp = ngrok_url_input
70
  st.success("✅ URL updated!")
 
 
 
71
 
72
  if st.button("📡 Test Connection"):
 
 
73
  try:
74
  from core.providers.ollama import OllamaProvider
75
  ollama_provider = OllamaProvider(st.session_state.selected_model)
76
  is_valid = ollama_provider.validate_model()
 
 
77
  if is_valid:
78
  st.success("✅ Connection successful!")
 
 
 
 
79
  else:
80
  st.error("❌ Model validation failed")
 
 
 
81
  except Exception as e:
 
82
  st.error(f"❌ Error: {str(e)[:50]}...")
 
 
 
 
 
83
 
84
  if st.button("🗑️ Clear History"):
 
85
  st.session_state.messages = []
86
  # Also clear backend session
87
  session_manager.clear_session("default_user")
88
  st.success("History cleared!")
 
89
 
90
  st.divider()
91
 
@@ -131,16 +179,32 @@ with st.sidebar:
131
  # Add wake-up button if scaled to zero or initializing
132
  if "scaled to zero" in status_message.lower() or "initializing" in status_message.lower():
133
  if st.button("⚡ Wake Up HF Endpoint", key="wake_up_hf"):
 
134
  with st.spinner("Attempting to wake up HF endpoint... This may take 2-4 minutes during initialization..."):
 
135
  if hf_monitor.attempt_wake_up():
 
136
  st.success("✅ Wake-up request sent! The endpoint should be initializing now. Try your request again in a moment.")
 
 
 
 
137
  time.sleep(3)
138
  st.experimental_rerun()
139
  else:
 
140
  st.error("❌ Failed to send wake-up request. Please try again or wait for initialization to complete.")
 
 
 
 
141
 
142
  except Exception as e:
143
  st.info(f"🤗 HF Endpoint: Error checking status - {str(e)}")
 
 
 
 
144
 
145
  # Redis Status
146
  try:
@@ -153,10 +217,25 @@ with st.sidebar:
153
 
154
  st.divider()
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  # Debug Info
157
  st.subheader("🐛 Debug Info")
158
  st.markdown(f"**Environment:** {'HF Space' if config.is_hf_space else 'Local'}")
159
  st.markdown(f"**Model:** {st.session_state.selected_model}")
 
160
 
161
  # Main interface
162
  st.title("🐱 CosmicCat AI Assistant")
@@ -181,6 +260,9 @@ for message in st.session_state.messages:
181
  user_input = st.chat_input("Type your message here...", key="chat_input")
182
 
183
  if user_input:
 
 
 
184
  chat_handler.process_user_message(user_input, selected_model_name)
185
 
186
  # About tab
@@ -206,3 +288,14 @@ with tab1:
206
  - **Secondary model**: HF Endpoint (advanced processing)
207
  - **Memory system**: Redis-based session management
208
  """)
 
 
 
 
 
 
 
 
 
 
 
 
14
  from core.memory import check_redis_health
15
  from core.errors import translate_error
16
  from core.personality import personality
17
+ from src.analytics.user_logger import user_logger
18
+ from src.analytics.session_analytics import session_analytics
19
  import logging
20
 
21
  # Set up logging
22
  logging.basicConfig(level=logging.INFO)
23
  logger = logging.getLogger(__name__)
24
 
 
 
25
  # Initialize session state
26
  if "messages" not in st.session_state:
27
  st.session_state.messages = []
 
33
  st.session_state.cosmic_mode = True
34
  if "show_welcome" not in st.session_state:
35
  st.session_state.show_welcome = True
36
+ if "session_id" not in st.session_state:
37
+ st.session_state.session_id = f"sess_{int(time.time())}_{os.urandom(4).hex()}"
38
+
39
+ # Start session tracking
40
+ session_analytics.start_session_tracking("default_user", st.session_state.session_id)
41
+
42
+ st.set_page_config(page_title="CosmicCat AI Assistant", page_icon="🐱", layout="wide")
43
+
44
+ # Log page view
45
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "page_view", {
46
+ "page": "main_chat"
47
+ })
48
 
49
  # Sidebar
50
  with st.sidebar:
 
60
  selected_model_name = st.selectbox(
61
  "Select Model",
62
  options=list(model_options.keys()),
63
+ index=0,
64
+ on_change=lambda: session_analytics.track_interaction("default_user", st.session_state.session_id, "model_selection", {
65
+ "selected_model": st.session_state.selected_model if 'selected_model' in st.session_state else model_options[list(model_options.keys())[0]]
66
+ })
67
  )
68
  st.session_state.selected_model = model_options[selected_model_name]
69
 
70
+ # Log model selection
71
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "model_selection", {
72
+ "selected_model": st.session_state.selected_model
73
+ })
74
+
75
  # Cosmic mode toggle
76
+ st.session_state.cosmic_mode = st.checkbox("Enable Cosmic Mode", value=st.session_state.cosmic_mode,
77
+ on_change=lambda: session_analytics.track_interaction("default_user", st.session_state.session_id, "cosmic_mode_toggle", {
78
+ "enabled": st.session_state.cosmic_mode
79
+ }))
80
 
81
  st.divider()
82
 
 
85
  ngrok_url_input = st.text_input(
86
  "Ollama Server URL",
87
  value=st.session_state.ngrok_url_temp,
88
+ help="Enter your ngrok URL",
89
+ on_change=lambda: session_analytics.track_interaction("default_user", st.session_state.session_id, "url_update", {
90
+ "url_changed": ngrok_url_input != st.session_state.ngrok_url_temp
91
+ })
92
  )
93
 
94
  if ngrok_url_input != st.session_state.ngrok_url_temp:
95
  st.session_state.ngrok_url_temp = ngrok_url_input
96
  st.success("✅ URL updated!")
97
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "url_updated", {
98
+ "new_url": ngrok_url_input
99
+ })
100
 
101
  if st.button("📡 Test Connection"):
102
+ start_time = time.time()
103
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "test_connection_click")
104
  try:
105
  from core.providers.ollama import OllamaProvider
106
  ollama_provider = OllamaProvider(st.session_state.selected_model)
107
  is_valid = ollama_provider.validate_model()
108
+ end_time = time.time()
109
+
110
  if is_valid:
111
  st.success("✅ Connection successful!")
112
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "connection_success", {
113
+ "response_time": end_time - start_time
114
+ })
115
+ user_logger.log_performance_metric("default_user", "connection_test", end_time - start_time)
116
  else:
117
  st.error("❌ Model validation failed")
118
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "connection_failed", {
119
+ "error": "model_validation_failed"
120
+ })
121
  except Exception as e:
122
+ end_time = time.time()
123
  st.error(f"❌ Error: {str(e)[:50]}...")
124
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "connection_error", {
125
+ "error": str(e)[:100],
126
+ "response_time": end_time - start_time
127
+ })
128
+ user_logger.log_error("default_user", "connection_test", str(e))
129
 
130
  if st.button("🗑️ Clear History"):
131
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "clear_history_click")
132
  st.session_state.messages = []
133
  # Also clear backend session
134
  session_manager.clear_session("default_user")
135
  st.success("History cleared!")
136
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "history_cleared")
137
 
138
  st.divider()
139
 
 
179
  # Add wake-up button if scaled to zero or initializing
180
  if "scaled to zero" in status_message.lower() or "initializing" in status_message.lower():
181
  if st.button("⚡ Wake Up HF Endpoint", key="wake_up_hf"):
182
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "wake_up_hf_click")
183
  with st.spinner("Attempting to wake up HF endpoint... This may take 2-4 minutes during initialization..."):
184
+ start_time = time.time()
185
  if hf_monitor.attempt_wake_up():
186
+ end_time = time.time()
187
  st.success("✅ Wake-up request sent! The endpoint should be initializing now. Try your request again in a moment.")
188
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "hf_wake_up_success", {
189
+ "response_time": end_time - start_time
190
+ })
191
+ user_logger.log_performance_metric("default_user", "hf_wake_up", end_time - start_time)
192
  time.sleep(3)
193
  st.experimental_rerun()
194
  else:
195
+ end_time = time.time()
196
  st.error("❌ Failed to send wake-up request. Please try again or wait for initialization to complete.")
197
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "hf_wake_up_failed", {
198
+ "response_time": end_time - start_time
199
+ })
200
+ user_logger.log_error("default_user", "hf_wake_up", "Failed to wake up HF endpoint")
201
 
202
  except Exception as e:
203
  st.info(f"🤗 HF Endpoint: Error checking status - {str(e)}")
204
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "hf_status_error", {
205
+ "error": str(e)
206
+ })
207
+ user_logger.log_error("default_user", "hf_status_check", str(e))
208
 
209
  # Redis Status
210
  try:
 
217
 
218
  st.divider()
219
 
220
+ # Feedback Section
221
+ st.subheader("⭐ Feedback")
222
+ rating = st.radio("How would you rate your experience?", [1, 2, 3, 4, 5], horizontal=True)
223
+ feedback_comment = st.text_area("Additional comments (optional):")
224
+ if st.button("Submit Feedback"):
225
+ user_logger.log_feedback("default_user", rating, feedback_comment)
226
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "feedback_submitted", {
227
+ "rating": rating,
228
+ "has_comment": bool(feedback_comment)
229
+ })
230
+ st.success("Thank you for your feedback! 🙏")
231
+
232
+ st.divider()
233
+
234
  # Debug Info
235
  st.subheader("🐛 Debug Info")
236
  st.markdown(f"**Environment:** {'HF Space' if config.is_hf_space else 'Local'}")
237
  st.markdown(f"**Model:** {st.session_state.selected_model}")
238
+ st.markdown(f"**Session ID:** {st.session_state.session_id}")
239
 
240
  # Main interface
241
  st.title("🐱 CosmicCat AI Assistant")
 
260
  user_input = st.chat_input("Type your message here...", key="chat_input")
261
 
262
  if user_input:
263
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "chat_message_sent", {
264
+ "message_length": len(user_input)
265
+ })
266
  chat_handler.process_user_message(user_input, selected_model_name)
267
 
268
  # About tab
 
288
  - **Secondary model**: HF Endpoint (advanced processing)
289
  - **Memory system**: Redis-based session management
290
  """)
291
+
292
+ # Log about page view
293
+ session_analytics.track_interaction("default_user", st.session_state.session_id, "about_page_view")
294
+
295
+ # End session tracking when app closes
296
+ def on_session_end():
297
+ session_analytics.end_session_tracking("default_user", st.session_state.session_id)
298
+
299
+ # Register cleanup function
300
+ import atexit
301
+ atexit.register(on_session_end)
src/analytics/redis_logger.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+ import logging
4
+ from typing import Dict, List, Any, Optional
5
+ from datetime import datetime, timedelta
6
+ from core.redis_client import redis_client
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class RedisAnalyticsLogger:
11
+ """Redis-based analytics storage system"""
12
+
13
+ def __init__(self):
14
+ self.redis_client = redis_client.get_client()
15
+
16
+ def store_event(self, event_type: str, data: Dict[str, Any], user_id: Optional[str] = None):
17
+ """Store an analytics event in Redis"""
18
+ try:
19
+ event_data = {
20
+ "event_type": event_type,
21
+ "timestamp": datetime.now().isoformat(),
22
+ "data": data
23
+ }
24
+
25
+ # Create unique key with timestamp
26
+ timestamp = int(time.time() * 1000) # Millisecond precision
27
+ key = f"analytics:{event_type}:{timestamp}"
28
+ if user_id:
29
+ key = f"analytics:{user_id}:{event_type}:{timestamp}"
30
+
31
+ # Store event data
32
+ self.redis_client.setex(key, 2592000, json.dumps(event_data)) # 30 days expiry
33
+
34
+ # Add to sorted set for time-based queries
35
+ index_key = f"analytics:index:{event_type}"
36
+ self.redis_client.zadd(index_key, {key: timestamp})
37
+
38
+ logger.debug(f"Stored analytics event: {key}")
39
+ return True
40
+ except Exception as e:
41
+ logger.error(f"Failed to store analytics event: {e}")
42
+ return False
43
+
44
+ def get_events(self, event_type: str, user_id: Optional[str] = None,
45
+ start_time: Optional[datetime] = None,
46
+ end_time: Optional[datetime] = None,
47
+ limit: int = 100) -> List[Dict[str, Any]]:
48
+ """Retrieve analytics events"""
49
+ try:
50
+ # Determine index key
51
+ index_key = f"analytics:index:{event_type}"
52
+ if user_id:
53
+ index_key = f"analytics:{user_id}:index:{event_type}"
54
+
55
+ # Calculate time range
56
+ if start_time is None:
57
+ start_time = datetime.now() - timedelta(days=30)
58
+ if end_time is None:
59
+ end_time = datetime.now()
60
+
61
+ start_timestamp = int(start_time.timestamp() * 1000)
62
+ end_timestamp = int(end_time.timestamp() * 1000)
63
+
64
+ # Get event keys in time range
65
+ event_keys = self.redis_client.zrevrangebyscore(
66
+ index_key,
67
+ end_timestamp,
68
+ start_timestamp,
69
+ start=0,
70
+ num=limit
71
+ )
72
+
73
+ # Retrieve event data
74
+ events = []
75
+ for key in event_keys:
76
+ try:
77
+ data = self.redis_client.get(key)
78
+ if data:
79
+ events.append(json.loads(data))
80
+ except Exception as e:
81
+ logger.warning(f"Failed to retrieve event {key}: {e}")
82
+
83
+ return events
84
+ except Exception as e:
85
+ logger.error(f"Failed to retrieve analytics events: {e}")
86
+ return []
87
+
88
+ def get_event_count(self, event_type: str, user_id: Optional[str] = None,
89
+ start_time: Optional[datetime] = None,
90
+ end_time: Optional[datetime] = None) -> int:
91
+ """Get count of events in time range"""
92
+ try:
93
+ index_key = f"analytics:index:{event_type}"
94
+ if user_id:
95
+ index_key = f"analytics:{user_id}:index:{event_type}"
96
+
97
+ if start_time is None:
98
+ start_time = datetime.now() - timedelta(days=30)
99
+ if end_time is None:
100
+ end_time = datetime.now()
101
+
102
+ start_timestamp = int(start_time.timestamp() * 1000)
103
+ end_timestamp = int(end_time.timestamp() * 1000)
104
+
105
+ return self.redis_client.zcount(index_key, start_timestamp, end_timestamp)
106
+ except Exception as e:
107
+ logger.error(f"Failed to count analytics events: {e}")
108
+ return 0
109
+
110
+ def aggregate_events(self, event_type: str, aggregation_field: str,
111
+ start_time: Optional[datetime] = None,
112
+ end_time: Optional[datetime] = None) -> Dict[str, int]:
113
+ """Aggregate events by a specific field"""
114
+ try:
115
+ events = self.get_events(event_type, start_time=start_time, end_time=end_time)
116
+ aggregation = {}
117
+
118
+ for event in events:
119
+ data = event.get("data", {})
120
+ field_value = data.get(aggregation_field)
121
+ if field_value:
122
+ aggregation[field_value] = aggregation.get(field_value, 0) + 1
123
+
124
+ return aggregation
125
+ except Exception as e:
126
+ logger.error(f"Failed to aggregate events: {e}")
127
+ return {}
128
+
129
+ # Global instance
130
+ redis_logger = RedisAnalyticsLogger()
src/analytics/session_analytics.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import json
3
+ import logging
4
+ from typing import Dict, List, Any, Optional
5
+ from datetime import datetime
6
+ from core.redis_client import redis_client
7
+ from src.analytics.user_logger import user_logger
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class SessionAnalytics:
12
+ """Session-level tracking and analytics"""
13
+
14
+ def __init__(self):
15
+ self.redis_client = redis_client.get_client()
16
+
17
+ def start_session_tracking(self, user_id: str, session_id: str):
18
+ """Start tracking a user session"""
19
+ try:
20
+ session_data = {
21
+ "user_id": user_id,
22
+ "session_id": session_id,
23
+ "start_time": datetime.now().isoformat(),
24
+ "interactions": [],
25
+ "duration": 0,
26
+ "page_views": 0,
27
+ "actions": {}
28
+ }
29
+
30
+ key = f"analytics:sessions:{session_id}"
31
+ self.redis_client.setex(key, 86400, json.dumps(session_data)) # 24 hours expiry
32
+
33
+ # Log session start
34
+ user_logger.log_user_action(user_id, "session_start", {
35
+ "session_id": session_id
36
+ })
37
+
38
+ logger.info(f"Started session tracking: {session_id}")
39
+ except Exception as e:
40
+ logger.error(f"Failed to start session tracking: {e}")
41
+
42
+ def track_interaction(self, user_id: str, session_id: str, interaction_type: str,
43
+ details: Dict[str, Any] = None):
44
+ """Track a user interaction within a session"""
45
+ try:
46
+ key = f"analytics:sessions:{session_id}"
47
+ session_data_str = self.redis_client.get(key)
48
+
49
+ if session_data_str:
50
+ session_data = json.loads(session_data_str)
51
+
52
+ # Add interaction
53
+ interaction = {
54
+ "type": interaction_type,
55
+ "timestamp": datetime.now().isoformat(),
56
+ "details": details or {}
57
+ }
58
+ session_data["interactions"].append(interaction)
59
+
60
+ # Update action counts
61
+ session_data["actions"][interaction_type] = session_data["actions"].get(interaction_type, 0) + 1
62
+
63
+ # Update page views for navigation events
64
+ if interaction_type == "page_view":
65
+ session_data["page_views"] += 1
66
+
67
+ # Update duration
68
+ start_time = datetime.fromisoformat(session_data["start_time"])
69
+ session_data["duration"] = (datetime.now() - start_time).total_seconds()
70
+
71
+ # Save updated session data
72
+ self.redis_client.setex(key, 86400, json.dumps(session_data))
73
+
74
+ # Log the interaction
75
+ user_logger.log_user_action(user_id, f"session_interaction_{interaction_type}", {
76
+ "session_id": session_id,
77
+ "details": details or {}
78
+ })
79
+
80
+ except Exception as e:
81
+ logger.error(f"Failed to track interaction: {e}")
82
+
83
+ def end_session_tracking(self, user_id: str, session_id: str):
84
+ """End session tracking and generate summary"""
85
+ try:
86
+ key = f"analytics:sessions:{session_id}"
87
+ session_data_str = self.redis_client.get(key)
88
+
89
+ if session_data_str:
90
+ session_data = json.loads(session_data_str)
91
+
92
+ # Update final duration
93
+ start_time = datetime.fromisoformat(session_data["start_time"])
94
+ session_data["end_time"] = datetime.now().isoformat()
95
+ session_data["duration"] = (datetime.now() - start_time).total_seconds()
96
+
97
+ # Save final session data
98
+ self.redis_client.setex(key, 2592000, json.dumps(session_data)) # 30 days expiry for completed sessions
99
+
100
+ # Log session end with summary
101
+ user_logger.log_user_action(user_id, "session_end", {
102
+ "session_id": session_id,
103
+ "duration": session_data["duration"],
104
+ "interactions": len(session_data["interactions"]),
105
+ "page_views": session_data["page_views"],
106
+ "actions": session_data["actions"]
107
+ })
108
+
109
+ logger.info(f"Ended session tracking: {session_id}")
110
+
111
+ except Exception as e:
112
+ logger.error(f"Failed to end session tracking: {e}")
113
+
114
+ def get_session_summary(self, session_id: str) -> Optional[Dict[str, Any]]:
115
+ """Get session summary data"""
116
+ try:
117
+ key = f"analytics:sessions:{session_id}"
118
+ session_data_str = self.redis_client.get(key)
119
+
120
+ if session_data_str:
121
+ return json.loads(session_data_str)
122
+ return None
123
+ except Exception as e:
124
+ logger.error(f"Failed to get session summary: {e}")
125
+ return None
126
+
127
+ def get_user_sessions(self, user_id: str, limit: int = 10) -> List[Dict[str, Any]]:
128
+ """Get recent sessions for a user"""
129
+ try:
130
+ # This would require a more complex indexing system
131
+ # For now, we'll return an empty list as this requires additional implementation
132
+ return []
133
+ except Exception as e:
134
+ logger.error(f"Failed to get user sessions: {e}")
135
+ return []
136
+
137
+ # Global instance
138
+ session_analytics = SessionAnalytics()
src/analytics/user_logger.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+ import logging
4
+ from typing import Dict, Any, Optional
5
+ from datetime import datetime
6
+ from core.redis_client import redis_client
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class UserLogger:
11
+ """Comprehensive user interaction logging system"""
12
+
13
+ def __init__(self):
14
+ self.redis_client = redis_client.get_client()
15
+
16
+ def log_user_action(self, user_id: str, action: str, details: Dict[str, Any] = None):
17
+ """Log a user action"""
18
+ try:
19
+ event_data = {
20
+ "user_id": user_id,
21
+ "action": action,
22
+ "timestamp": datetime.now().isoformat(),
23
+ "details": details or {}
24
+ }
25
+
26
+ # Store in Redis with expiration (30 days)
27
+ key = f"analytics:user_events:{user_id}:{int(time.time())}"
28
+ self.redis_client.setex(
29
+ key,
30
+ 2592000, # 30 days in seconds
31
+ json.dumps(event_data)
32
+ )
33
+
34
+ # Also store in global events
35
+ global_key = f"analytics:global_events:{int(time.time())}"
36
+ self.redis_client.setex(
37
+ global_key,
38
+ 2592000, # 30 days in seconds
39
+ json.dumps(event_data)
40
+ )
41
+
42
+ logger.info(f"Logged user action: {user_id} - {action}")
43
+ except Exception as e:
44
+ logger.error(f"Failed to log user action: {e}")
45
+
46
+ def log_ui_state(self, user_id: str, state: str, details: Dict[str, Any] = None):
47
+ """Log UI state changes"""
48
+ self.log_user_action(user_id, f"ui_state_{state}", details)
49
+
50
+ def log_performance_metric(self, user_id: str, metric_name: str, value: float, details: Dict[str, Any] = None):
51
+ """Log performance metrics"""
52
+ metric_details = {
53
+ "value": value,
54
+ "metric": metric_name
55
+ }
56
+ if details:
57
+ metric_details.update(details)
58
+
59
+ self.log_user_action(user_id, f"performance_{metric_name}", metric_details)
60
+
61
+ def log_error(self, user_id: str, error_type: str, error_message: str, details: Dict[str, Any] = None):
62
+ """Log errors"""
63
+ error_details = {
64
+ "error_type": error_type,
65
+ "error_message": error_message
66
+ }
67
+ if details:
68
+ error_details.update(details)
69
+
70
+ self.log_user_action(user_id, f"error_{error_type}", error_details)
71
+
72
+ def log_feedback(self, user_id: str, rating: int, comment: str = "", details: Dict[str, Any] = None):
73
+ """Log user feedback"""
74
+ feedback_details = {
75
+ "rating": rating,
76
+ "comment": comment
77
+ }
78
+ if details:
79
+ feedback_details.update(details)
80
+
81
+ self.log_user_action(user_id, "user_feedback", feedback_details)
82
+
83
+ # Global instance
84
+ user_logger = UserLogger()