collaborative-decoding / src /session /session_manager.py
Alon Albalak
major update: data saved to hf, user sessions maintain separation, fixed scoring bug
36d5e94
"""Session management and progress tracking functionality"""
import uuid
class SessionManager:
"""Manages user session state and progress tracking"""
def __init__(self, session_id=None, data_manager=None, achievement_system=None):
self.session_id = session_id or str(uuid.uuid4())
self.data_manager = data_manager
self.achievement_system = achievement_system
def get_session_statistics(self):
"""Get statistics for the current session."""
if not self.data_manager:
return self._get_empty_session_stats()
all_results = self.data_manager.get_results()
session_results = self.data_manager.filter_results_by_session(all_results, self.session_id)
if not session_results:
return self._get_empty_session_stats()
scores = [r["cosine_distance"] for r in session_results]
unique_prompts = set((r["prompt"], r["llm_partial_response"]) for r in session_results)
return {
"total_attempts": len(session_results),
"best_score": max(scores),
"average_score": sum(scores) / len(scores),
"total_tokens_used": sum(r["num_user_tokens"] for r in session_results),
"prompts_tried": len(unique_prompts),
"session_results": session_results
}
def _get_empty_session_stats(self):
"""Return empty session statistics structure"""
return {
"total_attempts": 0,
"best_score": 0.0,
"average_score": 0.0,
"total_tokens_used": 0,
"prompts_tried": 0
}
def generate_session_progress_html(self, template_renderer):
"""Generate HTML for session progress display."""
stats = self.get_session_statistics()
if stats["total_attempts"] == 0:
return template_renderer.load_template("session-progress-empty.html")
# Create achievement summary for session
session_achievements = set()
if self.achievement_system and "session_results" in stats:
for result in stats["session_results"]:
achievements = self.achievement_system.determine_achievement_titles(
result["cosine_distance"],
result["num_user_tokens"]
)
session_achievements.update(achievements)
achievement_badges = ""
if session_achievements:
badges_html = ""
for achievement in list(session_achievements)[:5]: # Show top 5
badges_html += template_renderer.load_template("achievement-badge.html", achievement=achievement)
achievement_badges = template_renderer.load_template("session-achievements.html",
achievement_badges=badges_html)
# Recent attempts preview
recent_attempts = sorted(stats["session_results"],
key=lambda x: x["timestamp"],
reverse=True)[:3]
attempts_html = ""
for i, attempt in enumerate(recent_attempts, 1):
score_color = "#4CAF50" if attempt["cosine_distance"] >= 0.3 else "#FF9800" if attempt["cosine_distance"] >= 0.15 else "#9E9E9E"
user_input = attempt['user_continuation'][:30] + ('...' if len(attempt['user_continuation']) > 30 else '')
plural = "s" if attempt["num_user_tokens"] != 1 else ""
attempt_number = len(stats["session_results"]) - i + 1
attempts_html += template_renderer.load_template("attempt-item.html",
attempt_number=attempt_number,
user_input=user_input,
score_color=score_color,
cosine_distance=attempt["cosine_distance"],
num_tokens=attempt["num_user_tokens"],
plural=plural
)
recent_html = template_renderer.load_template("recent-attempts.html", attempts=attempts_html)
return template_renderer.load_template("session-progress-main.html",
total_attempts=stats['total_attempts'],
best_score=stats['best_score'],
average_score=stats['average_score'],
prompts_tried=stats['prompts_tried'],
achievement_badges=achievement_badges,
recent_html=recent_html
)
def generate_session_page_html(self, template_renderer, statistics_calculator=None, scorer=None):
"""Generate comprehensive session page HTML with ranking information."""
stats = self.get_session_statistics()
if stats["total_attempts"] == 0:
empty_html = template_renderer.load_template("session-page-empty.html")
return empty_html, "", ""
# Calculate ranking statistics
ranking_stats = {}
if statistics_calculator and scorer and "session_results" in stats:
ranking_stats = statistics_calculator.calculate_session_ranking_stats(
stats["session_results"], self.data_manager, scorer
)
# Main stats overview
best_attempt = max(stats["session_results"], key=lambda x: x["cosine_distance"])
recent_attempts = sorted(stats["session_results"], key=lambda x: x["timestamp"], reverse=True)[:5]
# Determine trend icon and message
trend_icon = {"up": "πŸ“ˆ", "down": "πŸ“‰", "stable": "πŸ“Š"}.get(ranking_stats.get("ranking_trend", "stable"), "πŸ“Š")
trend_message = {
"up": "Improving!",
"down": "Room to grow",
"stable": "Consistent"
}.get(ranking_stats.get("ranking_trend", "stable"), "Consistent")
main_stats_html = f"""
<div class="session-progress">
<div class="session-stats">
<div class="stat-item">
<div class="stat-value">{stats['total_attempts']}</div>
<div class="stat-label">Creative Attempts</div>
</div>
<div class="stat-item">
<div class="stat-value">{stats['best_score']:.3f}</div>
<div class="stat-label">Personal Best</div>
</div>
<div class="stat-item">
<div class="stat-value">{stats['average_score']:.3f}</div>
<div class="stat-label">Average Score</div>
</div>"""
# Add ranking stats if available
if ranking_stats.get("total_ranked_attempts", 0) > 0:
main_stats_html += f"""
<div class="stat-item ranking-highlight">
<div class="stat-value">#{ranking_stats['best_rank']}</div>
<div class="stat-label">Best Rank</div>
</div>
<div class="stat-item ranking-highlight">
<div class="stat-value">{ranking_stats['average_percentile']:.0f}%</div>
<div class="stat-label">Avg vs Others</div>
</div>"""
else:
main_stats_html += f"""
<div class="stat-item">
<div class="stat-value">{stats['prompts_tried']}</div>
<div class="stat-label">Prompts Explored</div>
</div>"""
main_stats_html += f"""
</div>
<div class="session-overview">
<div class="best-response-showcase">
<h4>🎯 Your Best Response</h4>
<div class="best-response-content">
<span class="best-input">"{best_attempt['user_continuation']}"</span>
<div class="best-metrics">
<span class="score-badge">Score: {best_attempt['cosine_distance']:.3f}</span>"""
# Add ranking info for best attempt if available
if ranking_stats.get("total_ranked_attempts", 0) > 0 and "recent_percentiles" in ranking_stats:
best_ranking = next((r for r in ranking_stats["recent_percentiles"]
if abs(float(r["timestamp"].replace('-', '').replace(':', '').replace('T', '').replace('.', '')[:14]) -
float(best_attempt["timestamp"].replace('-', '').replace(':', '').replace('T', '').replace('.', '')[:14])) < 1000), None)
if best_ranking:
main_stats_html += f"""
<span class="rank-badge">#{best_ranking['rank']} of {best_ranking['total']}</span>"""
main_stats_html += f"""
</div>
</div>
</div>"""
# Add trend information if sufficient data
if ranking_stats.get("total_ranked_attempts", 0) >= 2:
main_stats_html += f"""
<div class="performance-trend">
{trend_icon} <strong>Ranking Trend:</strong> {trend_message}
<span class="trend-detail">({ranking_stats['total_ranked_attempts']} ranked attempts)</span>
</div>"""
main_stats_html += f"""
</div>
</div>
"""
# Enhanced detailed history with ranking
history_html = """
<div class="session-progress">
<div class="recent-attempts">
"""
for i, attempt in enumerate(recent_attempts, 1):
score_color = "#4CAF50" if attempt["cosine_distance"] >= 0.3 else "#FF9800" if attempt["cosine_distance"] >= 0.15 else "#9E9E9E"
# Truncate long inputs for display
display_input = attempt['user_continuation'][:50] + ('...' if len(attempt['user_continuation']) > 50 else '')
# Find ranking info for this attempt
ranking_info = None
if "recent_percentiles" in ranking_stats:
ranking_info = next((r for r in ranking_stats["recent_percentiles"]
if r["timestamp"] == attempt["timestamp"]), None)
history_html += f"""
<div class="attempt-item enhanced">
<span class="attempt-number">#{len(stats["session_results"]) - i + 1}</span>
<div class="attempt-content">
<div class="attempt-input">"{display_input}"</div>
<div class="attempt-details">
<span class="timestamp">{attempt['timestamp'][:10]} {attempt['timestamp'][11:16]}</span>"""
if ranking_info:
# Add percentile indicator
percentile = ranking_info["percentile"]
percentile_color = "#4CAF50" if percentile >= 75 else "#FF9800" if percentile >= 50 else "#9E9E9E"
percentile_icon = "πŸ†" if percentile >= 90 else "⭐" if percentile >= 75 else "πŸ“ˆ" if percentile >= 50 else "🎯"
history_html += f"""
<span class="ranking-info" style="color: {percentile_color};">
{percentile_icon} #{ranking_info['rank']} of {ranking_info['total']} ({percentile:.0f}%)
</span>"""
else:
history_html += f"""
<span class="ranking-info" style="opacity: 0.6;">
🌱 First attempt at this prompt+token combo
</span>"""
history_html += f"""
</div>
</div>
<div class="attempt-metrics">
<span class="attempt-score" style="color: {score_color};">
{attempt["cosine_distance"]:.3f}
</span>
<span class="token-count">{attempt["num_user_tokens"]} tokens</span>
</div>
</div>
"""
if len(recent_attempts) < len(stats["session_results"]):
remaining = len(stats["session_results"]) - len(recent_attempts)
history_html += f"""
<div class="attempt-item" style="opacity: 0.6;">
<span class="attempt-number">...</span>
<div class="attempt-content">
<div class="attempt-input">And {remaining} more creative attempts!</div>
<div class="attempt-details">
<span class="ranking-info">πŸ“Š View all attempts for complete ranking history</span>
</div>
</div>
<div class="attempt-metrics">
<span class="attempt-score">πŸ“ˆ</span>
</div>
</div>
"""
history_html += """
</div>
</div>
"""
# Achievements
all_achievements = set()
if self.achievement_system and "session_results" in stats:
for result in stats["session_results"]:
achievements = self.achievement_system.determine_achievement_titles(
result["cosine_distance"],
result["num_user_tokens"]
)
all_achievements.update(achievements)
achievements_html = """
<div class="session-progress">
"""
if all_achievements:
achievements_html += """
<div class="session-achievements">
<div class="achievement-badges">
"""
for achievement in sorted(all_achievements):
achievements_html += f'<div class="achievement-badge">{achievement}</div>'
achievements_html += """
</div>
</div>
"""
else:
achievements_html += """
<div class="session-tip">
πŸ† Keep being creative to unlock achievements! Try different token counts and creative approaches.
</div>
"""
achievements_html += """
</div>
"""
return main_stats_html, history_html, achievements_html