Dhruv Pawar commited on
Commit
92a25bc
Β·
1 Parent(s): 2287eaf

Enhanced UI: Added collapsible sidebar toggle, increased chat area size, fixed streaming bugs

Browse files
Files changed (3) hide show
  1. config.py +40 -39
  2. core.py +127 -74
  3. main.py +129 -29
config.py CHANGED
@@ -2,6 +2,7 @@ import logging
2
  from pathlib import Path
3
  from enum import Enum
4
  from logging.handlers import RotatingFileHandler
 
5
 
6
  def setup_logging():
7
  """Setup advanced logging with rotation"""
@@ -40,29 +41,29 @@ logger = setup_logging()
40
 
41
  class AppConfig:
42
  """Centralized application configuration"""
43
- MAX_HISTORY_LENGTH: int = 10
44
- MAX_CONVERSATION_STORAGE: int = 1000
45
- DEFAULT_TEMPERATURE: float = 0.7
46
- MIN_TEMPERATURE: float = 0.0
47
- MAX_TEMPERATURE: float = 2.0
48
- DEFAULT_MAX_TOKENS: int = 4000
49
- MIN_TOKENS: int = 100
50
- MAX_TOKENS: int = 32000
51
- REQUEST_TIMEOUT: int = 60
52
- MAX_RETRIES: int = 3
53
- RETRY_DELAY: float = 1.0
54
- CACHE_SIZE: int = 100
55
- CACHE_TTL: int = 3600
56
- RATE_LIMIT_REQUESTS: int = 50
57
- RATE_LIMIT_WINDOW: int = 60
58
- EXPORT_DIR: Path = Path("exports")
59
- BACKUP_DIR: Path = Path("backups")
60
- MAX_EXPORT_SIZE_MB: int = 50
61
- THEME_PRIMARY: str = "purple"
62
- THEME_SECONDARY: str = "blue"
63
- AUTO_SAVE_INTERVAL: int = 300
64
- ENABLE_ANALYTICS: bool = True
65
- ANALYTICS_BATCH_SIZE: int = 10
66
 
67
  @classmethod
68
  def validate(cls) -> bool:
@@ -70,6 +71,7 @@ class AppConfig:
70
  assert cls.MIN_TEMPERATURE <= cls.DEFAULT_TEMPERATURE <= cls.MAX_TEMPERATURE
71
  assert cls.MIN_TOKENS <= cls.DEFAULT_MAX_TOKENS <= cls.MAX_TOKENS
72
  assert cls.MAX_HISTORY_LENGTH > 0
 
73
  return True
74
  except AssertionError as e:
75
  logger.error(f"Configuration validation failed: {e}")
@@ -77,9 +79,12 @@ class AppConfig:
77
 
78
  @classmethod
79
  def create_directories(cls) -> None:
80
- cls.EXPORT_DIR.mkdir(exist_ok=True)
81
- cls.BACKUP_DIR.mkdir(exist_ok=True)
82
- logger.info("Application directories initialized")
 
 
 
83
 
84
  AppConfig.create_directories()
85
  AppConfig.validate()
@@ -185,10 +190,13 @@ CUSTOM_CSS = """
185
  margin: 1rem 0;
186
  font-family: 'JetBrains Mono', monospace;
187
  transition: var(--transition);
188
- color: #2c3e50 !important;
189
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
190
  }
191
 
 
 
 
 
192
  .metrics-card strong {
193
  color: #1a1a1a !important;
194
  font-weight: 600;
@@ -208,19 +216,20 @@ CUSTOM_CSS = """
208
  box-shadow: var(--shadow-lg);
209
  }
210
 
211
- .analytics-panel h3 {
212
  color: white !important;
 
 
 
213
  margin-bottom: 1rem;
214
  font-size: 1.5rem;
215
  }
216
 
217
  .analytics-panel p {
218
- color: rgba(255,255,255,0.95) !important;
219
  line-height: 1.6;
220
  }
221
 
222
  .analytics-panel strong {
223
- color: white !important;
224
  font-weight: 600;
225
  }
226
 
@@ -258,14 +267,6 @@ CUSTOM_CSS = """
258
  .gr-button:hover {
259
  transform: translateY(-2px) !important;
260
  }
261
-
262
- .gr-markdown {
263
- color: #2c3e50 !important;
264
- }
265
-
266
- .gr-markdown strong {
267
- color: #1a1a1a !important;
268
- }
269
  """
270
 
271
- logger.info("Enhanced configuration initialized")
 
2
  from pathlib import Path
3
  from enum import Enum
4
  from logging.handlers import RotatingFileHandler
5
+ from typing import ClassVar
6
 
7
  def setup_logging():
8
  """Setup advanced logging with rotation"""
 
41
 
42
  class AppConfig:
43
  """Centralized application configuration"""
44
+ MAX_HISTORY_LENGTH: ClassVar[int] = 10
45
+ MAX_CONVERSATION_STORAGE: ClassVar[int] = 1000
46
+ DEFAULT_TEMPERATURE: ClassVar[float] = 0.7
47
+ MIN_TEMPERATURE: ClassVar[float] = 0.0
48
+ MAX_TEMPERATURE: ClassVar[float] = 2.0
49
+ DEFAULT_MAX_TOKENS: ClassVar[int] = 4000
50
+ MIN_TOKENS: ClassVar[int] = 100
51
+ MAX_TOKENS: ClassVar[int] = 32000
52
+ REQUEST_TIMEOUT: ClassVar[int] = 60
53
+ MAX_RETRIES: ClassVar[int] = 3
54
+ RETRY_DELAY: ClassVar[float] = 1.0
55
+ CACHE_SIZE: ClassVar[int] = 100
56
+ CACHE_TTL: ClassVar[int] = 3600
57
+ RATE_LIMIT_REQUESTS: ClassVar[int] = 50
58
+ RATE_LIMIT_WINDOW: ClassVar[int] = 60
59
+ EXPORT_DIR: ClassVar[Path] = Path("exports")
60
+ BACKUP_DIR: ClassVar[Path] = Path("backups")
61
+ MAX_EXPORT_SIZE_MB: ClassVar[int] = 50
62
+ THEME_PRIMARY: ClassVar[str] = "purple"
63
+ THEME_SECONDARY: ClassVar[str] = "blue"
64
+ AUTO_SAVE_INTERVAL: ClassVar[int] = 300
65
+ ENABLE_ANALYTICS: ClassVar[bool] = True
66
+ ANALYTICS_BATCH_SIZE: ClassVar[int] = 10
67
 
68
  @classmethod
69
  def validate(cls) -> bool:
 
71
  assert cls.MIN_TEMPERATURE <= cls.DEFAULT_TEMPERATURE <= cls.MAX_TEMPERATURE
72
  assert cls.MIN_TOKENS <= cls.DEFAULT_MAX_TOKENS <= cls.MAX_TOKENS
73
  assert cls.MAX_HISTORY_LENGTH > 0
74
+ logger.info("Configuration validation passed")
75
  return True
76
  except AssertionError as e:
77
  logger.error(f"Configuration validation failed: {e}")
 
79
 
80
  @classmethod
81
  def create_directories(cls) -> None:
82
+ try:
83
+ cls.EXPORT_DIR.mkdir(exist_ok=True, parents=True)
84
+ cls.BACKUP_DIR.mkdir(exist_ok=True, parents=True)
85
+ logger.info("Application directories initialized")
86
+ except Exception as e:
87
+ logger.error(f"Failed to create directories: {e}")
88
 
89
  AppConfig.create_directories()
90
  AppConfig.validate()
 
190
  margin: 1rem 0;
191
  font-family: 'JetBrains Mono', monospace;
192
  transition: var(--transition);
 
193
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
194
  }
195
 
196
+ .metrics-card * {
197
+ color: #2c3e50 !important;
198
+ }
199
+
200
  .metrics-card strong {
201
  color: #1a1a1a !important;
202
  font-weight: 600;
 
216
  box-shadow: var(--shadow-lg);
217
  }
218
 
219
+ .analytics-panel * {
220
  color: white !important;
221
+ }
222
+
223
+ .analytics-panel h3 {
224
  margin-bottom: 1rem;
225
  font-size: 1.5rem;
226
  }
227
 
228
  .analytics-panel p {
 
229
  line-height: 1.6;
230
  }
231
 
232
  .analytics-panel strong {
 
233
  font-weight: 600;
234
  }
235
 
 
267
  .gr-button:hover {
268
  transform: translateY(-2px) !important;
269
  }
 
 
 
 
 
 
 
 
270
  """
271
 
272
+ logger.info("Enhanced configuration initialized")
core.py CHANGED
@@ -13,6 +13,7 @@ from concurrent.futures import ThreadPoolExecutor
13
 
14
  from dotenv import load_dotenv
15
  from groq import Groq
 
16
 
17
  from config import logger, AppConfig, ReasoningMode, ModelConfig
18
 
@@ -53,6 +54,8 @@ class ResponseCache:
53
  """Clear cache"""
54
  with self.lock:
55
  self.cache.clear()
 
 
56
  logger.info("Cache cleared")
57
 
58
  def get_stats(self) -> Dict[str, int]:
@@ -96,7 +99,7 @@ class RateLimiter:
96
 
97
  @dataclass
98
  class ConversationMetrics:
99
- """Enhanced metrics with advanced tracking"""
100
  reasoning_depth: int = 0
101
  self_corrections: int = 0
102
  confidence_score: float = 0.0
@@ -116,27 +119,45 @@ class ConversationMetrics:
116
  mode_switches: int = 0
117
  peak_tokens: int = 0
118
  total_latency: float = 0.0
 
119
 
120
  def update_confidence(self) -> None:
121
  """Calculate confidence based on multiple factors"""
122
- depth_score = min(30, self.reasoning_depth * 5)
123
- correction_score = min(20, self.self_corrections * 10)
124
- speed_score = min(25, 25 / max(1, self.avg_response_time))
125
- consistency_score = 25
126
- self.confidence_score = min(95.0, depth_score + correction_score + speed_score + consistency_score)
 
127
 
128
  def update_tokens_per_second(self, tokens: int, time_taken: float) -> None:
129
  """Calculate tokens per second"""
130
- if time_taken > 0:
131
- self.tokens_per_second = tokens / time_taken
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
  def reset(self) -> None:
134
  """Reset metrics for new session"""
135
- self.__init__()
 
136
 
137
  def to_dict(self) -> Dict[str, Any]:
138
  """Convert to dictionary"""
139
- return asdict(self)
 
 
 
140
 
141
  @dataclass
142
  class ConversationEntry:
@@ -168,7 +189,7 @@ class ConversationEntry:
168
 
169
  def _generate_id(self) -> str:
170
  """Generate unique conversation ID"""
171
- content = f"{self.timestamp}{self.user_message}"
172
  return hashlib.md5(content.encode()).hexdigest()[:12]
173
 
174
  def to_dict(self) -> Dict[str, Any]:
@@ -191,7 +212,7 @@ class ConversationEntry:
191
  self.rating = rating
192
 
193
  def error_handler(func):
194
- """Enhanced error handling decorator with retries"""
195
  @wraps(func)
196
  def wrapper(*args, **kwargs):
197
  max_retries = AppConfig.MAX_RETRIES
@@ -199,27 +220,51 @@ def error_handler(func):
199
 
200
  for attempt in range(max_retries):
201
  try:
202
- return func(*args, **kwargs)
203
- except Exception as e:
204
- logger.error(f"Error in {func.__name__} (attempt {attempt+1}/{max_retries}): {str(e)}")
205
-
206
- if attempt < max_retries - 1:
207
- logger.info(f"Retrying in {retry_delay}s...")
208
- time.sleep(retry_delay)
209
- retry_delay *= 2
210
  else:
211
- error_msg = f"System Error: {str(e)}\n\n"
212
-
213
- if "api" in str(e).lower() or "key" in str(e).lower():
214
- error_msg += "Please verify your GROQ_API_KEY in the .env file."
215
- elif "rate" in str(e).lower() or "limit" in str(e).lower():
216
- error_msg += "Rate limit exceeded. Please wait a moment and try again."
217
- elif "timeout" in str(e).lower():
218
- error_msg += "Request timed out. Please try again."
219
- else:
220
- error_msg += "Please try again or contact support if the issue persists."
221
-
222
- return error_msg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  return wrapper
224
 
225
  @contextmanager
@@ -514,7 +559,7 @@ class ConversationExporter:
514
 
515
  @staticmethod
516
  def to_pdf(entries: List[ConversationEntry], filename: str) -> str:
517
- """Export to PDF format"""
518
  try:
519
  from reportlab.lib.pagesizes import letter
520
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
@@ -589,23 +634,25 @@ class ConversationExporter:
589
  story.append(Spacer(1, 0.1*inch))
590
 
591
  story.append(Paragraph("<b>User:</b>", user_style))
592
- user_msg = entry.user_message.replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br/>')
593
- if len(user_msg) > 3000:
594
- user_msg = user_msg[:3000] + "... (truncated)"
 
595
  story.append(Paragraph(user_msg, user_style))
596
  story.append(Spacer(1, 0.15*inch))
597
 
598
  story.append(Paragraph("<b>Assistant:</b>", ai_style))
599
- ai_resp = entry.ai_response.replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br/>')
600
- if len(ai_resp) > 5000:
601
- ai_resp = ai_resp[:5000] + "... (truncated)"
 
602
  story.append(Paragraph(ai_resp, ai_style))
603
 
604
  if i < len(entries):
605
  story.append(PageBreak())
606
 
607
  doc.build(story)
608
- logger.info(f"PDF exported to {filename}")
609
  return filename
610
 
611
  except ImportError:
@@ -613,7 +660,7 @@ class ConversationExporter:
613
  logger.error(error_msg)
614
  return ""
615
  except Exception as e:
616
- logger.error(f"PDF export failed: {e}")
617
  return ""
618
 
619
  @classmethod
@@ -628,9 +675,9 @@ class ConversationExporter:
628
  filename = AppConfig.EXPORT_DIR / f"conversation_{timestamp}.{ext}"
629
  result = cls.to_pdf(entries, str(filename))
630
  if result:
631
- return "PDF exported successfully! Check the exports folder.", str(filename)
632
  else:
633
- return "PDF export failed. Install reportlab: pip install reportlab", ""
634
 
635
  exporters = {
636
  "json": lambda: cls.to_json(entries),
@@ -701,9 +748,11 @@ class AdvancedReasoner:
701
 
702
  def _generate_cache_key(self, query: str, model: str, mode: str,
703
  temp: float, template: str) -> str:
704
- """Generate cache key for request"""
705
- content = f"{query}|{model}|{mode}|{temp:.2f}|{template}"
706
- return hashlib.sha256(content.encode()).hexdigest()
 
 
707
 
708
  def _calculate_reasoning_depth(self, response: str) -> int:
709
  """Calculate reasoning depth from response"""
@@ -726,18 +775,21 @@ class AdvancedReasoner:
726
  mode: ReasoningMode,
727
  template: str
728
  ) -> List[Dict[str, str]]:
729
- """Build message list for API call"""
730
  messages = [
731
  {"role": "system", "content": self.prompt_engine.SYSTEM_PROMPTS[mode]}
732
  ]
733
 
734
  recent_history = history[-AppConfig.MAX_HISTORY_LENGTH:] if history else []
735
  for msg in recent_history:
736
- clean_msg = {
737
- "role": msg.get("role"),
738
- "content": msg.get("content", "")
739
- }
740
- messages.append(clean_msg)
 
 
 
741
 
742
  enhanced_query = self.prompt_engine.build_prompt(query, mode, template)
743
  messages.append({"role": "user", "content": enhanced_query})
@@ -753,7 +805,7 @@ class AdvancedReasoner:
753
  "context": context
754
  }
755
  self.error_log.append(error_entry)
756
- self.metrics.error_count += 1
757
  logger.error(f"Error logged: {error_entry}")
758
 
759
  @error_handler
@@ -769,28 +821,28 @@ class AdvancedReasoner:
769
  prompt_template: str = "Custom",
770
  use_cache: bool = True
771
  ) -> Generator[str, None, None]:
772
- """Generate response with advanced features"""
773
 
774
  is_valid, error_msg = validate_input(query)
775
  if not is_valid:
776
- yield f"Validation Error: {error_msg}"
777
  return
778
 
779
  allowed, wait_time = self.rate_limiter.is_allowed()
780
  if not allowed:
781
- yield f"Rate Limit: Please wait {wait_time:.1f} seconds."
782
  return
783
 
784
  cache_key = self._generate_cache_key(query, model, reasoning_mode.value, temperature, prompt_template)
785
  if use_cache:
786
  cached_response = self.cache.get(cache_key)
787
  if cached_response:
788
- self.metrics.cache_hits += 1
789
  logger.info("Returning cached response")
790
  yield cached_response
791
  return
792
 
793
- self.metrics.cache_misses += 1
794
 
795
  with timer(f"Response generation for {model}"):
796
  start_time = time.time()
@@ -808,12 +860,14 @@ class AdvancedReasoner:
808
  stream=True,
809
  )
810
 
 
811
  for chunk in stream:
812
  if chunk.choices[0].delta.content:
813
  content = chunk.choices[0].delta.content
814
  full_response += content
815
  token_count += 1
816
- self.metrics.tokens_used += 1
 
817
  yield full_response
818
 
819
  except Exception as e:
@@ -825,9 +879,9 @@ class AdvancedReasoner:
825
  raise
826
 
827
  inference_time = time.time() - start_time
828
- self.metrics.reasoning_depth = self._calculate_reasoning_depth(full_response)
829
  self.metrics.update_tokens_per_second(token_count, inference_time)
830
- self.metrics.peak_tokens = max(self.metrics.peak_tokens, token_count)
831
 
832
  if enable_critique and len(full_response) > 150:
833
  messages.append({"role": "assistant", "content": full_response})
@@ -837,6 +891,7 @@ class AdvancedReasoner:
837
  })
838
 
839
  full_response += "\n\n---\n### Validation & Self-Critique\n"
 
840
 
841
  try:
842
  critique_stream = self.client.chat.completions.create(
@@ -854,19 +909,19 @@ class AdvancedReasoner:
854
  token_count += 1
855
  yield full_response
856
 
857
- self.metrics.self_corrections += 1
858
 
859
  except Exception as e:
860
  logger.warning(f"Critique phase failed: {e}")
861
 
862
  final_inference_time = time.time() - start_time
863
- self.metrics.inference_time = final_inference_time
864
- self.metrics.total_latency += final_inference_time
865
  self.response_times.append(final_inference_time)
866
- self.metrics.avg_response_time = sum(self.response_times) / len(self.response_times)
867
- self.metrics.last_updated = datetime.now().strftime("%H:%M:%S")
868
  self.metrics.update_confidence()
869
- self.metrics.total_conversations += 1
870
 
871
  self.model_usage[model] += 1
872
  self.mode_usage[reasoning_mode.value] += 1
@@ -894,15 +949,13 @@ class AdvancedReasoner:
894
 
895
  if len(self.conversation_history) % 10 == 0:
896
  try:
897
- self.exporter.create_backup(self.conversation_history)
898
  except Exception as e:
899
  logger.warning(f"Auto-backup failed: {e}")
900
 
901
  if len(self.conversation_history) > AppConfig.MAX_CONVERSATION_STORAGE:
902
  self.conversation_history = self.conversation_history[-AppConfig.MAX_CONVERSATION_STORAGE:]
903
  logger.info(f"Trimmed history to {AppConfig.MAX_CONVERSATION_STORAGE} entries")
904
-
905
- yield full_response
906
 
907
  def export_conversation(self, format_type: str, include_metadata: bool = True) -> Tuple[str, str]:
908
  """Export conversation history"""
@@ -952,8 +1005,8 @@ class AdvancedReasoner:
952
  "total_time": total_time,
953
  "avg_inference_time": self.metrics.avg_response_time,
954
  "peak_tokens": self.metrics.peak_tokens,
955
- "most_used_model": max(set(models), key=models.count),
956
- "most_used_mode": max(set(modes), key=modes.count),
957
  "cache_hits": self.metrics.cache_hits,
958
  "cache_misses": self.metrics.cache_misses,
959
  "error_count": self.metrics.error_count
@@ -963,7 +1016,7 @@ class AdvancedReasoner:
963
  """Clear conversation history and reset metrics"""
964
  if self.conversation_history:
965
  try:
966
- self.exporter.create_backup(self.conversation_history)
967
  except Exception as e:
968
  logger.warning(f"Failed to backup before clearing: {e}")
969
 
@@ -983,4 +1036,4 @@ class AdvancedReasoner:
983
  self.executor.shutdown(wait=False)
984
  logger.info("AdvancedReasoner cleanup completed")
985
  except:
986
- pass
 
13
 
14
  from dotenv import load_dotenv
15
  from groq import Groq
16
+ import groq
17
 
18
  from config import logger, AppConfig, ReasoningMode, ModelConfig
19
 
 
54
  """Clear cache"""
55
  with self.lock:
56
  self.cache.clear()
57
+ self.hits = 0
58
+ self.misses = 0
59
  logger.info("Cache cleared")
60
 
61
  def get_stats(self) -> Dict[str, int]:
 
99
 
100
  @dataclass
101
  class ConversationMetrics:
102
+ """Enhanced metrics with thread-safe operations"""
103
  reasoning_depth: int = 0
104
  self_corrections: int = 0
105
  confidence_score: float = 0.0
 
119
  mode_switches: int = 0
120
  peak_tokens: int = 0
121
  total_latency: float = 0.0
122
+ _lock: threading.Lock = field(default_factory=threading.Lock, init=False, repr=False)
123
 
124
  def update_confidence(self) -> None:
125
  """Calculate confidence based on multiple factors"""
126
+ with self._lock:
127
+ depth_score = min(30, self.reasoning_depth * 5)
128
+ correction_score = min(20, self.self_corrections * 10)
129
+ speed_score = min(25, 25 / max(1, self.avg_response_time))
130
+ consistency_score = 25
131
+ self.confidence_score = min(95.0, depth_score + correction_score + speed_score + consistency_score)
132
 
133
  def update_tokens_per_second(self, tokens: int, time_taken: float) -> None:
134
  """Calculate tokens per second"""
135
+ with self._lock:
136
+ if time_taken > 0:
137
+ self.tokens_per_second = tokens / time_taken
138
+
139
+ def increment_field(self, field_name: str, value: Any = 1) -> None:
140
+ """Thread-safe field increment"""
141
+ with self._lock:
142
+ current = getattr(self, field_name)
143
+ setattr(self, field_name, current + value)
144
+
145
+ def set_field(self, field_name: str, value: Any) -> None:
146
+ """Thread-safe field setter"""
147
+ with self._lock:
148
+ setattr(self, field_name, value)
149
 
150
  def reset(self) -> None:
151
  """Reset metrics for new session"""
152
+ with self._lock:
153
+ self.__init__()
154
 
155
  def to_dict(self) -> Dict[str, Any]:
156
  """Convert to dictionary"""
157
+ with self._lock:
158
+ data = asdict(self)
159
+ data.pop('_lock', None)
160
+ return data
161
 
162
  @dataclass
163
  class ConversationEntry:
 
189
 
190
  def _generate_id(self) -> str:
191
  """Generate unique conversation ID"""
192
+ content = f"{self.timestamp}{self.user_message[:100]}"
193
  return hashlib.md5(content.encode()).hexdigest()[:12]
194
 
195
  def to_dict(self) -> Dict[str, Any]:
 
212
  self.rating = rating
213
 
214
  def error_handler(func):
215
+ """Enhanced error handling decorator for generator functions"""
216
  @wraps(func)
217
  def wrapper(*args, **kwargs):
218
  max_retries = AppConfig.MAX_RETRIES
 
220
 
221
  for attempt in range(max_retries):
222
  try:
223
+ # Check if function is a generator
224
+ result = func(*args, **kwargs)
225
+ if hasattr(result, '__iter__') and hasattr(result, '__next__'):
226
+ # It's a generator, yield from it
227
+ yield from result
 
 
 
228
  else:
229
+ # Regular function, return result
230
+ return result
231
+ return # Exit after successful completion
232
+
233
+ except groq.APIConnectionError as e:
234
+ error_msg = f"πŸ”Œ **Connection Error**: Cannot reach Groq API.\n\n"
235
+ error_msg += "Please check your internet connection and try again."
236
+ logger.error(f"API Connection Error in {func.__name__}: {str(e)}")
237
+
238
+ except groq.RateLimitError as e:
239
+ error_msg = f"⏱️ **Rate Limit Exceeded**: Too many requests.\n\n"
240
+ error_msg += "Please wait a moment and try again."
241
+ logger.error(f"Rate Limit Error in {func.__name__}: {str(e)}")
242
+
243
+ except groq.AuthenticationError as e:
244
+ error_msg = f"πŸ” **Authentication Error**: Invalid API key.\n\n"
245
+ error_msg += "Please verify your GROQ_API_KEY in the .env file."
246
+ logger.error(f"Authentication Error in {func.__name__}: {str(e)}")
247
+ yield error_msg
248
+ return # Don't retry authentication errors
249
+
250
+ except groq.APIStatusError as e:
251
+ error_msg = f"⚠️ **API Error** (Status {e.status_code}):\n\n"
252
+ error_msg += f"{str(e)}\n\nPlease try again or select a different model."
253
+ logger.error(f"API Status Error in {func.__name__}: {str(e)}")
254
+
255
+ except Exception as e:
256
+ error_msg = f"❌ **System Error**: {str(e)}\n\n"
257
+ error_msg += "Please try again or contact support if the issue persists."
258
+ logger.error(f"Unexpected error in {func.__name__}: {str(e)}", exc_info=True)
259
+
260
+ if attempt < max_retries - 1:
261
+ logger.info(f"Retrying in {retry_delay}s... (attempt {attempt+1}/{max_retries})")
262
+ time.sleep(retry_delay)
263
+ retry_delay *= 2
264
+ else:
265
+ yield error_msg
266
+ return
267
+
268
  return wrapper
269
 
270
  @contextmanager
 
559
 
560
  @staticmethod
561
  def to_pdf(entries: List[ConversationEntry], filename: str) -> str:
562
+ """Export to PDF format with memory optimization"""
563
  try:
564
  from reportlab.lib.pagesizes import letter
565
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
 
634
  story.append(Spacer(1, 0.1*inch))
635
 
636
  story.append(Paragraph("<b>User:</b>", user_style))
637
+ # Escape and truncate for memory efficiency
638
+ user_msg = entry.user_message.replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br/>')[:3000]
639
+ if len(entry.user_message) > 3000:
640
+ user_msg += "... (truncated)"
641
  story.append(Paragraph(user_msg, user_style))
642
  story.append(Spacer(1, 0.15*inch))
643
 
644
  story.append(Paragraph("<b>Assistant:</b>", ai_style))
645
+ # Escape and truncate for memory efficiency
646
+ ai_resp = entry.ai_response.replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br/>')[:5000]
647
+ if len(entry.ai_response) > 5000:
648
+ ai_resp += "... (truncated)"
649
  story.append(Paragraph(ai_resp, ai_style))
650
 
651
  if i < len(entries):
652
  story.append(PageBreak())
653
 
654
  doc.build(story)
655
+ logger.info(f"PDF exported successfully to {filename}")
656
  return filename
657
 
658
  except ImportError:
 
660
  logger.error(error_msg)
661
  return ""
662
  except Exception as e:
663
+ logger.error(f"PDF export failed: {e}", exc_info=True)
664
  return ""
665
 
666
  @classmethod
 
675
  filename = AppConfig.EXPORT_DIR / f"conversation_{timestamp}.{ext}"
676
  result = cls.to_pdf(entries, str(filename))
677
  if result:
678
+ return f"βœ… PDF exported successfully! File: conversation_{timestamp}.pdf", str(filename)
679
  else:
680
+ return "❌ PDF export failed. Install reportlab: pip install reportlab", ""
681
 
682
  exporters = {
683
  "json": lambda: cls.to_json(entries),
 
748
 
749
  def _generate_cache_key(self, query: str, model: str, mode: str,
750
  temp: float, template: str) -> str:
751
+ """Generate stable cache key for request"""
752
+ # Normalize inputs for consistent key generation
753
+ normalized_query = query.strip().lower()[:500] # Limit length
754
+ content = f"{normalized_query}|{model}|{mode}|{temp:.2f}|{template}"
755
+ return hashlib.sha256(content.encode('utf-8')).hexdigest()
756
 
757
  def _calculate_reasoning_depth(self, response: str) -> int:
758
  """Calculate reasoning depth from response"""
 
775
  mode: ReasoningMode,
776
  template: str
777
  ) -> List[Dict[str, str]]:
778
+ """Build message list for API call with validation"""
779
  messages = [
780
  {"role": "system", "content": self.prompt_engine.SYSTEM_PROMPTS[mode]}
781
  ]
782
 
783
  recent_history = history[-AppConfig.MAX_HISTORY_LENGTH:] if history else []
784
  for msg in recent_history:
785
+ # Validate message structure
786
+ if isinstance(msg, dict) and "role" in msg and "content" in msg:
787
+ role = msg.get("role")
788
+ content = msg.get("content", "")
789
+
790
+ # Only add valid user/assistant messages
791
+ if role in ["user", "assistant"] and content:
792
+ messages.append({"role": role, "content": str(content)})
793
 
794
  enhanced_query = self.prompt_engine.build_prompt(query, mode, template)
795
  messages.append({"role": "user", "content": enhanced_query})
 
805
  "context": context
806
  }
807
  self.error_log.append(error_entry)
808
+ self.metrics.increment_field("error_count")
809
  logger.error(f"Error logged: {error_entry}")
810
 
811
  @error_handler
 
821
  prompt_template: str = "Custom",
822
  use_cache: bool = True
823
  ) -> Generator[str, None, None]:
824
+ """Generate response with advanced features - FIXED for streaming"""
825
 
826
  is_valid, error_msg = validate_input(query)
827
  if not is_valid:
828
+ yield f"⚠️ Validation Error: {error_msg}"
829
  return
830
 
831
  allowed, wait_time = self.rate_limiter.is_allowed()
832
  if not allowed:
833
+ yield f"⏱️ Rate Limit: Please wait {wait_time:.1f} seconds."
834
  return
835
 
836
  cache_key = self._generate_cache_key(query, model, reasoning_mode.value, temperature, prompt_template)
837
  if use_cache:
838
  cached_response = self.cache.get(cache_key)
839
  if cached_response:
840
+ self.metrics.increment_field("cache_hits")
841
  logger.info("Returning cached response")
842
  yield cached_response
843
  return
844
 
845
+ self.metrics.increment_field("cache_misses")
846
 
847
  with timer(f"Response generation for {model}"):
848
  start_time = time.time()
 
860
  stream=True,
861
  )
862
 
863
+ # FIXED: Only yield new content, not full_response repeatedly
864
  for chunk in stream:
865
  if chunk.choices[0].delta.content:
866
  content = chunk.choices[0].delta.content
867
  full_response += content
868
  token_count += 1
869
+ self.metrics.increment_field("tokens_used")
870
+ # Yield only the accumulated response so far
871
  yield full_response
872
 
873
  except Exception as e:
 
879
  raise
880
 
881
  inference_time = time.time() - start_time
882
+ self.metrics.set_field("reasoning_depth", self._calculate_reasoning_depth(full_response))
883
  self.metrics.update_tokens_per_second(token_count, inference_time)
884
+ self.metrics.set_field("peak_tokens", max(self.metrics.peak_tokens, token_count))
885
 
886
  if enable_critique and len(full_response) > 150:
887
  messages.append({"role": "assistant", "content": full_response})
 
891
  })
892
 
893
  full_response += "\n\n---\n### Validation & Self-Critique\n"
894
+ yield full_response
895
 
896
  try:
897
  critique_stream = self.client.chat.completions.create(
 
909
  token_count += 1
910
  yield full_response
911
 
912
+ self.metrics.increment_field("self_corrections")
913
 
914
  except Exception as e:
915
  logger.warning(f"Critique phase failed: {e}")
916
 
917
  final_inference_time = time.time() - start_time
918
+ self.metrics.set_field("inference_time", final_inference_time)
919
+ self.metrics.increment_field("total_latency", final_inference_time)
920
  self.response_times.append(final_inference_time)
921
+ self.metrics.set_field("avg_response_time", sum(self.response_times) / len(self.response_times))
922
+ self.metrics.set_field("last_updated", datetime.now().strftime("%H:%M:%S"))
923
  self.metrics.update_confidence()
924
+ self.metrics.increment_field("total_conversations")
925
 
926
  self.model_usage[model] += 1
927
  self.mode_usage[reasoning_mode.value] += 1
 
949
 
950
  if len(self.conversation_history) % 10 == 0:
951
  try:
952
+ self.executor.submit(self.exporter.create_backup, self.conversation_history.copy())
953
  except Exception as e:
954
  logger.warning(f"Auto-backup failed: {e}")
955
 
956
  if len(self.conversation_history) > AppConfig.MAX_CONVERSATION_STORAGE:
957
  self.conversation_history = self.conversation_history[-AppConfig.MAX_CONVERSATION_STORAGE:]
958
  logger.info(f"Trimmed history to {AppConfig.MAX_CONVERSATION_STORAGE} entries")
 
 
959
 
960
  def export_conversation(self, format_type: str, include_metadata: bool = True) -> Tuple[str, str]:
961
  """Export conversation history"""
 
1005
  "total_time": total_time,
1006
  "avg_inference_time": self.metrics.avg_response_time,
1007
  "peak_tokens": self.metrics.peak_tokens,
1008
+ "most_used_model": max(set(models), key=models.count) if models else "N/A",
1009
+ "most_used_mode": max(set(modes), key=modes.count) if modes else "N/A",
1010
  "cache_hits": self.metrics.cache_hits,
1011
  "cache_misses": self.metrics.cache_misses,
1012
  "error_count": self.metrics.error_count
 
1016
  """Clear conversation history and reset metrics"""
1017
  if self.conversation_history:
1018
  try:
1019
+ self.executor.submit(self.exporter.create_backup, self.conversation_history.copy())
1020
  except Exception as e:
1021
  logger.warning(f"Failed to backup before clearing: {e}")
1022
 
 
1036
  self.executor.shutdown(wait=False)
1037
  logger.info("AdvancedReasoner cleanup completed")
1038
  except:
1039
+ pass
main.py CHANGED
@@ -5,6 +5,60 @@ from core import AdvancedReasoner, PromptEngine
5
  # Initialize system
6
  reasoner = AdvancedReasoner()
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  def get_metrics_html() -> str:
9
  """Generate enhanced metrics HTML"""
10
  m = reasoner.metrics
@@ -34,7 +88,7 @@ def get_empty_analytics_html() -> str:
34
  </div>"""
35
 
36
  def create_ui() -> gr.Blocks:
37
- """Create enhanced Gradio interface"""
38
 
39
  with gr.Blocks(
40
  theme=gr.themes.Soft(
@@ -42,7 +96,7 @@ def create_ui() -> gr.Blocks:
42
  secondary_hue=AppConfig.THEME_SECONDARY,
43
  font=gr.themes.GoogleFont("Inter")
44
  ),
45
- css=CUSTOM_CSS,
46
  title="Advanced AI Reasoning System Pro"
47
  ) as demo:
48
 
@@ -55,18 +109,20 @@ def create_ui() -> gr.Blocks:
55
  <span class="badge">Bai et al. 2022 - Constitutional AI</span>
56
  <span class="badge">Enhanced with 6 Reasoning Modes</span>
57
  <span class="badge">Performance Optimized</span>
 
58
  </div>
59
  </div>
60
  """)
61
 
62
  with gr.Tabs():
63
- # Main Chat Tab
64
  with gr.Tab("Reasoning Workspace"):
65
  with gr.Row():
66
- with gr.Column(scale=3):
 
67
  chatbot = gr.Chatbot(
68
  label="Reasoning Workspace",
69
- height=550,
70
  show_copy_button=True,
71
  type="messages",
72
  avatar_images=(
@@ -86,8 +142,11 @@ def create_ui() -> gr.Blocks:
86
  submit_btn = gr.Button("Process", variant="primary", scale=2)
87
  clear_btn = gr.Button("Clear", scale=1)
88
  pdf_btn = gr.Button("Download PDF", scale=1)
 
 
89
 
90
- with gr.Column(scale=1):
 
91
  gr.Markdown("### Configuration")
92
 
93
  reasoning_mode = gr.Radio(
@@ -151,6 +210,9 @@ def create_ui() -> gr.Blocks:
151
  **Rate Limit:** {AppConfig.RATE_LIMIT_REQUESTS} req/{AppConfig.RATE_LIMIT_WINDOW}s
152
  **Max History:** {AppConfig.MAX_HISTORY_LENGTH} messages
153
  """)
 
 
 
154
 
155
  # Export Tab
156
  with gr.Tab("Export & History"):
@@ -231,12 +293,11 @@ def create_ui() -> gr.Blocks:
231
  clear_cache_btn = gr.Button("Clear Cache", variant="stop")
232
  cache_status = gr.Markdown("")
233
 
234
- # Define pdf_file_output BEFORE event handlers
235
- pdf_file_output = gr.File(visible=False)
236
 
237
- # Event handlers
238
  def process_message(message, history, mode, critique, model_name, temp, tokens, template, cache):
239
- if not message.strip():
 
240
  return history, get_metrics_html()
241
 
242
  history = history or []
@@ -247,30 +308,42 @@ def create_ui() -> gr.Blocks:
247
 
248
  history.append({"role": "assistant", "content": ""})
249
 
250
- for response in reasoner.generate_response(
251
- message, history[:-1], model_name, mode_enum,
252
- critique, temp, tokens, template, cache
253
- ):
254
- history[-1]["content"] = response
 
 
 
 
 
255
  yield history, get_metrics_html()
256
 
257
  def reset_chat():
 
258
  reasoner.clear_history()
259
  return [], get_metrics_html()
260
 
261
  def export_conv(format_type, include_metadata):
 
262
  content, filename = reasoner.export_conversation(format_type, include_metadata)
263
- return content, filename
 
 
 
264
 
265
  def download_chat_pdf():
266
- """Download current chat as PDF"""
267
  pdf_file = reasoner.export_current_chat_pdf()
268
  if pdf_file:
 
269
  return pdf_file
270
  return None
271
 
272
  def search_conv(keyword):
273
- if not keyword.strip():
 
274
  return "Please enter a search keyword."
275
 
276
  results = reasoner.search_conversations(keyword)
@@ -280,7 +353,8 @@ def create_ui() -> gr.Blocks:
280
  output = f"### Found {len(results)} result(s) for '{keyword}'\n\n"
281
  for idx, entry in results[:10]:
282
  output += f"**{idx + 1}.** {entry.timestamp} | {entry.model}\n"
283
- output += f"**User:** {entry.user_message[:100]}...\n\n"
 
284
 
285
  if len(results) > 10:
286
  output += f"\n*Showing first 10 of {len(results)} results*"
@@ -288,6 +362,7 @@ def create_ui() -> gr.Blocks:
288
  return output
289
 
290
  def refresh_analytics():
 
291
  analytics = reasoner.get_analytics()
292
  if not analytics:
293
  return get_empty_analytics_html(), "No cache data.", "No data", "No data"
@@ -309,6 +384,7 @@ def create_ui() -> gr.Blocks:
309
  - Hits: {analytics['cache_hits']}
310
  - Misses: {analytics['cache_misses']}
311
  - Total: {analytics['cache_hits'] + analytics['cache_misses']}
 
312
  """
313
 
314
  model_dist_html = f"**Model Usage:** {analytics['most_used_model']}"
@@ -317,18 +393,33 @@ def create_ui() -> gr.Blocks:
317
  return analytics_html, cache_html, model_dist_html, mode_dist_html
318
 
319
  def update_history_stats():
 
320
  count = len(reasoner.conversation_history)
321
  if count == 0:
322
  return "No conversations yet."
323
 
324
  return f"""**Total Conversations:** {count}
325
- **Session:** {reasoner.session_id[:8]}..."""
 
326
 
327
  def clear_cache_action():
 
328
  reasoner.cache.clear()
329
- return "Cache cleared successfully!"
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
331
- # Connect events
332
  submit_btn.click(
333
  process_message,
334
  [msg, chatbot, reasoning_mode, enable_critique, model, temperature, max_tokens, prompt_template, use_cache],
@@ -342,10 +433,15 @@ def create_ui() -> gr.Blocks:
342
  ).then(lambda: "", None, msg)
343
 
344
  clear_btn.click(reset_chat, None, [chatbot, metrics_display])
345
-
346
- # PDF Download button
347
  pdf_btn.click(download_chat_pdf, None, pdf_file_output)
348
 
 
 
 
 
 
 
 
349
  export_btn.click(export_conv, [export_format, include_meta], [export_output, download_file])
350
  search_btn.click(search_conv, search_input, search_results)
351
  refresh_btn.click(
@@ -355,7 +451,6 @@ def create_ui() -> gr.Blocks:
355
  )
356
  clear_cache_btn.click(clear_cache_action, None, cache_status)
357
 
358
- # Update history stats on load
359
  demo.load(update_history_stats, None, history_stats)
360
 
361
  return demo
@@ -363,8 +458,9 @@ def create_ui() -> gr.Blocks:
363
  if __name__ == "__main__":
364
  try:
365
  logger.info("="*60)
366
- logger.info("Starting Advanced AI Reasoning System Pro...")
367
- logger.info(f"Session ID: {reasoner.session_id}")
 
368
  logger.info("="*60)
369
 
370
  demo = create_ui()
@@ -376,6 +472,10 @@ if __name__ == "__main__":
376
  show_api=False,
377
  favicon_path=None
378
  )
 
 
379
  except Exception as e:
380
- logger.critical(f"Failed to start application: {e}", exc_info=True)
381
- raise
 
 
 
5
  # Initialize system
6
  reasoner = AdvancedReasoner()
7
 
8
+ # Enhanced CSS with sidebar toggle animation
9
+ SIDEBAR_CSS = CUSTOM_CSS + """
10
+ /* Sidebar toggle animations */
11
+ .sidebar-hidden {
12
+ display: none !important;
13
+ }
14
+
15
+ .toggle-btn {
16
+ position: fixed;
17
+ right: 20px;
18
+ top: 50%;
19
+ transform: translateY(-50%);
20
+ z-index: 1000;
21
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
22
+ color: white;
23
+ border: none;
24
+ border-radius: 50%;
25
+ width: 50px;
26
+ height: 50px;
27
+ cursor: pointer;
28
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
29
+ transition: all 0.3s ease;
30
+ font-size: 20px;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ }
35
+
36
+ .toggle-btn:hover {
37
+ transform: translateY(-50%) scale(1.1);
38
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
39
+ }
40
+
41
+ /* Fullscreen chat mode */
42
+ .fullscreen-chat .gradio-container {
43
+ max-width: 98% !important;
44
+ }
45
+
46
+ /* Smooth transitions */
47
+ .settings-column {
48
+ transition: all 0.3s ease-in-out;
49
+ }
50
+
51
+ /* Mobile responsive toggle */
52
+ @media (max-width: 768px) {
53
+ .toggle-btn {
54
+ width: 40px;
55
+ height: 40px;
56
+ font-size: 16px;
57
+ right: 10px;
58
+ }
59
+ }
60
+ """
61
+
62
  def get_metrics_html() -> str:
63
  """Generate enhanced metrics HTML"""
64
  m = reasoner.metrics
 
88
  </div>"""
89
 
90
  def create_ui() -> gr.Blocks:
91
+ """Create enhanced Gradio interface with collapsible sidebar"""
92
 
93
  with gr.Blocks(
94
  theme=gr.themes.Soft(
 
96
  secondary_hue=AppConfig.THEME_SECONDARY,
97
  font=gr.themes.GoogleFont("Inter")
98
  ),
99
+ css=SIDEBAR_CSS,
100
  title="Advanced AI Reasoning System Pro"
101
  ) as demo:
102
 
 
109
  <span class="badge">Bai et al. 2022 - Constitutional AI</span>
110
  <span class="badge">Enhanced with 6 Reasoning Modes</span>
111
  <span class="badge">Performance Optimized</span>
112
+ <span class="badge">πŸŽ›οΈ Collapsible Sidebar</span>
113
  </div>
114
  </div>
115
  """)
116
 
117
  with gr.Tabs():
118
+ # Main Chat Tab with Toggle
119
  with gr.Tab("Reasoning Workspace"):
120
  with gr.Row():
121
+ # CHAT AREA - Larger with toggle support
122
+ with gr.Column(scale=4):
123
  chatbot = gr.Chatbot(
124
  label="Reasoning Workspace",
125
+ height=750,
126
  show_copy_button=True,
127
  type="messages",
128
  avatar_images=(
 
142
  submit_btn = gr.Button("Process", variant="primary", scale=2)
143
  clear_btn = gr.Button("Clear", scale=1)
144
  pdf_btn = gr.Button("Download PDF", scale=1)
145
+ # ⭐ NEW: Toggle Sidebar Button
146
+ toggle_sidebar_btn = gr.Button("βš™οΈ Toggle Settings", scale=1, variant="secondary")
147
 
148
+ # COLLAPSIBLE SIDEBAR
149
+ with gr.Column(scale=1, visible=True, elem_classes="settings-column") as sidebar:
150
  gr.Markdown("### Configuration")
151
 
152
  reasoning_mode = gr.Radio(
 
210
  **Rate Limit:** {AppConfig.RATE_LIMIT_REQUESTS} req/{AppConfig.RATE_LIMIT_WINDOW}s
211
  **Max History:** {AppConfig.MAX_HISTORY_LENGTH} messages
212
  """)
213
+
214
+ # PDF download output
215
+ pdf_file_output = gr.File(label="Download Your PDF", visible=True)
216
 
217
  # Export Tab
218
  with gr.Tab("Export & History"):
 
293
  clear_cache_btn = gr.Button("Clear Cache", variant="stop")
294
  cache_status = gr.Markdown("")
295
 
296
+ # ==================== EVENT HANDLERS ====================
 
297
 
 
298
  def process_message(message, history, mode, critique, model_name, temp, tokens, template, cache):
299
+ """Process message with streaming"""
300
+ if not message or not message.strip():
301
  return history, get_metrics_html()
302
 
303
  history = history or []
 
308
 
309
  history.append({"role": "assistant", "content": ""})
310
 
311
+ try:
312
+ for response in reasoner.generate_response(
313
+ message, history[:-1], model_name, mode_enum,
314
+ critique, temp, tokens, template, cache
315
+ ):
316
+ history[-1]["content"] = response
317
+ yield history, get_metrics_html()
318
+ except Exception as e:
319
+ history[-1]["content"] = f"❌ **Error:** {str(e)}\n\nPlease try again."
320
+ logger.error(f"Error in process_message: {e}", exc_info=True)
321
  yield history, get_metrics_html()
322
 
323
  def reset_chat():
324
+ """Reset chat"""
325
  reasoner.clear_history()
326
  return [], get_metrics_html()
327
 
328
  def export_conv(format_type, include_metadata):
329
+ """Export conversation"""
330
  content, filename = reasoner.export_conversation(format_type, include_metadata)
331
+ if filename:
332
+ return content, filename
333
+ else:
334
+ return content, None
335
 
336
  def download_chat_pdf():
337
+ """Download PDF"""
338
  pdf_file = reasoner.export_current_chat_pdf()
339
  if pdf_file:
340
+ logger.info(f"PDF ready: {pdf_file}")
341
  return pdf_file
342
  return None
343
 
344
  def search_conv(keyword):
345
+ """Search conversations"""
346
+ if not keyword or not keyword.strip():
347
  return "Please enter a search keyword."
348
 
349
  results = reasoner.search_conversations(keyword)
 
353
  output = f"### Found {len(results)} result(s) for '{keyword}'\n\n"
354
  for idx, entry in results[:10]:
355
  output += f"**{idx + 1}.** {entry.timestamp} | {entry.model}\n"
356
+ preview = entry.user_message[:100].replace('\n', ' ')
357
+ output += f"**User:** {preview}...\n\n"
358
 
359
  if len(results) > 10:
360
  output += f"\n*Showing first 10 of {len(results)} results*"
 
362
  return output
363
 
364
  def refresh_analytics():
365
+ """Refresh analytics"""
366
  analytics = reasoner.get_analytics()
367
  if not analytics:
368
  return get_empty_analytics_html(), "No cache data.", "No data", "No data"
 
384
  - Hits: {analytics['cache_hits']}
385
  - Misses: {analytics['cache_misses']}
386
  - Total: {analytics['cache_hits'] + analytics['cache_misses']}
387
+ - Hit Rate: {reasoner.cache.get_stats()['hit_rate']}%
388
  """
389
 
390
  model_dist_html = f"**Model Usage:** {analytics['most_used_model']}"
 
393
  return analytics_html, cache_html, model_dist_html, mode_dist_html
394
 
395
  def update_history_stats():
396
+ """Update history"""
397
  count = len(reasoner.conversation_history)
398
  if count == 0:
399
  return "No conversations yet."
400
 
401
  return f"""**Total Conversations:** {count}
402
+ **Session:** {reasoner.session_id[:8]}...
403
+ **Started:** {reasoner.metrics.session_start}"""
404
 
405
  def clear_cache_action():
406
+ """Clear cache"""
407
  reasoner.cache.clear()
408
+ return "βœ… Cache cleared successfully!"
409
+
410
+ def toggle_sidebar(sidebar_state):
411
+ """⭐ NEW: Toggle sidebar visibility"""
412
+ new_state = not sidebar_state
413
+ logger.info(f"Sidebar toggled: {'Hidden' if not new_state else 'Visible'}")
414
+ return gr.update(visible=new_state), new_state
415
+
416
+ # ==================== STATE MANAGEMENT ====================
417
+
418
+ # Track sidebar visibility state
419
+ sidebar_visible_state = gr.State(value=True)
420
+
421
+ # ==================== CONNECT EVENTS ====================
422
 
 
423
  submit_btn.click(
424
  process_message,
425
  [msg, chatbot, reasoning_mode, enable_critique, model, temperature, max_tokens, prompt_template, use_cache],
 
433
  ).then(lambda: "", None, msg)
434
 
435
  clear_btn.click(reset_chat, None, [chatbot, metrics_display])
 
 
436
  pdf_btn.click(download_chat_pdf, None, pdf_file_output)
437
 
438
+ # ⭐ NEW: Toggle Sidebar Event
439
+ toggle_sidebar_btn.click(
440
+ toggle_sidebar,
441
+ inputs=[sidebar_visible_state],
442
+ outputs=[sidebar, sidebar_visible_state]
443
+ )
444
+
445
  export_btn.click(export_conv, [export_format, include_meta], [export_output, download_file])
446
  search_btn.click(search_conv, search_input, search_results)
447
  refresh_btn.click(
 
451
  )
452
  clear_cache_btn.click(clear_cache_action, None, cache_status)
453
 
 
454
  demo.load(update_history_stats, None, history_stats)
455
 
456
  return demo
 
458
  if __name__ == "__main__":
459
  try:
460
  logger.info("="*60)
461
+ logger.info("πŸš€ Starting Advanced AI Reasoning System Pro...")
462
+ logger.info(f"πŸ“ Session ID: {reasoner.session_id}")
463
+ logger.info("πŸŽ›οΈ Collapsible Sidebar: Enabled")
464
  logger.info("="*60)
465
 
466
  demo = create_ui()
 
472
  show_api=False,
473
  favicon_path=None
474
  )
475
+ except KeyboardInterrupt:
476
+ logger.info("⏹️ Application stopped by user")
477
  except Exception as e:
478
+ logger.critical(f"❌ Failed to start application: {e}", exc_info=True)
479
+ raise
480
+ finally:
481
+ logger.info("πŸ‘‹ Shutting down gracefully...")