|
|
""" |
|
|
Event handlers for UI interactions |
|
|
""" |
|
|
from typing import Tuple, Optional |
|
|
from pathlib import Path |
|
|
import gradio as gr |
|
|
from src.core.reasoner import AdvancedReasoner |
|
|
from src.config.constants import ReasoningMode |
|
|
from src.utils.logger import logger |
|
|
from src.ui.components import UIComponents |
|
|
|
|
|
|
|
|
class EventHandlers: |
|
|
""" |
|
|
π― EVENT HANDLERS FOR UI INTERACTIONS |
|
|
""" |
|
|
|
|
|
def __init__(self, reasoner: AdvancedReasoner): |
|
|
self.reasoner = reasoner |
|
|
self.components = UIComponents() |
|
|
|
|
|
def process_message(self, message, history, mode, critique, model_name, |
|
|
temp, tokens, template, cache): |
|
|
""" |
|
|
π PROCESS MESSAGE WITH STREAMING |
|
|
""" |
|
|
if not message or not message.strip(): |
|
|
history = history or [] |
|
|
history.append({ |
|
|
"role": "assistant", |
|
|
"content": "β οΈ **Input Error:** Please enter a message before submitting." |
|
|
}) |
|
|
return history, self.components.get_metrics_html(self.reasoner) |
|
|
|
|
|
history = history or [] |
|
|
mode_enum = ReasoningMode(mode) |
|
|
|
|
|
|
|
|
history.append({"role": "user", "content": message}) |
|
|
yield history, self.components.get_metrics_html(self.reasoner) |
|
|
|
|
|
|
|
|
history.append({"role": "assistant", "content": ""}) |
|
|
|
|
|
try: |
|
|
for response in self.reasoner.generate_response( |
|
|
message, history[:-1], model_name, mode_enum, |
|
|
critique, temp, tokens, template, cache |
|
|
): |
|
|
history[-1]["content"] = response |
|
|
yield history, self.components.get_metrics_html(self.reasoner) |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"β **Unexpected Error:** {str(e)}\n\nPlease try again or check the logs for details." |
|
|
history[-1]["content"] = error_msg |
|
|
logger.error(f"Error in process_message: {e}", exc_info=True) |
|
|
yield history, self.components.get_metrics_html(self.reasoner) |
|
|
|
|
|
def reset_chat(self): |
|
|
"""ποΈ RESET CHAT""" |
|
|
self.reasoner.clear_history() |
|
|
logger.info("Chat history cleared by user") |
|
|
return [], self.components.get_metrics_html(self.reasoner) |
|
|
|
|
|
def export_conversation(self, format_type, include_metadata): |
|
|
"""π€ EXPORT CONVERSATION""" |
|
|
try: |
|
|
content, filename = self.reasoner.export_conversation(format_type, include_metadata) |
|
|
if filename: |
|
|
logger.info(f"Conversation exported: {filename}") |
|
|
return content, filename |
|
|
else: |
|
|
return content, None |
|
|
except Exception as e: |
|
|
logger.error(f"Export error: {e}") |
|
|
return f"β Export failed: {str(e)}", None |
|
|
|
|
|
def download_chat_pdf(self): |
|
|
"""π DOWNLOAD CHAT AS PDF""" |
|
|
try: |
|
|
pdf_file = self.reasoner.export_current_chat_pdf() |
|
|
if pdf_file: |
|
|
logger.info(f"PDF ready for download: {pdf_file}") |
|
|
return pdf_file |
|
|
else: |
|
|
logger.warning("No conversations to export") |
|
|
return None |
|
|
except Exception as e: |
|
|
logger.error(f"PDF download error: {e}") |
|
|
return None |
|
|
|
|
|
def search_conversations(self, keyword): |
|
|
"""π SEARCH CONVERSATIONS""" |
|
|
if not keyword or not keyword.strip(): |
|
|
return "β οΈ **Search Error:** Please enter a search keyword." |
|
|
|
|
|
try: |
|
|
results = self.reasoner.search_conversations(keyword) |
|
|
if not results: |
|
|
return f"π **No Results:** No conversations found containing '{keyword}'." |
|
|
|
|
|
output = f"### π Found {len(results)} result(s) for '{keyword}'\n\n" |
|
|
for idx, entry in results[:10]: |
|
|
output += f"**{idx + 1}.** π
{entry.timestamp} | π€ {entry.model}\n" |
|
|
preview = entry.user_message[:100].replace('\n', ' ') |
|
|
output += f"**π€ User:** {preview}...\n\n" |
|
|
|
|
|
if len(results) > 10: |
|
|
output += f"\n*Showing first 10 of {len(results)} results*" |
|
|
|
|
|
return output |
|
|
except Exception as e: |
|
|
logger.error(f"Search error: {e}") |
|
|
return f"β **Search Error:** {str(e)}" |
|
|
|
|
|
def refresh_analytics(self): |
|
|
"""π REFRESH ANALYTICS""" |
|
|
try: |
|
|
analytics = self.reasoner.get_analytics() |
|
|
if not analytics: |
|
|
return ( |
|
|
self.components.get_empty_analytics_html(), |
|
|
"No cache data available yet.", |
|
|
"**Model Usage:** No data", |
|
|
"**Reasoning Mode Usage:** No data" |
|
|
) |
|
|
|
|
|
analytics_html = f"""<div class="analytics-panel"> |
|
|
<h3>π Session Analytics</h3> |
|
|
<p><strong>π Session ID:</strong> {analytics['session_id']}</p> |
|
|
<p><strong>π¬ Total Conversations:</strong> {analytics['total_conversations']}</p> |
|
|
<p><strong>π Total Tokens:</strong> {analytics['total_tokens']:,}</p> |
|
|
<p><strong>β±οΈ Total Time:</strong> {analytics['total_time']:.1f}s</p> |
|
|
<p><strong>β‘ Avg Inference Time:</strong> {analytics['avg_inference_time']:.2f}s</p> |
|
|
<p><strong>ποΈ Peak Tokens:</strong> {analytics['peak_tokens']}</p> |
|
|
<p><strong>π€ Most Used Model:</strong> {analytics['most_used_model']}</p> |
|
|
<p><strong>π§ Most Used Mode:</strong> {analytics['most_used_mode']}</p> |
|
|
<p><strong>β οΈ Errors:</strong> {analytics['error_count']}</p> |
|
|
</div>""" |
|
|
|
|
|
cache_html = f"""**πΎ Cache Performance:** |
|
|
- β
Hits: {analytics['cache_hits']} |
|
|
- β Misses: {analytics['cache_misses']} |
|
|
- π Total: {analytics['cache_hits'] + analytics['cache_misses']} |
|
|
- π Hit Rate: {self.reasoner.cache.get_stats()['hit_rate']}% |
|
|
""" |
|
|
|
|
|
model_dist_html = f"**π€ Most Used Model:** {analytics['most_used_model']}" |
|
|
mode_dist_html = f"**π§ Most Used Mode:** {analytics['most_used_mode']}" |
|
|
|
|
|
return analytics_html, cache_html, model_dist_html, mode_dist_html |
|
|
except Exception as e: |
|
|
logger.error(f"Analytics refresh error: {e}") |
|
|
return self.components.get_empty_analytics_html(), "Error loading cache data", "No data", "No data" |
|
|
|
|
|
def update_history_stats(self): |
|
|
"""π UPDATE HISTORY STATS""" |
|
|
try: |
|
|
count = len(self.reasoner.conversation_history) |
|
|
if count == 0: |
|
|
return "π **No conversations yet.** Start chatting to build your history!" |
|
|
|
|
|
return f"""**π Conversation Statistics:** |
|
|
|
|
|
- π¬ Total Conversations: {count} |
|
|
- π Session ID: `{self.reasoner.session_id[:8]}...` |
|
|
- π
Session Started: {self.reasoner.metrics.session_start} |
|
|
- π€ Models Used: {len(self.reasoner.model_usage)} |
|
|
- π§ Reasoning Modes Used: {len(self.reasoner.mode_usage)} |
|
|
""" |
|
|
except Exception as e: |
|
|
logger.error(f"History stats error: {e}") |
|
|
return "Error loading history statistics" |
|
|
|
|
|
def clear_cache_action(self): |
|
|
"""ποΈ CLEAR CACHE""" |
|
|
try: |
|
|
self.reasoner.cache.clear() |
|
|
logger.info("Cache cleared by user") |
|
|
return "β
**Success:** Cache cleared successfully!" |
|
|
except Exception as e: |
|
|
logger.error(f"Cache clear error: {e}") |
|
|
return f"β **Error:** Failed to clear cache: {str(e)}" |
|
|
|
|
|
def reset_metrics_action(self): |
|
|
"""π RESET METRICS""" |
|
|
try: |
|
|
self.reasoner.metrics.reset() |
|
|
logger.info("Metrics reset by user") |
|
|
return "β
**Success:** Metrics reset successfully!" |
|
|
except Exception as e: |
|
|
logger.error(f"Metrics reset error: {e}") |
|
|
return f"β **Error:** Failed to reset metrics: {str(e)}" |
|
|
|
|
|
def toggle_sidebar(self, sidebar_state): |
|
|
"""βοΈ TOGGLE SIDEBAR VISIBILITY""" |
|
|
new_state = not sidebar_state |
|
|
logger.info(f"Sidebar toggled: {'Visible' if new_state else 'Hidden'}") |
|
|
return gr.update(visible=new_state), new_state |
|
|
|