Sazid2's picture
Rename app.py01 to app.py
1d19995 verified
import streamlit as st
import requests
import os
from datetime import datetime, timedelta
import re
import hashlib
import json
import time
# ===============================
# SUPABASE CACHE CLASS - FIXED VERSION
# ===============================
class SupabaseCache:
def __init__(self, ttl_days=7):
"""
Supabase-based cache for multi-user, persistent storage
"""
self.ttl_days = ttl_days
# Get Supabase credentials from environment
self.supabase_url = os.environ.get("SUPABASE_URL", "")
self.supabase_key = os.environ.get("SUPABASE_KEY", "")
# Initialize Supabase client
self.supabase = None
self._init_supabase()
# In-memory fallback cache
self.memory_cache = {}
self.max_memory_entries = 100
def _init_supabase(self):
"""Initialize Supabase client"""
if self.supabase_url and self.supabase_key:
try:
from supabase import create_client
self.supabase = create_client(self.supabase_url, self.supabase_key)
# Test connection
self.supabase.table("seba_cache").select("count", count="exact").limit(1).execute()
except ImportError:
# supabase-py not installed
self.supabase = None
except Exception as e:
# Connection failed
print(f"Supabase connection error: {e}")
self.supabase = None
else:
self.supabase = None
def get(self, cache_key):
"""Get cached answer - try Supabase first, then memory"""
# First check memory cache (fastest)
if cache_key in self.memory_cache:
entry = self.memory_cache[cache_key]
if self._is_valid(entry):
entry['access_count'] = entry.get('access_count', 0) + 1
entry['last_accessed'] = datetime.now().isoformat()
return entry
# Try Supabase if available - FIXED: Removed TTL filter from query
if self.supabase:
try:
# Get entry without TTL filter - we'll check TTL in Python
response = self.supabase.table("seba_cache") \
.select("*") \
.eq("key_hash", cache_key) \
.execute()
if response.data and len(response.data) > 0:
entry = response.data[0]
# Check if entry is expired
created_at_str = entry.get('created_at')
is_expired = False
if created_at_str:
try:
# Parse the timestamp
if 'Z' in created_at_str:
created_at = datetime.fromisoformat(created_at_str.replace('Z', '+00:00'))
else:
created_at = datetime.fromisoformat(created_at_str)
# Check TTL
if (datetime.now() - created_at).days >= self.ttl_days:
# Entry expired, delete it
is_expired = True
try:
self.supabase.table("seba_cache") \
.delete() \
.eq("key_hash", cache_key) \
.execute()
except:
pass
except Exception:
# If we can't parse date, assume not expired
pass
if not is_expired:
# Convert to standard format
cached_data = {
'answer': entry['answer'],
'tokens': entry.get('tokens', 0),
'subject': entry.get('subject', ''),
'chapter': entry.get('chapter', ''),
'question': entry.get('question', ''),
'access_count': entry.get('access_count', 0) + 1,
'created_at': entry.get('created_at'),
'last_accessed': datetime.now().isoformat()
}
# Update access count in Supabase
try:
self.supabase.table("seba_cache") \
.update({
"last_accessed": datetime.now().isoformat(),
"access_count": entry.get('access_count', 0) + 1
}) \
.eq("key_hash", cache_key) \
.execute()
except:
pass
# Store in memory cache for faster access
self.memory_cache[cache_key] = cached_data
# Limit memory cache size
if len(self.memory_cache) > self.max_memory_entries:
oldest_key = min(self.memory_cache.keys(),
key=lambda k: self.memory_cache[k].get('last_accessed', ''))
del self.memory_cache[oldest_key]
return cached_data
except Exception as e:
# Silently fail - fall back to memory cache
print(f"Supabase get error: {e}")
pass
return None
def set(self, cache_key, data):
"""Store answer in both Supabase and memory cache"""
# Prepare data
cache_data = {
'answer': data['answer'],
'tokens': data.get('tokens', 0),
'subject': data.get('subject', ''),
'chapter': data.get('chapter', ''),
'question': data.get('question', '')[:200],
'access_count': 1,
'created_at': datetime.now().isoformat(),
'last_accessed': datetime.now().isoformat()
}
# Store in memory cache
self.memory_cache[cache_key] = cache_data
# Limit memory cache size
if len(self.memory_cache) > self.max_memory_entries:
oldest_key = min(self.memory_cache.keys(),
key=lambda k: self.memory_cache[k].get('last_accessed', ''))
del self.memory_cache[oldest_key]
# Store in Supabase if available
if self.supabase:
try:
self.supabase.table("seba_cache").upsert({
"key_hash": cache_key,
"question": cache_data['question'],
"answer": cache_data['answer'],
"subject": cache_data['subject'],
"chapter": cache_data['chapter'],
"tokens": cache_data['tokens'],
"created_at": cache_data['created_at'],
"last_accessed": cache_data['last_accessed'],
"access_count": cache_data['access_count']
}).execute()
except Exception as e:
# Silently fail - at least we have memory cache
print(f"Supabase set error: {e}")
pass
def _is_valid(self, entry):
"""Check if cache entry is not expired"""
created_at = entry.get('created_at')
if isinstance(created_at, str):
try:
if 'Z' in created_at:
created_at = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
else:
created_at = datetime.fromisoformat(created_at)
except:
return True # If we can't parse, assume valid
if created_at and (datetime.now() - created_at).days < self.ttl_days:
return True
return False
def clear_expired(self):
"""Clear expired entries from memory cache"""
expired_keys = []
for key, entry in self.memory_cache.items():
if not self._is_valid(entry):
expired_keys.append(key)
for key in expired_keys:
del self.memory_cache[key]
return len(expired_keys)
def clear_all(self):
"""Clear all cache entries"""
self.memory_cache = {}
# Also clear Supabase cache if available
if self.supabase:
try:
# Delete entries older than 1 day (safer than deleting all)
self.supabase.table("seba_cache") \
.delete() \
.lt("created_at", f"now() - interval '1 day'") \
.execute()
except:
pass
def get_stats(self):
"""Get cache statistics"""
# Memory cache stats
memory_entries = len(self.memory_cache)
memory_tokens = sum(entry.get('tokens', 0) for entry in self.memory_cache.values())
# Try to get Supabase stats
supabase_entries = 0
supabase_tokens = 0
if self.supabase:
try:
# Get count from Supabase
response = self.supabase.table("seba_cache") \
.select("count", count="exact") \
.execute()
supabase_entries = response.count or 0
# Get total tokens (might be heavy, so approximate)
if supabase_entries > 0:
response = self.supabase.table("seba_cache") \
.select("tokens") \
.limit(100) \
.execute()
supabase_tokens = sum(entry.get('tokens', 0) for entry in response.data)
except:
pass
total_entries = memory_entries + supabase_entries
total_tokens = memory_tokens + supabase_tokens
return {
'total_entries': total_entries,
'memory_entries': memory_entries,
'supabase_entries': supabase_entries,
'total_saved_tokens': total_tokens,
'ttl_days': self.ttl_days,
'storage_mode': 'Supabase + Memory' if self.supabase else 'Memory Only',
'supabase_connected': self.supabase is not None
}
# ===============================
# SUPABASE SUBSCRIPTION MANAGER
# ===============================
class SupabaseSubscriptionManager:
def __init__(self, supabase_client):
self.supabase = supabase_client
def create_user(self, user_id, email=None, phone=None):
"""Create a new user with free plan"""
try:
# Create user
user_data = {
"user_id": user_id,
"email": email,
"phone": phone,
"is_active": True
}
# Remove None values
user_data = {k: v for k, v in user_data.items() if v is not None}
# Upsert user (insert or update if exists)
response = self.supabase.table("seba_users").upsert(user_data).execute()
# Create free subscription (30 questions lifetime)
subscription_data = {
"user_id": user_id,
"plan_type": "free",
"price_paid": 0.00,
"start_date": datetime.now().isoformat(),
"end_date": (datetime.now() + timedelta(days=3650)).isoformat(), # 10 years
"total_questions": 30,
"questions_used": 0,
"daily_limit": 1,
"is_active": True
}
response = self.supabase.table("seba_subscriptions").upsert(subscription_data).execute()
return True
except Exception as e:
print(f"Error creating user: {e}")
return False
def get_user_plan(self, user_id):
"""Get user's current subscription plan"""
try:
response = self.supabase.table("seba_subscriptions") \
.select("*") \
.eq("user_id", user_id) \
.eq("is_active", True) \
.gte("end_date", datetime.now().isoformat()) \
.order("start_date", desc=True) \
.limit(1) \
.execute()
if response.data and len(response.data) > 0:
plan = response.data[0]
return {
'plan_type': plan['plan_type'],
'total_questions': plan['total_questions'],
'questions_used': plan['questions_used'],
'daily_limit': plan['daily_limit'],
'end_date': plan['end_date'],
'questions_remaining': plan['total_questions'] - plan['questions_used'],
'is_active': True,
'plan_id': plan['id']
}
# No active subscription found
return None
except Exception as e:
print(f"Error getting user plan: {e}")
return None
def get_or_create_user_plan(self, user_id, email=None, phone=None):
"""Get user plan, create if doesn't exist"""
plan = self.get_user_plan(user_id)
if plan is None:
# Create user with free plan
self.create_user(user_id, email, phone)
plan = self.get_user_plan(user_id)
return plan
def can_ask_question(self, user_id):
"""Check if user can ask a new question"""
try:
# Get user's plan
plan = self.get_user_plan(user_id)
if not plan:
return False, "No subscription found. Please sign up."
# Check if subscription expired
try:
end_date_str = plan['end_date']
# Handle different timestamp formats
if 'Z' in end_date_str:
end_date = datetime.fromisoformat(end_date_str.replace('Z', '+00:00'))
else:
end_date = datetime.fromisoformat(end_date_str)
if datetime.now() > end_date:
return False, "Subscription expired. Please renew."
except Exception as date_error:
print(f"Date parsing error: {date_error}")
# Continue if date parsing fails
# Check monthly/lifetime quota
if plan['questions_used'] >= plan['total_questions']:
if plan['plan_type'] == 'free':
return False, "Free quota exhausted. Please upgrade to Pro."
else:
return False, f"Monthly quota ({plan['total_questions']}) exhausted. Renew or upgrade."
# For all plans, check daily limit
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_end = datetime.now().replace(hour=23, minute=59, second=59, microsecond=999999)
# Count today's non-cached questions
response = self.supabase.table("seba_usage_logs") \
.select("id", count="exact") \
.eq("user_id", user_id) \
.eq("cached", False) \
.gte("timestamp", today_start.isoformat()) \
.lt("timestamp", today_end.isoformat()) \
.execute()
today_used = response.count or 0
if today_used >= plan['daily_limit']:
return False, f"Daily limit ({plan['daily_limit']}) reached. Try again tomorrow."
return True, "Can ask question"
except Exception as e:
print(f"Error checking question allowance: {e}")
# For now, allow questions if check fails (fail-open)
return True, "Proceeding with question..."
def log_question(self, user_id, question_hash, subject="", chapter="", tokens_used=0, cached=False):
"""Log a question usage"""
try:
# Get current plan
plan = self.get_user_plan(user_id)
if not plan:
plan_type = "free"
else:
plan_type = plan['plan_type']
# Log usage
log_data = {
"user_id": user_id,
"question_hash": question_hash,
"subject": subject[:100] if subject else "",
"chapter": chapter[:100] if chapter else "",
"tokens_used": tokens_used,
"cached": cached,
"plan_type": plan_type
}
response = self.supabase.table("seba_usage_logs").insert(log_data).execute()
# Update subscription usage (only for non-cached questions)
if not cached and plan and plan.get('plan_id'):
# Increment questions_used
new_used = plan['questions_used'] + 1
self.supabase.table("seba_subscriptions") \
.update({"questions_used": new_used}) \
.eq("id", plan['plan_id']) \
.execute()
return True
except Exception as e:
print(f"Error logging question: {e}")
return False
def upgrade_plan(self, user_id, plan_type, payment_id=None):
"""Upgrade user's subscription plan"""
plans = {
'pro_monthly': {
'price': 50,
'questions': 450,
'daily_limit': 15,
'days': 30
},
'pro_plus': {
'price': 100,
'questions': 1200,
'daily_limit': 40,
'days': 30
}
}
if plan_type not in plans:
return False, "Invalid plan type"
plan = plans[plan_type]
try:
# Deactivate current active subscription
self.supabase.table("seba_subscriptions") \
.update({"is_active": False}) \
.eq("user_id", user_id) \
.eq("is_active", True) \
.execute()
# Create new subscription
subscription_data = {
"user_id": user_id,
"plan_type": plan_type,
"price_paid": plan['price'],
"start_date": datetime.now().isoformat(),
"end_date": (datetime.now() + timedelta(days=plan['days'])).isoformat(),
"total_questions": plan['questions'],
"questions_used": 0,
"daily_limit": plan['daily_limit'],
"is_active": True,
"payment_id": payment_id
}
response = self.supabase.table("seba_subscriptions").insert(subscription_data).execute()
return True, f"Upgraded to {plan_type.replace('_', ' ').title()} successfully!"
except Exception as e:
return False, f"Error: {str(e)}"
def get_user_stats(self, user_id):
"""Get user statistics"""
try:
# Get today's usage
today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
today_end = datetime.now().replace(hour=23, minute=59, second=59, microsecond=999999)
response = self.supabase.table("seba_usage_logs") \
.select("id", count="exact") \
.eq("user_id", user_id) \
.eq("cached", False) \
.gte("timestamp", today_start.isoformat()) \
.lt("timestamp", today_end.isoformat()) \
.execute()
today_used = response.count or 0
# Get total usage
response = self.supabase.table("seba_usage_logs") \
.select("id", count="exact") \
.eq("user_id", user_id) \
.eq("cached", False) \
.execute()
total_used = response.count or 0
# Get cached usage
response = self.supabase.table("seba_usage_logs") \
.select("id", count="exact") \
.eq("user_id", user_id) \
.eq("cached", True) \
.execute()
cached_used = response.count or 0
return {
'today_used': today_used,
'total_used': total_used,
'cached_used': cached_used
}
except Exception as e:
print(f"Error getting user stats: {e}")
return {'today_used': 0, 'total_used': 0, 'cached_used': 0}
# ===============================
# API KEY HANDLING
# ===============================
# Get API key from environment variable
api_key = os.environ.get("DEEPSEEK_API_KEY", "")
# Page config - must be first Streamlit command
st.set_page_config(
page_title="SEBA দশম শ্ৰেণীৰ AI টিউটাৰ",
page_icon="🎓",
layout="wide",
initial_sidebar_state="collapsed"
)
# Enhanced CSS with streaming and focus features - FIXED LATEX RENDERING
st.markdown("""
<style>
/* Assamese-friendly fonts */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Bengali:wght@400;500;600;700;800&family=Hind+Siliguri:wght@300;400;500;600;700&display=swap');
* {
font-family: 'Noto Sans Bengali', 'Hind Siliguri', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* Reduced spacing header */
.header-container {
background: linear-gradient(135deg, #0d47a1 0%, #1565c0 50%, #1976d2 100%);
padding: 1.25rem;
border-radius: 15px;
margin-bottom: 1rem;
color: white;
position: relative;
overflow: hidden;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 5px 15px rgba(13,71,161,0.2);
}
/* The top rainbow line */
.header-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #FF5722, #FF9800, #4CAF50);
}
/* Header text */
.header-container h1 {
color: #ffffff;
font-size: 1.5rem;
font-weight: 800;
text-shadow: 0px 1px 3px rgba(0,0,0,0.5);
margin: 0;
line-height: 1.2;
}
.header-container p {
color: #f6f9ff;
font-weight: 600;
font-size: 0.95rem;
opacity: 1 !important;
text-shadow: 0px 1px 2px rgba(0,0,0,0.4);
margin-top: .2rem;
}
.subject-card {
background: linear-gradient(145deg, #ffffff 0%, #f0f7ff 100%);
padding: 0.75rem;
border-radius: 10px;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
border-left: 4px solid #2196F3;
margin: 0.5rem 0;
transition: all 0.3s ease;
border: 1px solid #e3f2fd;
}
.subject-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 12px rgba(33, 150, 243, 0.15);
}
.answer-box {
background: linear-gradient(145deg, #f8fdff 0%, #ffffff 100%);
padding: 1rem;
border-radius: 10px;
border: 1px solid #e1f5fe;
margin: 0.75rem 0;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.05);
position: relative;
}
.stButton > button {
background: linear-gradient(135deg, #FF5722 0%, #FF9800 100%);
color: white;
border: none;
padding: 0.4rem 1rem;
border-radius: 8px;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s;
box-shadow: 0 2px 6px rgba(255, 87, 34, 0.3);
}
.stButton > button:hover {
transform: translateY(-1px);
box-shadow: 0 3px 9px rgba(255, 87, 34, 0.4);
}
.sidebar-section {
background: linear-gradient(145deg, #f8f9fa 0%, #e3f2fd 100%);
padding: 0.75rem;
border-radius: 10px;
margin-bottom: 0.75rem;
border: 1px solid #bbdefb;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}
.assamese-highlight {
background: linear-gradient(120deg, #FFF176 0%, #FFEB3B 100%);
background-repeat: no-repeat;
background-size: 100% 0.3em;
background-position: 0 90%;
padding: 0.1rem 0.2rem;
font-weight: 700;
color: #FF6F00;
}
.assamese-text {
font-family: 'Noto Sans Bengali', sans-serif;
font-weight: 500;
color: #0d47a1;
line-height: 1.4;
}
.assamese-title {
font-family: 'Noto Sans Bengali', sans-serif;
font-weight: 700;
color: #1565c0;
}
/* Chat bubble styling */
.user-bubble {
background: linear-gradient(135deg, #2196F3 0%, #0d47a1 100%) !important;
color: white;
padding: 0.5rem 0.75rem;
border-radius: 12px 12px 0 12px;
max-width: 80%;
box-shadow: 0 2px 6px rgba(33, 150, 243, 0.2);
margin-left: auto;
}
.ai-bubble {
background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%) !important;
padding: 0.75rem;
border-radius: 12px 12px 12px 0;
border: 1px solid #e0e0e0;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
/* Chat container */
.chat-container {
margin-bottom: 1rem;
scroll-margin-top: 20px;
}
.chat-message {
margin-bottom: 0.75rem;
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
/* LaTeX equation styling - FIXED VERSION */
.katex {
font-size: 1.1em !important;
padding: 0.2rem 0.5rem !important;
background: rgba(33, 150, 243, 0.1) !important;
border-radius: 4px !important;
margin: 0.3rem 0.1rem !important;
display: inline-block !important;
vertical-align: middle !important;
}
.katex-display {
margin: 1rem 0 !important;
padding: 1rem !important;
background: linear-gradient(145deg, #f0f7ff 0%, #e3f2fd 100%) !important;
border-radius: 8px !important;
border-left: 4px solid #2196F3 !important;
overflow-x: auto !important;
overflow-y: hidden !important;
text-align: center !important;
}
.katex-display .katex {
background: transparent !important;
font-size: 1.2em !important;
padding: 0.5rem !important;
}
/* Ensure LaTeX works inside streaming text */
.streaming-text .katex {
animation: none !important;
background: rgba(33, 150, 243, 0.05) !important;
}
.streaming-text .katex-display {
margin: 0.5rem 0 !important;
padding: 0.8rem !important;
}
/* Stop cursor animation on LaTeX elements */
.katex::after {
content: '' !important;
animation: none !important;
}
/* Control panel styling */
.control-panel {
background: linear-gradient(145deg, #f8f9fa 0%, #e3f2fd 100%);
padding: 1rem;
border-radius: 15px;
margin: 1rem 0;
border: 1px solid #bbdefb;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
/* Enhanced streaming text animation */
.streaming-text {
display: inline-block;
overflow: hidden;
white-space: pre-wrap;
}
.streaming-text::after {
content: '▋';
animation: cursor-blink 1s infinite;
font-weight: bold;
color: #2196F3;
}
.streaming-character {
display: inline-block;
animation: charPop 0.1s ease-out;
}
@keyframes charPop {
0% { opacity: 0; transform: translateY(2px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes cursor-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* Enhanced progress indicator */
.progress-indicator {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
color: #0d47a1;
font-weight: 600;
padding: 0.5rem;
min-height: 100px;
}
.thinking-dots {
display: flex;
gap: 0.2rem;
}
.thinking-dots span {
width: 0.4rem;
height: 0.4rem;
border-radius: 50%;
background: #2196F3;
animation: thinking 1.4s infinite ease-in-out;
}
.thinking-dots span:nth-child(1) { animation-delay: -0.32s; }
.thinking-dots span:nth-child(2) { animation-delay: -0.16s; }
.thinking-dots span:nth-child(3) { animation-delay: 0s; }
@keyframes thinking {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
/* Cache indicator styling */
.cache-badge {
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
color: white;
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 0.2rem;
}
/* Enhanced answer container styling */
.answer-container {
background: linear-gradient(135deg, #f8fdff 0%, #ffffff 100%);
border-radius: 10px;
padding: 1rem;
margin: 1rem 0;
border: 1px solid #e1f5fe;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.05);
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Highlight animation for new answers */
@keyframes highlightPulse {
0% { box-shadow: 0 0 0 0 rgba(33, 150, 243, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(33, 150, 243, 0); }
100% { box-shadow: 0 0 0 0 rgba(33, 150, 243, 0); }
}
.highlight-answer {
animation: highlightPulse 1.5s ease-in-out;
}
/* Focus container for auto-scroll */
.focus-container {
scroll-margin-top: 100px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.header-container {
padding: 0.75rem;
}
.subject-card {
padding: 0.5rem;
}
.user-bubble, .ai-bubble {
max-width: 90%;
}
.control-panel {
padding: 0.75rem;
}
}
/* Subscription banner styling */
.subscription-banner {
padding: 0.75rem 1rem;
border-radius: 10px;
margin: 0.5rem 0 1.5rem 0;
border-left: 5px solid;
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
color: white;
}
.subscription-banner strong {
font-size: 1rem;
}
.subscription-banner .usage-stats {
font-size: 0.85rem;
opacity: 0.9;
}
.subscription-banner .upgrade-btn {
background: rgba(255,255,255,0.2);
padding: 0.3rem 0.8rem;
border-radius: 5px;
font-size: 0.85rem;
cursor: pointer;
}
</style>
""", unsafe_allow_html=True)
# ===============================
# HELPER FUNCTIONS - FIXED CACHE KEY
# ===============================
def create_cache_key(question, subject, chapter_name):
"""Create a unique cache key for the question"""
# Normalize the question more aggressively for better cache matching
normalized_question = question.strip().lower()
# Remove extra whitespace
normalized_question = re.sub(r'\s+', ' ', normalized_question)
# Remove punctuation that might vary
normalized_question = re.sub(r'[^\w\s\u0980-\u09FF]', '', normalized_question)
normalized_question = normalized_question[:200]
# Normalize subject and chapter
# Take only the main subject name (before parentheses)
normalized_subject = subject.split('(')[0].strip() if '(' in subject else subject
# Take only chapter number/name before colon
normalized_chapter = chapter_name.split(':')[0].strip() if ':' in chapter_name else chapter_name
key_string = f"{normalized_subject}|{normalized_chapter}|{normalized_question}"
cache_key = hashlib.md5(key_string.encode()).hexdigest()
return cache_key
def get_question_guidance(question, subject, chapter_name):
question_lower = question.lower()
simple_keywords = [
"সংজ্ঞা", "কি", "কাক কয়", "মানে", "definition", "what is",
"নাম", "কেইটা", "কিমান", "count", "number", "কি নাম", "কাক বোলে"
]
moderate_keywords = [
"কেনেকৈ", "কেনেকুৱা", "কিয়", "বুজাই দিয়ক", "explain", "how",
"why", "difference", "পাৰ্থক্য", "উদাহৰণ", "example", "সমাধান",
"solve", "কোনবোৰ", "তুলনা", "compare", "সাদৃশ্য", "similarity"
]
complex_keywords = [
"বিশ্লেষণ", "আলোচনা", "মূল্যায়ন", "বৰ্ণনা", "discuss",
"analyze", "evaluate", "describe", "প্ৰমাণ", "prove",
"সমাধান কৰি দেখুৱাওক", "solve and show", "step by step",
"ধাপে ধাপে", "সম্পূৰ্ণ", "সম্পূৰ্ণ বিৱৰণ", "full explanation",
"সবিশেষ", "in detail", "detailed", "সবিস্তাৰে"
]
guidance_text = ""
if "📐 গণিত" in subject:
guidance_text = "গণিতৰ সমস্যাৰ বাবে ধাপে ধাপে সমাধান দিব লাগে। "
elif "🔬 বিজ্ঞান" in subject:
guidance_text = "বিজ্ঞানৰ উত্তৰ বৈজ্ঞানিকভাৱে সঠিক হ'ব লাগে। "
elif "🌍 সমাজ বিজ্ঞান" in subject:
guidance_text = "তথ্য সঠিক আৰু বিশ্লেষণাত্মক হ'ব লাগে। "
if any(keyword in question_lower for keyword in complex_keywords):
return f"{guidance_text} প্ৰশ্নটো জটিল, গতিকে বিশদ উত্তৰ দিবা।"
elif any(keyword in question_lower for keyword in moderate_keywords):
return f"{guidance_text} প্ৰশ্নটো মধ্যমীয়া, গতিকে সম্পূৰ্ণ উত্তৰ দিবা।"
elif any(keyword in question_lower for keyword in simple_keywords):
return f"{guidance_text} প্ৰশ্নটো সৰল, গতিকে সংক্ষিপ্ত উত্তৰ দিবা।"
else:
return f"{guidance_text} প্ৰশ্নৰ প্ৰকৃতি অনুসৰি উত্তৰ দিবা।"
def get_subject_prompt(subject, chapter_name, question):
if subject not in SUBJECT_PROMPTS:
subject = "📐 গণিত (Mathematics)"
prompt_template = SUBJECT_PROMPTS[subject]
base_prompt = prompt_template["base_prompt"].format(chapter_name=chapter_name)
guidance = prompt_template["guidance"]
if subject == "📐 গণিত (Mathematics)" or subject == "🔬 বিজ্ঞান (Science)":
latex_instruction = "\n\n**গুৰুত্বপূৰ্ণ**: সকলো গাণিতিক সূত্ৰ, সমীকৰণ LaTeX ফৰ্মেটত দিবা ($ চিহ্নৰ মাজত)।"
else:
latex_instruction = ""
question_guidance = get_question_guidance(question, subject, chapter_name)
full_prompt = f"""{base_prompt}
{guidance}{latex_instruction}
**উত্তৰৰ নিৰ্দেশনা:**
{question_guidance}
**উত্তৰ যিমান দৰকাৰী সিমান দীঘল হ'ব লাগে।**
**শুদ্ধ অসমীয়া ব্যৱহাৰ কৰিবা, বাংলা শব্দ বা বৰ্ণমালা ব্যৱহাৰ নকৰিবা**
**ছাত্ৰক মাতি লওঁ:**
"বন্ধু, এইটো এনেদৰে বুজিব লাগে..."
"চিন্তা নকৰিব, এইটো সহজ..."
এতিয়া এই প্ৰশ্নটোৰ উত্তৰ দিয়া: {question}"""
return full_prompt
# ===============================
# FIXED: STREAMING TEXT WITH LATEX SUPPORT
# ===============================
def stream_text_with_animation(text, placeholder, speed=10):
"""
Display text with streaming animation (character by character)
WITH PROPER LATEX SUPPORT
"""
display_text = ""
# Split into characters for animation
for i, char in enumerate(text):
display_text += char
# Update streaming display with better animation
placeholder.markdown(
f'<div class="streaming-text">{display_text}</div>',
unsafe_allow_html=True
)
# Control speed (except for whitespace)
if char not in [' ', '\n']:
time.sleep(1/speed)
# CRITICAL FIX: After completion, clear and re-render with proper LaTeX support
placeholder.empty() # Clear the streaming placeholder
# Re-render the complete text with proper markdown/LaTeX support
placeholder.markdown(text)
return True
# ===============================
# ENHANCED: STREAMLIT STREAMING RESPONSE FUNCTION
# ===============================
def stream_deepseek_response(prompt, question, subject, chapter_name):
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "তুমি এজন বিশেষজ্ঞ SEBA দশম শ্ৰেণীৰ শিক্ষক।"},
{"role": "user", "content": prompt}
],
"temperature": 0.3,
"stream": True
}
try:
# Make streaming request
response = requests.post(
"https://api.deepseek.com/v1/chat/completions",
headers=headers,
json=payload,
stream=True,
timeout=180
)
if response.status_code == 200:
full_response = ""
tokens_used = 0
# Create a placeholder for streaming text
streaming_placeholder = st.empty()
# Process streaming response
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data: '):
data = line[6:] # Remove 'data: ' prefix
if data == '[DONE]':
break
try:
chunk = json.loads(data)
if 'choices' in chunk and len(chunk['choices']) > 0:
delta = chunk['choices'][0].get('delta', {})
if 'content' in delta:
content = delta['content']
full_response += content
# Update streaming display with better animation
streaming_placeholder.markdown(
f'<div class="streaming-text">{full_response}</div>',
unsafe_allow_html=True
)
# Track tokens
if 'usage' in chunk:
tokens_used = chunk['usage'].get('total_tokens', 0)
except json.JSONDecodeError:
continue
# Clear streaming cursor after completion and re-render with LaTeX support
streaming_placeholder.empty()
# Render the final answer with proper LaTeX support
st.markdown(full_response)
# Store the complete response
st.session_state.last_answer = full_response
st.session_state.tokens_used = tokens_used
# Save to cache using manager
cache_key = create_cache_key(question, subject, chapter_name)
st.session_state.cache_manager.set(cache_key, {
'answer': full_response,
'tokens': tokens_used,
'subject': subject,
'chapter': chapter_name,
'question': question[:200]
})
# Add to history
history_entry = {
'subject': subject,
'chapter': chapter_name,
'question': question[:100],
'timestamp': datetime.now().strftime("%H:%M"),
'tokens': tokens_used,
'cached': False
}
st.session_state.history.append(history_entry)
# Add JavaScript to scroll to answer and highlight it
st.markdown("""
<script>
// Scroll to answer container smoothly
setTimeout(function() {
const answerContainers = document.querySelectorAll('.chat-container');
if (answerContainers.length > 0) {
const lastAnswer = answerContainers[answerContainers.length - 1];
lastAnswer.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Add highlight animation
lastAnswer.classList.add('highlight-answer');
setTimeout(() => {
lastAnswer.classList.remove('highlight-answer');
}, 1500);
}
}, 100);
</script>
""", unsafe_allow_html=True)
else:
st.error(f"API ত্ৰুটি {response.status_code}: {response.text}")
except Exception as e:
st.error(f"সংযোগ ত্ৰুটি: {str(e)}")
# ===============================
# FIXED: CACHE ANSWER WITH THINKING ANIMATION
# ===============================
def display_cached_answer_with_animation(cached_data, question, subject, chapter_name, cache_source):
"""
Display cached answer with thinking animation and streaming effect
"""
# Display user question
st.markdown(f"""
<div class="chat-container">
<div style="display: flex; justify-content: flex-end; margin-bottom: 0.3rem;">
<div class="user-bubble">
<div style="font-weight: 600; margin-bottom: 0.2rem;">👤 আপুনি:</div>
<div>{question[:200]}{'...' if len(question) > 200 else ''}</div>
</div>
</div>
""", unsafe_allow_html=True)
# AI answer header with thinking animation initially
st.markdown(f"""
<div style="display: flex; align-items: flex-start; margin-bottom: 0.3rem;">
<div style="margin-right: 0.5rem; font-size: 1.2rem;">🤖</div>
<div style="flex: 1;">
<div class="ai-bubble">
<div style="display: flex; align-items: center; margin-bottom: 0.5rem; padding-bottom: 0.5rem; border-bottom: 2px solid #4CAF50;">
<div style="display: flex; align-items: center;">
<div style="background: #4CAF50; color: white; padding: 0.2rem 0.5rem; border-radius: 8px;
font-weight: 600; font-size: 0.8rem; margin-right: 0.5rem;">
<span style="margin-right: 0.3rem;">⚡</span> Cached Answer
</div>
<div style="font-weight: 600; color: #0d47a1; font-size: 0.9rem;">
{cached_data.get('subject', subject)}{cached_data.get('chapter', chapter_name)}
</div>
</div>
<div style="font-size: 0.75rem; color: #666; background: #f1f8e9; padding: 0.2rem 0.5rem; border-radius: 4px;">
<span style="margin-right: 0.3rem;">💾</span> From {cache_source}
</div>
</div>
<div id="cached-answer-content" style="color: #333; line-height: 1.5; font-size: 0.95rem;">
<!-- Answer will be streamed here -->
</div>
</div>
</div>
</div>
</div>
""", unsafe_allow_html=True)
# Create a placeholder for the thinking animation
thinking_placeholder = st.empty()
# Show thinking animation for 1 second
thinking_placeholder.markdown("""
<div class="progress-indicator">
<span>উত্তৰ প্ৰস্তুত কৰি আছো...</span>
<div class="thinking-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
""", unsafe_allow_html=True)
# Wait for 1 second to simulate thinking
time.sleep(1)
# Clear thinking animation
thinking_placeholder.empty()
# Create a new placeholder for streaming answer
answer_placeholder = st.empty()
# Stream the cached answer with animation (USING FIXED FUNCTION)
stream_text_with_animation(cached_data['answer'], answer_placeholder, speed=20)
# Show token usage
if cached_data.get('tokens', 0) > 0:
st.caption(f"📊 Original token cost (saved): {cached_data['tokens']:,} tokens")
# Add JavaScript to scroll to answer and highlight it
st.markdown("""
<script>
// Scroll to answer container smoothly
setTimeout(function() {
const answerContainers = document.querySelectorAll('.chat-container');
if (answerContainers.length > 0) {
const lastAnswer = answerContainers[answerContainers.length - 1];
lastAnswer.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Add highlight animation
lastAnswer.classList.add('highlight-answer');
setTimeout(() => {
lastAnswer.classList.remove('highlight-answer');
}, 1500);
}
}, 100);
</script>
""", unsafe_allow_html=True)
# Add to history
history_entry = {
'subject': subject,
'chapter': chapter_name,
'question': question[:100],
'timestamp': datetime.now().strftime("%H:%M"),
'tokens': cached_data['tokens'],
'cached': True,
'cache_source': cache_source
}
st.session_state.history.append(history_entry)
# ===============================
# SEBA CURRICULUM DATA
# ===============================
SEBA_CURRICULUM = {
"📐 গণিত (Mathematics)": {
"অধ্যায় ১": "বাস্তৱ সংখ্যা (Real Numbers)",
"অধ্যায় ২": "বহুপদ (Polynomials)",
"অধ্যায় ৩": "দ্বিঘাত সমীকৰণ (Quadratic Equations)",
"অধ্যায় ৪": "সামান্তৰিক শ্রেণী (Arithmetic Progressions)",
"অধ্যায় ৫": "ত্ৰিভুজ (Triangles)",
"অধ্যায় ৬": "ত্রিকোণমিতি (Trigonometry)",
"অধ্যায় ৭": "বৃত্ত (Circles)",
"অধ্যায় ৮": "স্থানাঙ্ক জ্যামিতি (Coordinate Geometry)",
"অধ্যায় ৯": "ক্ষেত্রফল আৰু আয়তন (Areas and Volumes)",
"অধ্যায় ১০": "পৰিসংখ্যা (Statistics)",
"অধ্যায় ১১": "সম্ভাৱিতা (Probability)"
},
"🔬 বিজ্ঞান (Science)": {
"অধ্যায় ১": "ৰাসায়নিক বিক্রিয়া আৰু সমীকৰণ",
"অধ্যায় ২": "এছিড, ক্ষাৰক আৰু লৱণ",
"অধ্যায় ৩": "ধাতু আৰু অধাতু",
"অধ্যায় ৪": "কার্বন আৰু তাৰ যৌগ",
"অধ্যায় ৫": "পৰ্যাবৃত্ত শ্রেণীবিভাজন",
"অধ্যায় ৬": "জীৱন প্ৰক্ৰিয়া",
"অধ্যায় ৭": "নিয়ন্ত্ৰণ আৰু সমন্বয়",
"অধ্যায় ৮": "জীৱই কেনেদৰে বংশবিস্তাৰ কৰে",
"অধ্যায় ৯": "আনুভূমিক আৰু ঊর্ধ্বমুখী বংশগতি",
"অধ্যায় ১০": "পোহৰ-প্ৰতিফলন আৰু প্ৰতিসৰণ",
"অধ্যায় ১১": "মানুহৰ চকু আৰু বৰ্ণিল পৃথিৱী",
"অধ্যায় ১২": "বিদ্যুৎ",
"অধ্যায় ১৩": "বিদ্যুৎ-চুম্বকীয় প্ৰভাৱ",
"অধ্যায় ১৪": "শক্তিৰ উৎসসমূহ",
"অধ্যায় ১৫": "আমাৰ পৰিৱেশ",
"অধ্যায় ১৬": "প্রাকৃতিক সম্পদৰ ব্যৱস্থাপনা"
},
"🌍 সমাজ বিজ্ঞান (Social Science)": {
"অধ্যায় ১": "ইউৰোপত ৰাষ্ট্ৰবাদৰ উত্থান",
"অধ্যায় ২": "ভাৰতীয় জাতীয়তাবাদৰ উত্থান",
"অধ্যায় ৩": "ভূগোল-প্রাকৃতিক আৰু মানৱ",
"অধ্যায় ৪": "অৰ্থনীতি-উন্নয়ন",
"অধ্যায় ৫": "লোকসাধাৰণৰ সংস্কৃতি আৰু জাতীয়তাবাদ",
"অধ্যায় ৬": "উদ্যোগ",
"অধ্যায় ৭": "অৰ্থনৈতিক অৱস্থা",
"অধ্যায় ৮": "ৰাজনৈতিক দল",
"অধ্যায় ৯": "ক্ষমতাৰ ভাগ-বতৰা",
"অধ্যায় ১০": "জনসম্পদ"
},
"📖 ইংৰাজী (English)": {
"পাঠ ১": "A Letter to God",
"পাঠ ২": "Nelson Mandela: Long Walk to Freedom",
"পাঠ ৩": "Two Stories about Flying",
"পাঠ ৪": "From the Diary of Anne Frank",
"পাঠ ৫": "The Hundred Dresses – I",
"পাঠ ৬": "The Hundred Dresses – II",
"পাঠ ৭": "Glimpses of India",
"পাঠ ৮": "Mijbil the Otter",
"পাঠ ৯": "Madam Rides the Bus",
"পাঠ ১০": "The Sermon at Benares",
"পাঠ ১১": "The Proposal"
},
"📜 অসমীয়া (Assamese)": {
"পাঠ ১": "বৰগীত",
"পাঠ ২": "জীৱন-সঙ্গীত",
"পাঠ ৩": "প্রশস্তি",
"পাঠ ৪": "মোৰ মৰমি জনমভূমি",
"পাঠ ৫": "অসমীয়া ভাষাৰ উন্নতি",
"পাঠ ৬": "অসমৰ লোক-সংস্কৃতি",
"পাঠ ৭": "আমাৰ ঋতু",
"পাঠ ৮": "বহাগ বিহু",
"পাঠ ৯": "মহাপুরুষীয়া ধৰ্ম",
"পাঠ ১০": "সাহিত্যৰ ৰূপ"
},
"📘 হিন্দী (Hindi)": {
"পাঠ ১": "साखी",
"পাঠ ২": "पद",
"পাঠ ৩": "दोहे",
"পাঠ ৪": "मनुष्यता",
"পাঠ ५": "पर्वत प्रदेश में पावस",
"পাঠ ६": "मधुर-मधुर मेरे दीपक जल",
"পাঠ ৭": "तोप",
"পাঠ ৮": "कर चले हम फ़िदा",
"পাঠ ৯": "आत्मत्राण",
"পাঠ ১০": "बड़े भाई साहब"
}
}
# Subject-wise prompt templates
SUBJECT_PROMPTS = {
"📐 গণিত (Mathematics)": {
"base_prompt": """তুমি এজন বিশেষজ্ঞ গণিত শিক্ষক। SEBA দশম শ্ৰেণীৰ গণিতৰ পাঠ্যপুথিৰ {chapter_name} অধ্যায়ত থকা সকলো ধাৰণা, সূত্ৰ, আৰু উদাহৰণ তুমি ভালকৈ জানা।
**গণিতৰ বিশেষ নিৰ্দেশনা:**
১. **সকলো সূত্ৰ LaTeX ফৰ্মেটত দিবা**: $formula$ (দুয়োটা $ চিহ্নৰ মাজত)
২. **ধাপে ধাপে সমাধান দেখুৱাবা**
৩. **প্ৰতিটো ধাপৰ ব্যাখ্যা দিবা**
৪. **সহজ পদ্ধতিৰে বুজাবা**
৫. **পৰীক্ষাৰ বাবে গুৰুত্বপূৰ্ণ সূত্ৰবোৰ পৃথকৈ দেখুৱাবা**
৬. **সকলো গাণিতিক সমীকৰণ আৰু সূতৰবোৰ `$` চিহ্নৰ মাজত লিখিবা, আলোচনাৰ বাহিৰত পৃথক লাইনত দেখুৱাবা।**
**গণিতৰ সূত্ৰৰ উদাহৰণ (LaTeX ফৰ্মেটত):**
- দ্বিঘাত সমীকৰণ: $ax^2 + bx + c = 0$
- বৃত্তৰ কালি: $A = \\pi r^2$
- সম্ভাৱিতা: $P(E) = \\frac{{n(E)}}{{n(S)}}$
- পাইথাগোৰাছৰ উপপাদ্য: $a^2 + b^2 = c^2$
**বক্তব্য শৈলী:**
"চিন্তা নকৰিব, এই গণিতৰ সমস্যাটো সহজ।"
"ধাপে ধাপে শিকো আহক..."
"এই সূত্ৰটো মনত ৰাখিব - পৰীক্ষাত আহিব পাৰে!" """,
"guidance": "সমীকৰণ, সূত্ৰ আৰু গাণিতিক প্ৰক্ৰিয়া LaTeX ফৰ্মেটত দেখুৱাব লাগে।"
},
"🔬 বিজ্ঞান (Science)": {
"base_prompt": """তুমি এজন বিজ্ঞান শিক্ষক। SEBA দশম শ্ৰেণীৰ বিজ্ঞানৰ {chapter_name} অধ্যায়ৰ সকলো বৈজ্ঞানিক ধাৰণা, প্ৰক্ৰয়া, আৰু নীতি তুমি জানা।
**বিজ্ঞানৰ বিশেষ নিৰ্দেশনা:**
১. **বৈজ্ঞানিক প্ৰক্ৰয়া ধাপে ধাপে বুজাবা**
২. **ৰাসায়নিক সমীকৰণ সঠিকভাৱে দিবা**
৩. **জীৱবিজ্ঞানৰ চিত্ৰ/ৰেখাচিত্ৰৰ বৰ্ণনা দিবা**
৪. **পদাৰ্থবিজ্ঞানৰ সূত্ৰ LaTeX ফৰ্মেটত দিবা**
**ৰাসায়নিক উদাহৰণ:**
$2H_2 + O_2 \\rightarrow 2H_2O$
**পদাৰ্থবিজ্ঞান সূত্ৰ:**
$F = ma$, $v = u + at$
**বক্তব্য শৈলী:**
"এই বৈজ্ঞানিক ধাৰণাটো বুজোৱাৰ বাবে এটা সাধাৰণ উদাহৰণ চাওঁ..."
"প্ৰকতিৰ এই ৰহস্যবোৰ মন কৰিছিল নেকি?" """,
"guidance": "ৰাসায়নিক সমীকৰণ আৰু পদাৰ্থবিজ্ঞানৰ সূত্ৰ LaTeX ফৰ্মেটত দিব লাগে।"
},
"🌍 সমাজ বিজ্ঞান (Social Science)": {
"base_prompt": """তুমি এজন সমাজ বিজ্ঞান শিক্ষক। SEBA দশম শ্ৰেণীৰ {chapter_name} অধ্যায়ৰ ঐতিহাসিক ঘটনা, ভৌগোলিক ধাৰণা, অৰ্থনৈতিক নীতি, আৰু ৰাজনৈতিক গঠন তুমি জানা।
**সমাজ বিজ্ঞানৰ বিশেষ নিৰ্দেশনা:**
১. **সহজ অসমীয়া ভাষা ব্যৱহাৰ কৰিবা**
২. **প্ৰশ্ন অনুসৰি উত্তৰ দিবা**""",
"guidance": "তথ্য আৰু বিশ্লেষণ স্পষ্টকৈ দিব লাগে।"
},
"📖 ইংৰাজী (English)": {
"base_prompt": """তুমি এজন ইংৰাজী শিক্ষক। SEBA দশম শ্ৰেণীৰ {chapter_name} পাঠটোৰ সকলো সাহিত্যিক উপাদান, ব্যাকৰণ, আৰু ভাষা কৌশল তুমি জানা।
**ইংৰাজীৰ বিশেষ নিৰ্দেশনা:**
১. Answer in English with Assamese translation""",
"guidance": "ইংৰাজী বাক্যৰ সৈতে অসমীয়া ব্যাখ্যা দিব লাগে।"
},
"📜 অসমীয়া (Assamese)": {
"base_prompt": """তুমি এজন অসমীয়া সাহিত্য শিক্ষক। SEBA দশম শ্ৰেণীৰ {chapter_name} পাঠটোৰ সাহিত্যিক মুল্য, ভাষা বৈশিষ্ট্য, আৰু সাংস্কৃতিক প্ৰসংগ তুমি জানা।
**অসমীয়াৰ বিশেষ নিৰ্দেশনা:**
১. **সাহিত্যিক বিশ্লেষণ অসমীয়াত দিবা**
২. **প্ৰশ্ন অনুসৰি উত্তৰ দিবা**""",
"guidance": "অসমীয়া ভাষাৰ সৌন্দৰ্য্য আৰু গভীৰতা দেখুৱাব লাগে।"
},
"📘 হিন্দী (Hindi)": {
"base_prompt": """तुम एक हिंदी शिक्षक हो। SEBA दशम श्रेणी के {chapter_name} पाठ के सभी साहित्यिक तत्व, व्याकरण, और भाषा कौशल तुम जानते हो।
**हिंदी के विशेष निर्देश:**
१. **साहित्यिक विश्लेषण हिंदी में देना, साथ असमिया व्याख्या देना**
२. **प्रश्न के अनुसार उत्तर देना**""",
"guidance": "हिंदी वाक्य के साथ असमिया व्याख्या देना"
}
}
# ===============================
# SIMPLE AUTHENTICATION UI
# ===============================
def show_simple_auth():
"""Show simple login/signup for testing"""
st.markdown("""
<div style="text-align: center; padding: 2rem; background: linear-gradient(135deg, #0d47a1 0%, #1976d2 100%);
border-radius: 15px; color: white; margin-bottom: 2rem;">
<h1 style="color: white;">🎓 SEBA AI Tutor</h1>
<p>Assam's Smart AI Tutor for Class 10 Students</p>
</div>
""", unsafe_allow_html=True)
# Simple email-based authentication for testing
st.subheader("🔐 Quick Login for Testing")
# Create two columns
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Test with Sample Account")
if st.button("Login as Test Student", use_container_width=True, type="primary"):
test_email = "student@example.com"
user_id = hashlib.md5(test_email.encode()).hexdigest()
st.session_state.user_id = user_id
st.session_state.user_email = test_email
st.session_state.logged_in = True
# Create user in database if subscription manager exists
if st.session_state.subscription_manager:
st.session_state.subscription_manager.create_user(user_id, email=test_email)
st.session_state.user_plan = st.session_state.subscription_manager.get_user_plan(user_id)
st.success("✅ Logged in as test student!")
time.sleep(1)
st.rerun()
with col2:
st.markdown("#### Use Your Email")
user_email = st.text_input("Enter your email")
if st.button("Login / Sign Up", use_container_width=True, type="secondary"):
if user_email and "@" in user_email:
user_id = hashlib.md5(user_email.lower().strip().encode()).hexdigest()
st.session_state.user_id = user_id
st.session_state.user_email = user_email
st.session_state.logged_in = True
# Create user in database if subscription manager exists
if st.session_state.subscription_manager:
st.session_state.subscription_manager.create_user(user_id, email=user_email)
st.session_state.user_plan = st.session_state.subscription_manager.get_user_plan(user_id)
st.success(f"✅ Welcome {user_email}!")
time.sleep(1)
st.rerun()
else:
st.error("Please enter a valid email address")
# Show features
st.markdown("---")
st.markdown("### ✨ Features")
features_col1, features_col2, features_col3 = st.columns(3)
with features_col1:
st.markdown("""
<div style="text-align: center; padding: 1rem;">
<div style="font-size: 2.5rem;">📚</div>
<h4>All SEBA Subjects</h4>
<p>Mathematics, Science, Social Science, English, Assamese, Hindi</p>
</div>
""", unsafe_allow_html=True)
with features_col2:
st.markdown("""
<div style="text-align: center; padding: 1rem;">
<div style="font-size: 2.5rem;">🤖</div>
<h4>AI Tutor</h4>
<p>Step-by-step explanations in Assamese & English</p>
</div>
""", unsafe_allow_html=True)
with features_col3:
st.markdown("""
<div style="text-align: center; padding: 1rem;">
<div style="font-size: 2.5rem;">⚡</div>
<h4>Instant Answers</h4>
<p>Cached responses for faster learning</p>
</div>
""", unsafe_allow_html=True)
# Pricing info
st.markdown("---")
st.markdown("### 💰 Pricing Plans")
pricing_col1, pricing_col2, pricing_col3 = st.columns(3)
with pricing_col1:
st.markdown("""
<div style="border: 2px solid #4CAF50; border-radius: 10px; padding: 1rem; text-align: center;">
<h3 style="color: #4CAF50;">🆓 Free</h3>
<h2>₹0</h2>
<p><strong>1 question/day</strong></p>
<p>30 lifetime questions</p>
<p>Perfect for trying out</p>
</div>
""", unsafe_allow_html=True)
with pricing_col2:
st.markdown("""
<div style="border: 2px solid #FF9800; border-radius: 10px; padding: 1rem; text-align: center; background: #FFF8E1;">
<h3 style="color: #FF9800;">⭐ Pro</h3>
<h2>₹50/month</h2>
<p><strong>15 questions/day</strong></p>
<p>450 questions/month</p>
<p>Best for regular study</p>
</div>
""", unsafe_allow_html=True)
with pricing_col3:
st.markdown("""
<div style="border: 2px solid #2196F3; border-radius: 10px; padding: 1rem; text-align: center; background: #E3F2FD;">
<h3 style="color: #2196F3;">✨ Pro Plus</h3>
<h2>₹100/month</h2>
<p><strong>40 questions/day</strong></p>
<p>1200 questions/month</p>
<p>For serious learners</p>
</div>
""", unsafe_allow_html=True)
def show_user_dashboard():
"""Show user dashboard in sidebar"""
if st.session_state.get('logged_in'):
st.sidebar.markdown("---")
st.sidebar.markdown("### 👤 Your Account")
# Show user info
if st.session_state.get('user_email'):
st.sidebar.markdown(f"**Email:** {st.session_state.user_email}")
# Show plan info
if st.session_state.get('user_plan'):
plan = st.session_state.user_plan
plan_name = plan['plan_type'].replace('_', ' ').title()
st.sidebar.markdown(f"**Plan:** {plan_name}")
st.sidebar.markdown(f"**Used:** {plan['questions_used']}/{plan['total_questions']}")
# Progress bar
progress = plan['questions_used'] / plan['total_questions']
st.sidebar.progress(min(progress, 1.0))
# Daily usage stats
if st.session_state.subscription_manager:
stats = st.session_state.subscription_manager.get_user_stats(st.session_state.user_id)
st.sidebar.markdown(f"**Today:** {stats['today_used']}/{plan['daily_limit']}")
# Upgrade button for free users
if plan['plan_type'] == 'free':
if st.sidebar.button("🚀 Upgrade Plan", use_container_width=True, type="primary"):
st.session_state.show_subscription_plans = True
st.rerun()
# Logout button
if st.sidebar.button("🚪 Logout", use_container_width=True):
for key in ['user_id', 'user_email', 'user_phone', 'logged_in', 'user_plan', 'show_subscription_plans']:
if key in st.session_state:
del st.session_state[key]
st.rerun()
# ===============================
# INITIALIZE SESSION STATE - FIXED
# ===============================
if 'history' not in st.session_state:
st.session_state.history = []
if 'current_subject' not in st.session_state:
st.session_state.current_subject = "📐 গণিত (Mathematics)"
if 'current_chapter' not in st.session_state:
st.session_state.current_chapter = "অধ্যায় ১"
if 'processing' not in st.session_state:
st.session_state.processing = False
if 'last_answer' not in st.session_state:
st.session_state.last_answer = None
if 'question_text' not in st.session_state:
st.session_state.question_text = ""
if 'streaming_answer' not in st.session_state:
st.session_state.streaming_answer = ""
if 'tokens_used' not in st.session_state:
st.session_state.tokens_used = 0
if 'cache_manager' not in st.session_state:
st.session_state.cache_manager = SupabaseCache(ttl_days=7)
# Pre-warm cache by checking Supabase connection on startup
cache_stats = st.session_state.cache_manager.get_stats()
if cache_stats['supabase_connected'] and cache_stats['supabase_entries'] > 0:
st.toast(f"📦 Cache loaded: {cache_stats['supabase_entries']} entries available", icon="✅")
if 'show_cached_answer' not in st.session_state:
st.session_state.show_cached_answer = False
if 'cached_answer_data' not in st.session_state:
st.session_state.cached_answer_data = None
if 'current_cache_key' not in st.session_state:
st.session_state.current_cache_key = None
if 'cache_debug' not in st.session_state:
st.session_state.cache_debug = False
# ===============================
# SUBSCRIPTION MANAGER INITIALIZATION
# ===============================
if 'subscription_manager' not in st.session_state:
if 'cache_manager' in st.session_state and st.session_state.cache_manager.supabase:
st.session_state.subscription_manager = SupabaseSubscriptionManager(
st.session_state.cache_manager.supabase
)
else:
st.session_state.subscription_manager = None
# Initialize authentication state
if 'logged_in' not in st.session_state:
st.session_state.logged_in = False
if 'user_id' not in st.session_state:
st.session_state.user_id = None
if 'user_email' not in st.session_state:
st.session_state.user_email = None
if 'user_phone' not in st.session_state:
st.session_state.user_phone = None
if 'show_subscription_plans' not in st.session_state:
st.session_state.show_subscription_plans = False
if 'user_plan' not in st.session_state:
st.session_state.user_plan = None
# ===============================
# AUTHENTICATION CHECK
# ===============================
# Check if user needs to login
if not st.session_state.get('logged_in'):
show_simple_auth()
st.stop()
# Show subscription plans if requested
if st.session_state.get('show_subscription_plans'):
st.title("📊 Upgrade Your Plan")
# Simple plans display
col1, col2, col3 = st.columns(3)
with col1:
st.markdown("""
<div style="border: 2px solid #e0e0e0; border-radius: 10px; padding: 1.5rem; text-align: center; height: 100%;">
<h3 style="color: #0d47a1;">🆓 Free</h3>
<h1 style="color: #4CAF50;">₹0</h1>
<p><strong>1 question/day</strong></p>
<p>30 lifetime questions</p>
<hr>
<p>✓ Basic access</p>
<p>✓ Cached answers</p>
</div>
""", unsafe_allow_html=True)
if st.session_state.get('user_plan', {}).get('plan_type') == 'free':
st.success("✓ Current Plan")
else:
st.button("Select", disabled=True, use_container_width=True)
with col2:
st.markdown("""
<div style="border: 2px solid #FF9800; border-radius: 10px; padding: 1.5rem; text-align: center; height: 100%; background: #FFF8E1;">
<h3 style="color: #FF9800;">⭐ Pro Monthly</h3>
<h1 style="color: #FF9800;">₹50</h1>
<p><strong>450 questions/month</strong></p>
<p>≈ 15 questions/day</p>
<hr>
<p>✓ All Free features</p>
<p>✓ Priority access</p>
<p>✓ 30-day validity</p>
</div>
""", unsafe_allow_html=True)
if st.session_state.get('user_plan', {}).get('plan_type') == 'pro_monthly':
st.success("✓ Current Plan")
else:
if st.button("Upgrade to Pro", type="primary", use_container_width=True):
if st.session_state.subscription_manager:
success, message = st.session_state.subscription_manager.upgrade_plan(
st.session_state.user_id, 'pro_monthly'
)
if success:
st.success(message)
st.session_state.user_plan = st.session_state.subscription_manager.get_user_plan(st.session_state.user_id)
time.sleep(2)
st.session_state.show_subscription_plans = False
st.rerun()
else:
st.error(message)
with col3:
st.markdown("""
<div style="border: 2px solid #2196F3; border-radius: 10px; padding: 1.5rem; text-align: center; height: 100%; background: #E3F2FD;">
<h3 style="color: #2196F3;">✨ Pro Plus</h3>
<h1 style="color: #2196F3;">₹100</h1>
<p><strong>1200 questions/month</strong></p>
<p>≈ 40 questions/day</p>
<hr>
<p>✓ All Pro features</p>
<p>✓ Highest priority</p>
<p>✓ 30-day validity</p>
</div>
""", unsafe_allow_html=True)
if st.session_state.get('user_plan', {}).get('plan_type') == 'pro_plus':
st.success("✓ Current Plan")
else:
if st.button("Upgrade to Pro Plus", type="primary", use_container_width=True):
if st.session_state.subscription_manager:
success, message = st.session_state.subscription_manager.upgrade_plan(
st.session_state.user_id, 'pro_plus'
)
if success:
st.success(message)
st.session_state.user_plan = st.session_state.subscription_manager.get_user_plan(st.session_state.user_id)
time.sleep(2)
st.session_state.show_subscription_plans = False
st.rerun()
else:
st.error(message)
st.markdown("---")
st.info("💡 **Note:** For now, this is a simulation. Real payment integration will be added soon.")
if st.button("← Back to Tutor", use_container_width=True):
st.session_state.show_subscription_plans = False
st.rerun()
st.stop()
# User is logged in - show main app
# Load user plan if not loaded
if st.session_state.logged_in and not st.session_state.get('user_plan'):
if st.session_state.subscription_manager and st.session_state.user_id:
st.session_state.user_plan = st.session_state.subscription_manager.get_user_plan(st.session_state.user_id)
if not st.session_state.user_plan:
# Create user if doesn't exist
st.session_state.subscription_manager.create_user(
st.session_state.user_id,
email=st.session_state.get('user_email')
)
st.session_state.user_plan = st.session_state.subscription_manager.get_user_plan(st.session_state.user_id)
# Show user dashboard in sidebar
show_user_dashboard()
# ===============================
# HEADER SECTION
# ===============================
st.markdown("""
<div class="header-container">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem;">
<div style="font-size: 2rem;">🎓</div>
<div>
<h1 class="assamese-title">
নমস্কাৰ! মই আপোনাৰ দশম শ্ৰেণীৰ AI শিক্ষক
</h1>
<p class="assamese-text">
<span class="assamese-highlight">SEBAৰ সকলো বিষয় মই জানো</span> – গণিত, বিজ্ঞান, সমাজ বিজ্ঞান, ইংৰাজী, অসমীয়া, হিন্দী ইত্যাদি।
</p>
</div>
</div>
</div>
""", unsafe_allow_html=True)
# ===============================
# USAGE BANNER
# ===============================
if st.session_state.get('logged_in') and st.session_state.get('user_plan'):
user_plan = st.session_state.user_plan
remaining = user_plan['questions_remaining']
plan_name = user_plan['plan_type'].replace('_', ' ').title()
# Get today's usage
today_used = 0
if st.session_state.subscription_manager:
stats = st.session_state.subscription_manager.get_user_stats(st.session_state.user_id)
today_used = stats['today_used']
# Determine banner color based on usage
if remaining <= 5 or today_used >= user_plan['daily_limit']:
# Warning banner
banner_color = "linear-gradient(135deg, #FF5722 0%, #FF9800 100%)"
border_color = "#D32F2F"
elif user_plan['plan_type'] == 'free':
# Info banner for free users
banner_color = "linear-gradient(135deg, #2196F3 0%, #1976D2 100%)"
border_color = "#0D47A1"
else:
# Success banner for paid users
banner_color = "linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%)"
border_color = "#1B5E20"
st.markdown(f"""
<div style="background: {banner_color};
color: white;
padding: 0.75rem 1rem;
border-radius: 10px;
margin: 0.5rem 0 1.5rem 0;
border-left: 5px solid {border_color};
box-shadow: 0 3px 10px rgba(0,0,0,0.1);">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<strong style="font-size: 1rem;">📊 {plan_name} Plan</strong>
<div style="font-size: 0.85rem; opacity: 0.9;">
Today: {today_used}/{user_plan['daily_limit']} • Remaining: {remaining}/{user_plan['total_questions']}
</div>
</div>
<div>
<span style="background: rgba(255,255,255,0.2);
padding: 0.3rem 0.8rem;
border-radius: 5px;
font-size: 0.85rem;
cursor: pointer;"
onclick="window.location.href='?show_plans=true'">
{ 'Upgrade' if user_plan['plan_type'] == 'free' else 'Manage' }
</span>
</div>
</div>
</div>
""", unsafe_allow_html=True)
# ===============================
# CONTROL PANEL SECTION
# ===============================
st.markdown('<div class="control-panel">', unsafe_allow_html=True)
control_col1, control_col2 = st.columns(2)
with control_col1:
st.markdown("#### 📚 বিষয় বাছনি কৰক")
subject_list = list(SEBA_CURRICULUM.keys())
current_subject = st.session_state.current_subject
current_index = subject_list.index(current_subject) if current_subject in subject_list else 0
selected_subject = st.selectbox(
"আপুনি কোনটো বিষয় শিকিব বিচাৰে?",
subject_list,
index=current_index,
key="subject_selector",
label_visibility="collapsed"
)
if selected_subject != st.session_state.current_subject:
st.session_state.current_subject = selected_subject
chapters = SEBA_CURRICULUM[selected_subject]
st.session_state.current_chapter = list(chapters.keys())[0]
with control_col2:
st.markdown("#### 📖 অধ্যায় বাছনি কৰক")
chapters = SEBA_CURRICULUM[selected_subject]
chapter_options = []
chapter_display_map = {}
for chap_num, chap_name in chapters.items():
display_text = f"{chap_num}: {chap_name}"
chapter_options.append(display_text)
chapter_display_map[display_text] = chap_num
current_chapter = st.session_state.current_chapter
current_chap_display = next((disp for disp, num in chapter_display_map.items() if num == current_chapter), chapter_options[0])
current_chap_index = chapter_options.index(current_chap_display) if current_chap_display in chapter_options else 0
selected_chapter_display = st.selectbox(
"কোন অধ্যায়ৰ পৰা প্ৰশ্ন সুধিব?",
chapter_options,
index=current_chap_index,
key="chapter_selector",
label_visibility="collapsed"
)
selected_chapter_key = chapter_display_map[selected_chapter_display]
if selected_chapter_key != st.session_state.current_chapter:
st.session_state.current_chapter = selected_chapter_key
st.markdown('</div>', unsafe_allow_html=True)
# ===============================
# CURRENT SELECTION INFO
# ===============================
current_chapter_name = chapters[selected_chapter_key]
st.info(f"""
**📚 বৰ্তমানৰ বিষয়:** {selected_subject}
**📖 বৰ্তমানৰ অধ্যায়:** {current_chapter_name}
""")
# ===============================
# SAMPLE QUESTIONS SECTION
# ===============================
SAMPLE_QUESTIONS = {
"📐 গণিত (Mathematics)": {
"অধ্যায় ১": [
"ইউক্লিডৰ বিভাজন প্ৰমেয়ি (Euclid's Division Lemma) কি? উদাহৰণসহ বুজাই দিয়ক।",
"অনুৰূপ আৰু মৌলিক সংখ্যাৰ পাৰ্থক্য লিখক। 17 আৰু 23 কি মৌলিক সংখ্যা?",
"দুটা ধনাত্মক সংখ্যাৰ গ.সা.উ. 24 আৰু ল.সা.গু. 96। সংখ্যাদুটা উলিয়াওক।",
"প্ৰমাণ কৰক যে √2 এটা অপৰিমেয় সংখ্যা।",
"15, 18, আৰু 24 ৰ গ.সা.উ. আৰু ল.সা.গু. নিৰ্ণয় কৰক।"
],
"অধ্যায় ২": [
"বহুপদৰ শূন্যৰ ধাৰণাটো বুজাই দিয়ক। বহুপদ p(x) = x² - 4x + 3 ৰ শূন্যবোৰ উলিয়াওক।",
"এটা দ্বিঘাত বহুপদ উলিয়াওক যাৰ শূন্যবোৰ 2 আৰু -3।",
"বহুপদৰ শূন্য আৰু গুণাংকৰ সম্পৰ্ক ব্যাখ্যা কৰক।",
"বহুপদ x³ - 3x² - x + 3 ৰ শূন্যবোৰ উলিয়াওক।",
"এটা দ্বিঘাত বহুপদ উলিয়াওক যাৰ শূন্যবোৰৰ যোগফল 4 আৰু গুণফল 3।"
],
"অধ্যায় ৩": [
"দ্বিঘাত সমীকৰণ x² - 5x + 6 = 0 ৰ মূল নিৰ্ণয় কৰক।",
"দ্বিঘাত সূত্ৰ ব্যৱহাৰ কৰি 2x² + 5x + 3 = 0 সমীকৰণটো সমাধান কৰক।",
"দুটা সংখ্যা উলিয়াওক যাৰ যোগফল 27 আৰু গুণফল 182।",
"দ্বিঘাত সমীকৰণৰ বিচৰ্ষক কাক বোলে? x² - 4x + 4 = 0 ৰ বিচৰ্ষক নিৰ্ণয় কৰক।",
"এটা আয়তাকাৰ পথাৰৰ দীঘ ইয়াৰ প্ৰস্থতকৈ 5 মিটাৰ বেছি। কালি 150 বৰ্গমিটাৰ হ'লে দীঘ-প্ৰস্থ উলিয়াওক।"
],
"অধ্যায় ৪": [
"এটা সমান্তৰ শ্ৰেণীৰ প্ৰথম পদ 5 আৰু সাধাৰণ অন্তৰ 3। দশম পদটো উলিয়াওক।",
"সমান্তৰ শ্ৰেণী 10, 7, 4, ... -62 ৰ শেষৰ পৰা 11 সংখ্যক পদ উলিয়াওক।",
"সমান্তৰ শ্ৰেণীৰ n সংখ্যক পদৰ যোগফলৰ সূত্ৰটো লিখক।",
"এটা সমান্তৰ শ্ৰেণীৰ প্ৰথম n পদৰ যোগফল Sn = 3n² + 5n। সাধাৰণ অন্তৰ উলিয়াওক।",
"100 ৰ পৰা 200 লৈ 6 ৰে বিভাজ্য সংখ্যাবোৰৰ যোগফল উলিয়াওক।"
],
"অধ্যায় ৫": [
"থেলছৰ উপপাদ্যটো লিখি প্ৰমাণ কৰক।",
"সমকোণী ত্ৰিভুজ ABC ত A সমকোণ। AD ⟂ BC। প্ৰমাণ কৰক যে AB² = BD × BC।",
"দুটা সদৃশ ত্ৰিভুজৰ কালিৰ অনুপাত ত্ৰিভুজদুটাৰ অনুৰূপ বাহুৰ অনুপাতৰ বৰ্গৰ সমান - প্ৰমাণ কৰক।",
"ত্ৰিভুজৰ মধ্যমা ত্ৰিভুজটো সমান কালিৰ দুটা ত্ৰিভুজত বিভক্ত কৰে - প্ৰমাণ কৰক।",
"পাইথাগোৰাছৰ উপপাদ্যটো প্ৰমাণ কৰক।"
],
"অধ্যায় ৬": [
"sin²θ + cos²θ = 1 ৰ প্ৰমাণ দিয়ক।",
"ত্রিকোণমিতিক সূত্র sin(A+B) = sinA cosB + cosA sinB প্ৰমাণ কৰক।",
"মান নির্ণয় কৰক: sin30° + cos60° - tan45°",
"যদি sinθ = 3/5 হয়, তেন্তে cosθ আৰু tanθ ৰ মান উলিয়াওক।",
"প্ৰমাণ কৰক: (1 + tan²θ) = sec²θ"
],
"অধ্যায় ৭": [
"বৃত্তৰ জ্যাই কেন্দ্ৰত উৎপন্ন কৰা কোণবোৰৰ সম্পৰ্ক কি?",
"বৃত্তৰ এটা বিন্দুত স্পৰ্শক আৰু ব্যাসাৰ্ধৰ মাজৰ কোণ 90° হয় - প্ৰমাণ কৰক।",
"বৃত্তচাপে কেন্দ্ৰত উৎপন্ন কৰা কোণ পৰিধিত উৎপন্ন কৰা কোণৰ দুগুণ হয় - প্ৰমাণ কৰক।",
"দুটা বৃত্ত বাহিৰৰ পৰা স্পৰ্শ কৰিলে স্পৰ্শবিন্দুৰ মাজেৰে যোৱা ৰেখাডাল কেন্দ্ৰদ্বয়ৰ সংযোগী ৰেখাক ছেদ কৰে - প্ৰমাণ কৰক।",
"বৃত্তৰ ক্ষেত্ৰত বৰ্তুলীয় স্তম্ভৰ উপপাদ্য বুজাই দিয়ক।"
],
"অধ্যায় ৮": [
"দুটা বিন্দু (2,3) আৰু (5,7) ৰ মাজৰ দূৰত্ব নিৰ্ণয় কৰক।",
"বিন্দু (4,5), (7,6) আৰু (4,3) ৰ পৰা সমদূৰৱৰ্তী বিন্দুটোৰ স্থানাংক উলিয়াওক।",
"ভাগ সূত্ৰ ব্যৱহাৰ কৰি বিন্দু (-2,3) আৰু (4,1) ৰ সংযোগী ৰেখাখণ্ডক 3:1 অনুপাতত বিভক্ত কৰা বিন্দুটোৰ স্থানাংক উলিয়াওক।",
"তিনিটা বিন্দু (1,2), (3,4) আৰু (5,6) একে ৰেখাত আছে নে নাই পৰীক্ষা কৰক।",
"ত্ৰিভুজৰ মাধ্যমাৰ ছেদবিন্দুৰ স্থানাংকৰ সূত্ৰটো লিখক।"
],
"অধ্যায় ৯": [
"এটা চুঙাৰ বক্ৰপৃষ্ঠৰ কালি আৰু আয়তনৰ সূত্ৰ লিখক।",
"এটা শংকুৰ ঢালু উচ্চতা 13 ছে.মি. আৰু ভূমিৰ ব্যাসাৰ্ধ 5 ছে.মি.। ইয়াৰ মুঠ পৃষ্ঠকালি উলিয়াওক।",
"এটা গোলকৰ আয়তন 4851 ঘন ছে.মি.। ইয়াৰ ব্যাসাৰ্ধ উলিয়াওক।",
"এটা আয়তক্ষেত্ৰৰ দীঘ 16 মি. আৰু প্ৰস্থ 10 মি.। ইয়াৰ কৰ্ণৰ দৈৰ্ঘ্য উলিয়াওক।",
"এটা বৰ্গক্ষেত্ৰৰ কৰ্ণৰ দৈৰ্ঘ্য 10√2 ছে.মি.। ইয়াৰ বাহুৰ দৈৰ্ঘ্য উলিয়াওক।"
],
"অধ্যায় ১০": [
"পৰিসংখ্যাৰ মাধ্যম আৰু মধ্যমাৰ পাৰ্থক্য লিখক।",
"তলৰ তথ্যৰ পৰা মধ্যমা উলিয়াওক: 12, 15, 18, 20, 25, 30, 32",
"শ্ৰেণী-বিন্যাসিত তথ্যৰ পৰা বহুলক উলিয়াওকৰ সূত্ৰটো লিখক।",
"এটা বিভাজনৰ শ্ৰেণী মধ্যবিন্দু 25 আৰু শ্ৰেণী দৈৰ্ঘ্য 10। শ্ৰেণী সীমা উলিয়াওক।",
"পৰিসংখ্যাৰ চিত্ৰৰ প্ৰয়োজনীয়তা লিখক।"
],
"অধ্যায় ১১": [
"সম্ভাৱিতা নিৰ্ণয়ৰ মৌলিক সূত্ৰটো লিখক।",
"এটা মুদ্ৰা দুবাৰ টছ কৰোতে দুয়োবাৰ হেড পোৱাৰ সম্ভাৱিতা কিমান?",
"52খন তাছপাতৰ পৰা এখন ৰাণী পোৱাৰ সম্ভাৱিতা কিমান?",
"এটা ডাইচ দলিয়ালে জোৰ সংখ্যা পোৱাৰ সম্ভাৱিতা কিমান?",
"সম্ভাৱিতা আৰু অনুমানৰ মাজৰ পাৰ্থক্য লিখক।"
]
},
"🔬 বিজ্ঞান (Science)": {
"অধ্যায় ১": [
"ৰাসায়নিক বিক্ৰয়া আৰু ৰাসায়নিক সমীকৰণৰ মাজৰ পাৰ্থক্য কি?",
"মেগনেছিয়ামৰ ফিটা পোৰাৰ ৰাসায়নিক সমীকৰণ লিখক।",
"দহন বিক্ৰয়া কাক বোলে? উদাহৰণ দিয়ক।",
"বিয়োজন বিক্ৰয়া কি? উদাহৰণসহ বুজাই দিয়ক।",
"ৰাসায়নিক সমীকৰণ সন্তুলিত কৰা পদ্ধতি দুটাৰ নাম লিখক।"
],
"অধ্যায় ২": [
"এছিড আৰু ক্ষাৰকৰ মাজৰ প্ৰধান পাৰ্থক্যবোৰ উল্লেখ কৰক।",
"ফেনলফথেলিনৰ সৈতে এছিড আৰু ক্ষাৰকৰ বিক্ৰয়া কেনে হয়?",
"পাকস্থলীত গেছ্ট্ৰিক এছিডৰ পৰিমাণ বাঢ়িলে কি কৰিব লাগে?",
"কপাৰ চালফেটৰ সৈতে জিংকৰ বিক্ৰয়া দেখুৱাই ৰাসায়নিক সমীকৰণ লিখক।",
"pH স্কেল কি? ইয়াৰ গুৰুত্ব লিখক।"
],
"অধ্যায় ৩": [
"ধাতু আৰু অধাতুৰ মাজৰ প্ৰধান পাৰ্থক্যবোৰ উল্লেখ কৰক।",
"ধাতুবোৰ বিদ্যুৎৰ সুপৰিবাহী কিয়?",
"ধাতুৰ মলিয়ন কাক বোলে? ইয়াক কেনেকৈ প্ৰতিৰোধ কৰিব পাৰি?",
"অধাতুৰ প্ৰধান ধৰম্বোৰ লিখক।",
"লোৰ ওপৰত জিংকৰ প্ৰলেপ দিয়া প্ৰক্ৰিয়াটো ব্যাখ্যা কৰক।"
],
"অধ্যায় ৪": [
"কাৰ্বনৰ যোজ্য়তা 4 হয় কিয়?",
"সমসংযোজী বন্ধন কাক বোলে? উদাহৰণ দিয়ক।",
"হাইড্ৰ'কাৰ্বন কাক বোলে? ইয়াৰ দুটা উদাহৰণ দিয়ক।",
"সমাবয়ৱী পদাৰ্থ কাক বোলে? উদাহৰণসহ বুজাই দিয়ক।",
"এলকাইন আৰু এলকিনৰ মাজৰ পাৰ্থক্য লিখক।"
],
"অধ্যায় ৫": [
"মেন্ডেলিফৰ পৰ্যাবৃত্ত সূত্ৰটো লিখক।",
"পৰ্যাবৃত্ত সূত্ৰৰ গুৰুত্ব লিখক।",
"পৰ্যাবৃত্ত তালিকাত আধুনিক দীঘল ৰূপটো ব্যাখ্যা কৰক।",
"পৰ্যাবৃত্ত তালিকাত পৰ্যায় আৰু শ্ৰেণীৰ ধাৰণা বুজাই দিয়ক।",
"মৌলৰ যোজ্য়তা পৰ্যাবৃত্ত তালিকাত কিদৰে সলনি হয়?"
],
"অধ্যায় ৬": [
"মানুহৰ হৃদযন্ত্ৰৰ কাৰ্য প্ৰণালী বৰ্ণনা কৰক।",
"উচ্চককী আৰু নিম্নককী উদ্ভিদৰ মাজৰ পাৰ্থক্য লিখক।",
"মানুহৰ ৰেচন প্ৰণালী বৰ্ণনা কৰক।",
"মানুহৰ শ্বাস-প্ৰশ্বাস প্ৰণালীৰ কাৰ্য ব্যাখ্যা কৰক।",
"মানুহৰ পাচন প্ৰণালীৰ বিভিন্ন অংশবোৰৰ নাম লিখক।"
],
"অধ্যায় ৭": [
"নিয়ন্ত্ৰণ আৰু সমন্বয় কাক বোলে?",
"মানুহৰ মস্তিষ্কৰ তিনিটা অংশৰ নাম লিখি প্ৰত্যেকৰ কাৰ্য বৰ্ণনা কৰক।",
"প্ৰতিবৰ্তী ক্ৰিয়া কাক বোলে? উদাহৰণ দিয়ক।",
"হৰম'ন কাক বোলে? ইয়াৰ গুৰুত্ব লিখক।",
"মানুহৰ স্নায়ু প্ৰণালীৰ গঠন বৰ্ণনা কৰক।"
],
"অধ্যায় ৮": [
"অলৈঙ্গিক প্ৰজননৰ পদ্ধতিবোৰ উল্লেখ কৰক।",
"ক্ৰমবিকাশ কাক বোলে? ইয়াৰ গুৰুত্ব লিখক।",
"স্ত্ৰী আৰু পুৰুষ জননাংগৰ মাজৰ পাৰ্থক্য লিখক।",
"লিংগিক প্ৰজননৰ সুবিধাবোৰ লিখক।",
"ভ্রূণ কাক বোলে? ইয়াৰ বিকাশৰ স্তৰবোৰ বৰ্ণনা কৰক।"
],
"অধ্যায় ৯": [
"ডি.এন.এ.ৰ গঠন বৰ্ণনা কৰক।",
"বংশগতি আৰু ক্ৰমবিকাশৰ মাজৰ পাৰ্থক্য লিখক।",
"মেণ্ডেলৰ নিয়মবোৰ ব্যাখ্যা কৰক।",
"লিংগ নিৰ্ণয় কিহে কৰে? ব্যাখ্যা কৰক।",
"মিউটেশ্যন কাক বোলে? ইয়াৰ কাৰণবোৰ লিখক।"
],
"অধ্যায় ১০": [
"প্ৰতিফলন আৰু প্ৰতিসৰণৰ মাজৰ পাৰ্থক্য লিখক।",
"লেন্ছৰ ক্ষমতাৰ সূত্ৰটো লিখক।",
"সূৰ্য্যৰ পোহৰ বগা কিয়?",
"দাপোণৰ সূত্ৰ 1/f = 1/u + 1/v প্ৰমাণ কৰক।",
"আলোকৰ বিচ্ছুৰণ কাক বোলে? উদাহৰণ দিয়ক।"
],
"অধ্যায় ১১": [
"মানুহৰ চকুৰ গঠন বৰ্ণনা কৰক।",
"নিকট দৃষ্টি আৰু দূৰদৃষ্টিৰ পাৰ্থক্য লিখক।",
"কেমেৰা আৰু চকুৰ মাজৰ সাদৃশ্য লিখক।",
"ৰামধেনু কেনেকৈ সৃষ্টি হয়?",
"মায়'পিয়া আৰু হাইপাৰমেট্ৰ'পিয়া ৰোগ কেনেকৈ শুধৰোৱা হয়?"
],
"অধ্যায় ১২": [
"ওহমৰ সূত্ৰটো লিখি ব্যাখ্যা কৰক।",
"বিদ্যুৎ প্রবাহ আৰু বিভৱ ভেদৰ মাজৰ সম্পৰ্ক লিখক।",
"বিদ্যুৎ চুলাৰ কেনেকৈ কাম কৰে?",
"বৈদ্যুতিক বাল্বৰ ভিতৰত কেনে ধৰণৰ তাঁৰ ব্যৱহাৰ কৰা হয় আৰু কিয়?",
"বৈদ্যুতিক শক্তি আৰু ক্ষমতাৰ মাজৰ পাৰ্থক্য লিখক।"
],
"অধ্যায় ১৩": [
"বিদ্যুৎ-চুম্বকীয় প্ৰভাৱ কি?",
"বিদ্যুৎচুম্বকৰ গঠন আৰু কাৰ্য প্ৰণালী বৰ্ণনা কৰক।",
"ফেৰাডেৰ ইলেক্ট্ৰ'মেগনেটিক ইণ্ডাকচনৰ নিয়ম লিখক।",
"মটৰ আৰু জেনেৰেটৰৰ মাজৰ পাৰ্থক্য লিখক।",
"ট্ৰান্সফৰ্মাৰ কিয় ব্যৱহাৰ কৰা হয়?"
],
"অধ্যায় ১৪": [
"নৱীকৰণযোগ্য শক্তিৰ উৎসবোৰৰ নাম লিখক।",
"সৌৰশক্তিৰ সুবিধা আৰু অসুবিধাবোৰ লিখক।",
"জৈৱ ভৰ কাক বোলে? ইয়াৰ গুৰুত্ব লিখক।",
"ভূ-তাপীয় শক্তিৰ উৎস লিখক।",
"নিউক্লীয় বিভাজন আৰু নিউক্লীয় সংযোজনৰ মাজৰ পাৰ্থক্য লিখক।"
],
"অধ্যায় ১৫": [
"পৰিৱেশ দূষণৰ কাৰণবোৰ উল্লেখ কৰক।",
"এছিড বৰষুণ কিয় হয়? ইয়াৰ প্ৰভাৱ লিখক।",
"ওজন স্তৰৰ ক্ষতিৰ কাৰণবোৰ লিখক।",
"জৈৱবৈচিত্ৰ্যৰ গুৰুত্ব লিখক।",
"হৰিত গৃহ প্ৰভাৱ কি? ইয়াৰ পৰিণতি লিখক।"
],
"অধ্যায় ১৬": [
"প্ৰাকৃতিক সম্পদ সংৰক্ষণৰ উপায়বোৰ লিখক।",
"বৰ্ষাৰণ্য সংৰক্ষণৰ গুৰুত্ব লিখক।",
"জলসম্পদৰ ব্যৱস্থাপনা কেনেকৈ কৰিব লাগে?",
"মৃত্তিকা সংৰক্ষণৰ পদ্ধতিবোৰ লিখক।",
"বায়ু দূষণ ৰোধ কৰাৰ উপায়বোৰ লিখক।"
]
},
"🌍 সমাজ বিজ্ঞান (Social Science)": {
"অধ্যায় ১": [
"ইউৰোপত ৰাষ্ট্ৰবাদৰ উত্থানৰ প্ৰধান কাৰকবোৰ কি আছিল?",
"ইটালীৰ ঐক্যবাদত গেৰিবাল্ডিৰ ভূমিকা আলোচনা কৰক।",
"বিসমাৰ্কৰ ৰক্ত আৰু লোহাৰ নীতি ব্যাখ্যা কৰক。",
"জাৰ্মানীৰ ঐক্যবাদ কেনেকৈ সম্পন্ন হৈছিল?",
"ৰাষ্ট্ৰবাদৰ উত্থানে ইউৰোপত কেনে প্ৰভাৱ পেলাইছিল?"
],
"অধ্যায় ২": [
"ভাৰতীয় জাতীয়তাবাদৰ উত্থানত মহাত্মা গান্ধীৰ অৱদান আলোচনা কৰক।",
"ভাৰতীয় জাতীয় কংগ্ৰেছৰ প্ৰতিষ্ঠা আৰু ইয়াৰ প্ৰাথমিক লক্ষ্যবোৰ লিখক।",
"বংগ বিভাজনৰ কাৰণ আৰু প্ৰভাৱ আলোচনা কৰক。",
"স্বদেশী আন্দোলন কি আছিল? ইয়াৰ গুৰুত্ব লিখক।",
"জালিয়ানৱালাবাগ হত্যাকাণ্ডৰ ঘটনাটো বৰ্ণনা কৰক。"
],
"অধ্যায় ৩": [
"ভূগোলৰ প্ৰাকৃতিক আৰু মানৱ সম্পদৰ পাৰ্থক্য দৰ্শোৱা।",
"অসমৰ প্ৰাকৃতিক সম্পদবোৰৰ নাম লিখক।",
"ভাৰতৰ কৃষিজ সম্পদবোৰৰ নাম লিখক。",
"খনিজ সম্পদৰ গুৰুত্ব লিখক。",
"বনজ সম্পদ সংৰক্ষণৰ গুৰুত্ব লিখক。"
],
"অধ্যায় ৪": [
"অৰ্থনৈতিক উন্নয়ন আৰু অৰ্থনৈতিক বৃদ্ধিৰ মাজৰ পাৰ্থক্য লিখক।",
"ভাৰতৰ অৰ্থনৈতিক উন্নয়নত কৃষিৰ ভূমিকা আলোচনা কৰক。",
"শিল্পায়নৰ সুবিধা আৰু অসুবিধাবোৰ লিখক。",
"বেকাৰ সমস্যা সমাধানৰ উপায়বোৰ লিখক।",
"দৰিদ্ৰতা নিৰ্মূল কৰাৰ উপায়বোৰ আলোচনা কৰক。"
],
"অধ্যায় ৫": [
"অসমৰ লোক সংস্কৃতিৰ বৈশিষ্ট্যসমূহ বৰ্ণনা কৰক。",
"বিহুৰ বিভিন্ন ৰূপবোৰৰ বৰ্ণনা দিয়ক।",
"অসমীয়া লোক সংগীতৰ বৈশিষ্ট্য লিখক।",
"অসমৰ লোক নৃত্যৰ নাম লিখি বৰ্ণনা কৰক।",
"অসমৰ সাজ-পোচাকৰ বৈচিত্ৰ্য বৰ্ণনা কৰক。"
],
"অধ্যায় ৬": [
"ভাৰতৰ প্ৰধান উদ্যোগবোৰৰ নাম লিখক。",
"লো আৰু ইস্পাত উদ্যোগৰ গুৰুত্ব লিখক。",
"কপাহী বস্ত্ৰ উদ্যোগৰ সমস্যাসমূহ আলোচনা কৰক。",
"ছুগাৰ মিল উদ্যোগৰ স্থানীয়কৰণৰ কাৰণবোৰ লিখক।",
"উদ্যোগিক দূষণ ৰোধ কৰাৰ উপায়বোৰ লিখক。"
],
"অধ্যায় ৭": [
"ভাৰতীয় অৰ্থনীতিৰ প্ৰধান সমস্যাসমূহ আলোচনা কৰক。",
"মুদ্ৰাস্ফীতিৰ কাৰণ আৰু প্ৰভাৱ লিখক。",
"বিত্তীয় ঘাটিৰ অৰ্থ লিখক。",
"ৰপ্তানি আৰু আমদানিৰ মাজৰ পাৰ্থক্য লিখক।",
"অৰ্থনৈতিক আয়োজন কেনেকৈ কৰা হয়?"
],
"অধ্যায় ৮": [
"ভাৰতৰ ৰাজনৈতিক দলসমূহৰ শ্ৰেণীবিভাজন কৰক।",
"ৰাষ্ট্ৰীয় দল আৰু ৰাজ্যিক দলৰ মাজৰ পাৰ্থক্য লিখক。",
"ভাৰতত বহুদলীয় গণতন্ত্ৰৰ গুৰুত্ব লিখক。",
"ৰাজনৈতিক দলৰ কাৰ্যবোৰ লিখক。",
"নিৰ্বাচন আয়োগৰ কাৰ্যবোৰ লিখক।"
],
"অধ্যায় ৯": [
"ভাৰতৰ সংবিধানত ক্ষমতাৰ বিভাজন কেনেদৰে কৰা হৈছে?",
"কাৰ্যপালিকা, বিধানমণ্ডল আৰু ন্যায়পালিকাৰ মাজৰ সম্পৰ্ক লিখক。",
"কেন্দ্ৰ আৰু ৰাজ্য চৰকাৰৰ মাজৰ সম্পৰ্ক লিখক。",
"স্থানীয় স্বায়ত্তশাসনৰ গুৰুত্ব লিখক।",
"পঞ্চায়েতী ৰাজ ব্যৱস্থাৰ গঠন বৰ্ণনা কৰক。"
],
"অধ্যায় ১০": [
"জনসম্পদ উন্নয়নৰ অৰ্থ লিখক。",
"শিক্ষাৰ গুৰুত্ব লিখক。",
"স্বাস্থ্য সেৱাৰ উন্নয়নৰ উপায়বোৰ লিখক।",
"জনসংখ্যা বিস্ফোৰণৰ কাৰণবোৰ লিখক।",
"লিংগ সমতাৰ গুৰুত্ব লিখক।"
]
},
"📖 ইংৰাজী (English)": {
"পাঠ ১": [
"What is the central theme of 'A Letter to God'?",
"Describe the character of Lencho in the story.",
"Why did Lencho write a letter to God?",
"What does the story teach us about faith and human nature?",
"How did the postmaster react to Lencho's letter?"
],
"পাঠ ২": [
"Describe the qualities of Nelson Mandela that made him a great leader.",
"What is the significance of the title 'Long Walk to Freedom'?",
"What were Mandela's views on love and hate?",
"Describe the inauguration ceremony at the Union Buildings.",
"What does Mandela say about courage?"
],
"পাঠ ৩": [
"What is the moral lesson of 'Two Stories about Flying'?",
"Compare and contrast the two stories in this lesson.",
"Describe the young seagull's first flight.",
"What motivated the young seagull to finally fly?",
"How does the second story about the pilot differ from the first?"
],
"পাঠ ৪": [
"How does Anne Frank's diary reflect the struggles of Jewish people during WWII?",
"What kind of person was Anne Frank? Describe her character.",
"Why is Anne's diary considered an important historical document?",
"What were Anne's dreams and aspirations?",
"How did Anne view her captivity in the Secret Annex?"
],
"পাঠ ৫": [
"What is the significance of the hundred dresses in the story?",
"Describe the character of Wanda Petronski.",
"Why did the other girls make fun of Wanda?",
"What lesson did Maddie learn from the incident?",
"How does the story address the theme of bullying?"
],
"পাঠ ৬": [
"How does Maddie's character develop in 'The Hundred Dresses II'?",
"What did the girls discover about Wanda after she left?",
"Why did Maddie feel guilty about her behavior?",
"What was Wanda's letter about?",
"How did the story end?"
],
"পাঠ ৭": [
"Describe the cultural diversity of India as shown in 'Glimpses of India'.",
"What are the main features of Coorg as described in the text?",
"How is tea cultivation described in the lesson?",
"What makes Goa different from other parts of India?",
"What are the various glimpses of India presented in this lesson?"
],
"পাঠ ৮": [
"What is the relationship between the narrator and Mijbil in 'Mijbil the Otter'?",
"Describe Mijbil's habits and characteristics.",
"How did the otter adjust to his new environment?",
"What adventures did the narrator have with Mijbil?",
"What does the story tell us about human-animal relationships?"
],
"পাঠ ৯": [
"What does Valli learn from her bus journey in 'Madam Rides the Bus'?",
"Describe Valli's character and her curiosity.",
"What were Valli's preparations for her bus journey?",
"What did Valli see during her journey?",
"How did the journey change Valli?"
],
"পাঠ ১০": [
"What is the main teaching of Buddha in 'The Sermon at Benares'?",
"How did Kisa Gotami realize the truth about death?",
"What does Buddha say about grief and suffering?",
"Why is death compared to ripe fruits?",
"What is the significance of the mustard seed in the story?"
],
"পাঠ ১১": [
"Describe the humorous elements in 'The Proposal'.",
"What is the main conflict in the play?",
"Describe the characters of Lomov, Natalya, and Chubukov.",
"What are they arguing about in the play?",
"How does the play end?"
]
},
"📜 অসমীয়া (Assamese)": {
"পাঠ ১": [
"বৰগীতৰ সাহিত্যিক মূল্য আলোচনা কৰক।",
"শংকৰদেৱে ৰচনা কৰা বৰগীতৰ বিষয়বস্তু কি?",
"বৰগীতৰ ভাষা শৈলীৰ বৈশিষ্ট্য লিখক।",
"বৰগীতত প্ৰকাশ পোৱা ভক্তিধর্মীয় ভাৱ লিখক।",
"বৰগীতৰ ৰচনা ৰীতি ব্যাখ্যা কৰক।"
],
"পাঠ ২": [
"জীৱন-সঙ্গীত কবিতাটোৰ মূল বক্তব্য ব্যাখ্যা কৰক।",
"জীৱন-সঙ্গীত কবিতাটোত কবিয়ে জীৱনক কেনেদৰে চিত্ৰিত কৰিছে?",
"কবিতাটোৰ ছন্দ আৰু অলংকাৰৰ বৈশিষ্ট্য লিখক।",
"কবিতাটোত প্ৰকাশ পোৱা দাৰ্শনিক চিন্তা আলোচনা কৰক।",
"জীৱন-সঙ্গীত কবিতাটোৰ শিৰোনামৰ সাৰ্থকতা লিখক।"
],
"পাঠ ৩": [
"প্ৰশস্তি কবিতাটোত কবিয়ে কি বৰ্ণনা কৰিছে?",
"প্ৰশস্তি কবিতাটোৰ ৰচনা শৈলীৰ বৈশিষ্ট্য লিখক।",
"কবিতাটোত ব্যৱহৃত উপমা আৰু ৰূপকবোৰ উল্লেখ কৰক।",
"প্ৰশস্তি কবিতাটোৰ ভাষাৰ সৌন্দৰ্য্য বৰ্ণনা কৰক।",
"কবিতাটোৰ প্ৰাসঙ্গিকতা বৰ্তমান সময়ত আলোচনা কৰক।"
],
"পাঠ ৪": [
"মোৰ মৰমি জনমভূমি কবিতাটোৰ বিষয়বস্তু আলোচনা কৰক।",
"কবিতাটোত কবিয়ে মাতৃভূমিৰ প্ৰতি থকা মৰম কেনেদৰে প্ৰকাশ কৰিছে?",
"মোৰ মৰমি জনমভূমি কবিতাটোৰ শৈলীগত বৈশিষ্ট্য লিখক।",
"কবিতাটোত প্ৰকাশ পোৱা দেশপ্ৰেমৰ ভাৱ লিখক।",
"কবিতাটোৰ শিৰোনামৰ সাৰ্থকতা লিখক।"
],
"পাঠ ৫": [
"অসমীয়া ভাষাৰ উন্নতিৰ বাবে কি কৰিব লাগে?",
"অসমীয়া ভাষাৰ বৰ্তমান অৱস্থা আলোচনা কৰক।",
"ভাষা সংৰক্ষণৰ গুৰুত্ব লিখক।",
"অসমীয়া ভাষাৰ উন্নতিত শিক্ষাৰ ভূমিকা লিখক।",
"ভাষা বিকাশৰ বাবে আধুনিক প্ৰযুক্তিৰ ভূমিকা আলোচনা কৰক।"
],
"পাঠ ৬": [
"অসমৰ লোক-সংস্কৃতিৰ বৈশিষ্ট্যসমূহ বৰ্ণনা কৰক।",
"অসমৰ লোক-সংগীতৰ প্ৰকাৰবোৰৰ নাম লিখক।",
"অসমৰ লোক-নৃত্যৰ বৈচিত্ৰ্য বৰ্ণনা কৰক।",
"অসমীয়া লোক-কথাৰ বৈশিষ্ট্য লিখক।",
"লোক-সংস্কৃতি সংৰক্ষণৰ গুৰুত্ব লিখক।"
],
"পাঠ ৭": [
"আমাৰ ঋতু কবিতাটোত কবিয়ে ঋতুচক্ৰ কেনেদৰে বৰ্ণনা কৰিছে?",
"অসমৰ ছয়টা ঋতুৰ নাম লিখি প্ৰত্যেকৰ বৈশিষ্ট্য বৰ্ণনা কৰক।",
"ঋতুভিত্তিক কৃষিকৰ্মৰ সম্পৰ্ক লিখক।",
"ঋতু পৰিৱৰ্তনে মানুহৰ জীৱনত কেনে প্ৰভাৱ পেলায়?",
"কবিতাটোত ব্যৱহৃত প্ৰাকৃতিক দৃশ্যবোৰ বৰ্ণনা কৰক।"
],
"পাঠ ৮": [
"বহাগ বিহুৰ সামাজিক আৰু সাংস্কৃতিক গুৰুত্ব লিখক।",
"বহাগ বিহু উদযাপনৰ পৰম্পৰাগত ৰীতি-নীতিবোৰ বৰ্ণনা কৰক।",
"বিহু গীতৰ বিষয়বস্তু আৰু বৈশিষ্ট্য লিখক।",
"বিহু নৃত্যৰ বিভিন্ন ৰূপবোৰৰ বৰ্ণনা দিয়ক।",
"বিহুৰ ঐতিহ্য সংৰক্ষণৰ গুৰুত্ব লিখক।"
],
"পাঠ ৯": [
"মহাপুৰুষীয়া ধৰ্মৰ মূল নীতিবোৰ কি?",
"শংকৰদেৱ আৰু মাধৱদেৱৰ ধৰ্মীয় অৱদান আলোচনা কৰক।",
"মহাপুৰুষীয়া ধৰ্মত নাম-ধৰ্মৰ গুৰুত্ব লিখক।",
"একশৰণ ধৰ্মৰ মূল তত্ত্ববোৰ ব্যাখ্যা কৰক।",
"মহাপুৰুষীয়া ধৰ্মৰ প্ৰচাৰৰ বাবে কি কৰা হৈছিল?"
],
"পাঠ ১০": [
"সাহিত্যৰ ৰূপ পাঠটোত সাহিত্যৰ কেইটা ৰূপৰ কথা উল্লেখ আছে?",
"সাহিত্যৰ বিভিন্ন ৰূপবোৰৰ নাম লিখি বৰ্ণনা কৰক।",
"কবিতা আৰু গদ্যৰ মাজৰ পাৰ্থক্য লিখক।",
"নাটকৰ বৈশিষ্ট্যবোৰ লিখক।",
"সাহিত্যৰ সমাজত থকা ভূমিকা আলোচনা কৰক।"
]
},
"📘 হিন্দী (Hindi)": {
"पाठ १": [
"साखी पाठ का मुख्य संदेश क्या है?",
"कबीरदास की साखियों की भाषा-शैली पर प्रकाश डालिए।",
"साखी पाठ की किन्हीं दो साखियों का अर्थ समझाइए।",
"कबीरदास के दोहे समाज को क्या संदेश देते हैं?",
"साखी पाठ से हमें क्या शिक्षा मिलती है?"
],
"पाठ २": [
"पद पाठ की साहित्यिक विशेषताएँ बताइए।",
"मीराबाई के पदों में भक्ति भावना कैसे व्यक्त हुई है?",
"मीराबाई के जीवन पर प्रकाश डालिए।",
"पद पाठ की किन्हीं दो पंक्तियों का भावार्थ लिखिए।",
"मीराबाई के पदों में कृष्ण भक्ति कैसे दिखाई देती है?"
],
"पाठ ३": [
"दोहे पाठ के दोहे का अर्थ समझाइए।",
"रहीम के दोहों की विशेषताएँ बताइए।",
"रहीम के जीवन पर संक्षिप्त टिप्पणी लिखिए।",
"दोहे पाठ के किन्हीं दो दोहों का भावार्थ लिखिए।",
"रहीम के दोहे हमें क्या सीख देते हैं?"
],
"पाठ ४": [
"मनुष्यता कविता का सारांश लिखिए।",
"मैथिलीशरण गुप्त की 'मनुष्यता' कविता का मूल भाव क्या है?",
"मनुष्यता कविता की भाषा-शैली पर प्रकाश डालिए।",
"कविता में मनुष्य के कर्तव्यों के बारे में क्या कहा गया है?",
"मनुष्यता कविता से हमें क्या प्रेरणा मिलती है?"
],
"पाठ ५": [
"पर्वत प्रदेश में पावस कविता की भाषा-शैली पर प्रकाश डालिए।",
"सुमित्रानंदन पंत की कविता 'पर्वत प्रदेश में पावस' का केंद्रीय भाव लिखिए।",
"कविता में वर्षा ऋतु का कैसा चित्रण किया गया है?",
"कविता में प्रकृति चित्रण कैसे हुआ है?",
"पर्वत प्रदेश में पावस कविता की किन्हीं दो पंक्तियों की व्याख्या कीजिए।"
],
"पाठ ६": [
"मधुर-मधुर मेरे दीपक जल कविता की व्याख्या कीजिए।",
"महादेवी वर्मा की कविता 'मधुर-মधुर मेरे दीপক जल' का सार लिखिए।",
"कविता में दीपक किसका प्रतीक है?",
"महादेवी वर्मा की काव्य शैली की विशेषताएँ बताइए।",
"कविता से हमें क्या संदेश मिलता है?"
],
"पाठ ७": [
"तोप कविता का प्रतीकार्थ समझाइए।",
"केदारनाथ अग्रवाल की कविता 'तोप' का मुख्य विषय क्या है?",
"कविता में तोप किसका प्रतीक है?",
"कविता में युद्ध के प्रति क्या दृष्टिकोण व्यक्त किया गया है?",
"तोप कविता की भाषागत विशेषताएँ लिखिए।"
],
"पाठ ८": [
"कर चले हम फ़िदा गीत का ऐतिहासिक संदर्भ क्या है?",
"गीत 'कर चले हम फ़िदा' का मुख्य भाव लिखिए।",
"यह गीत हमें देशभक्ति की क्या सीख देता है?",
"गीत में वीर सैनिकों के बलिदान का कैसे वर्णन किया गया है?",
"गीत की भाषा-शैली पर टिप्पणी लिखिए।"
],
"पाठ ९": [
"आत्मत्राण कविता का केंद्रीय भाव लिखिए।",
"रवींद्रनाथ टैगोर की कविता 'आत्मत्राण' का सारांश लिखिए।",
"कविता में कवि ने ईश्वर से क्या प्रार्थना की है?",
"आत्मत्राण कविता से हमें क्या शिक्षा मिलती है?",
"कविता की भाषागत विशेषताएँ बताइए।"
],
"पाठ १०": [
"बड़े भाई साहब कहानी का नैतिक संदेश क्या है?",
"प्रेमचंद की कहानी 'बड़े भाई साहब' का सारांश लिखिए।",
"कहानी के दोनों भाइयों के चरित्र की तुलना कीजिए।",
"कहानी में शिक्षा प्रणाली पर क्या टिप्पणी की गई है?",
"प्रेमचंद की कहानी शैली की विशेषताएँ बताइए।"
]
}
}
# ===============================
# SAMPLE QUESTIONS DISPLAY SECTION
# ===============================
st.markdown("""
<div style="background: linear-gradient(145deg, #f8f9fa 0%, #e3f2fd 100%);
padding: 0.8rem;
border-radius: 10px;
border-left: 4px solid #2196F3;
margin-bottom: 1rem;">
<h4 style="color: #0d47a1; margin: 0; display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.2rem;">📋</span> নমুনা প্ৰশ্ন বাছনি কৰক
</h4>
<p style="color: #546e7a; font-size: 0.85rem; margin: 0.3rem 0 0 0;">
তলৰ তালিকাৰ পৰা এটা প্ৰশ্ন বাছনি কৰক
</p>
</div>
""", unsafe_allow_html=True)
sample_questions = SAMPLE_QUESTIONS.get(selected_subject, {}).get(selected_chapter_key, [])
if sample_questions:
# Create dropdown with sample questions
question_options = ["🎯 এটা প্ৰশ্ন বাছনি কৰক"] + sample_questions
# Custom styled dropdown container
st.markdown("""
<div style="background: white;
border: 2px solid #e3f2fd;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
""", unsafe_allow_html=True)
selected_question = st.selectbox(
"**নমুনা প্ৰশ্নৰ তালিকা:**",
options=question_options,
index=0,
key="styled_dropdown",
help="ড্ৰপডাউন খুলি প্ৰশ্নবোৰ চাওক",
label_visibility="collapsed"
)
st.markdown("</div>", unsafe_allow_html=True)
# If a question is selected
if selected_question != "🎯 এটা প্ৰশ্ন বাছনি কৰক":
# Show selected question in a styled box
st.markdown(f"""
<div style="background: linear-gradient(145deg, #e8f5e9 0%, #f1f8e9 100%);
border-left: 4px solid #4CAF50;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
box-shadow: 0 3px 10px rgba(76, 175, 80, 0.1);">
<div style="display: flex; align-items: center; margin-bottom: 0.5rem;">
<div style="background: #4CAF50;
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
margin-right: 0.8rem;">
</div>
<div>
<div style="font-weight: 700; color: #2e7d32; font-size: 0.9rem;">
বাছনি কৰা প্ৰশ্ন
</div>
<div style="font-size: 0.8rem; color: #558b2f;">
এতিয়া এই প্ৰশ্নটো ব্যৱহাৰ কৰিব পাৰে
</div>
</div>
</div>
<div style="background: white;
padding: 1rem;
border-radius: 6px;
border: 1px solid #c8e6c9;
font-size: 0.95rem;
color: #333;
line-height: 1.5;">
{selected_question}
</div>
</div>
""", unsafe_allow_html=True)
# Styled load button
col1, col2 = st.columns([1, 1])
with col1:
if st.button(
"✅ এই প্ৰশ্নটো ব্যৱহাৰ কৰক",
use_container_width=True,
type="primary",
help="প্ৰশ্নটো মেইন ইনপুট বাক্সত ল'ড কৰিব"
):
st.session_state.question_text = selected_question
st.success("✅ প্ৰশ্নটো সফলভাৱে ল'ড কৰা হৈছে!")
st.rerun()
with col2:
if st.button(
"🔄 নতুনকৈ বাছনি কৰক",
use_container_width=True,
type="secondary",
help="বেলেগ প্ৰশ্ন বাছনি কৰিব"
):
# Reset dropdown by removing the key
if 'styled_dropdown' in st.session_state:
del st.session_state.styled_dropdown
st.rerun()
# Show quick stats
st.markdown(f"""
<div style="display: flex;
justify-content: space-between;
background: #f5f5f5;
padding: 0.6rem 1rem;
border-radius: 6px;
margin-top: 1rem;
font-size: 0.85rem;">
<div style="color: #666;">
<span style="font-weight: bold; color: #2196F3;">{len(sample_questions)}</span> টা প্ৰশ্ন উপলব্ধ
</div>
<div style="color: #666;">
বিষয়: <span style="font-weight: bold; color: #2196F3;">{selected_subject.split(' ')[1] if ' ' in selected_subject else selected_subject}</span>
</div>
<div style="color: #666;">
অধ্যায়: <span style="font-weight: bold; color: #2196F3;">{selected_chapter_key}</span>
</div>
</div>
""", unsafe_allow_html=True)
else:
st.markdown("""
<div style="background: linear-gradient(145deg, #fff3e0 0%, #ffecb3 100%);
border-left: 4px solid #FF9800;
border-radius: 8px;
padding: 1.5rem;
text-align: center;
margin: 1rem 0;">
<div style="font-size: 3rem; margin-bottom: 0.5rem;">📭</div>
<h4 style="color: #EF6C00; margin: 0 0 0.5rem 0;">নমুনা প্ৰশ্ন উপলব্ধ নাই</h4>
<p style="color: #8d6e63; margin: 0; font-size: 0.9rem;">
<strong>{selected_subject}</strong>ৰ <strong>{current_chapter_name}</strong> অধ্যায়ৰ বাবে
নমুনা প্ৰশ্ন যোগ কৰা হোৱা নাই। <br>আপুনি নিজৰ প্ৰশ্নটো ওপৰৰ বাক্সত লিখিব পাৰে।
</p>
</div>
""", unsafe_allow_html=True)
# ===============================
# QUESTION INPUT AREA
# ===============================
st.markdown("---")
st.markdown("#### ✍️ আপোনাৰ প্ৰশ্নটো ইয়াত লিখক")
question = st.text_area(
"আপোনাৰ প্ৰশ্নটো ইয়াত লিখক:",
value=st.session_state.question_text,
height=100,
placeholder=f"উদাহৰণ: '{current_chapter_name}' অধ্যায়টো মোৰ বাবে বুজাই দিয়ক...",
key="question_input",
label_visibility="collapsed"
)
if question != st.session_state.question_text:
st.session_state.question_text = question
# Show API key status
if not api_key:
st.error("""
⚠️ **API কি ছেট আপ কৰক:**
**Hugging Face Spaces:**
১. Space Settings → Repository secrets
২. `DEEPSEEK_API_KEY` যোগ কৰক
৩. আপোনাৰ DeepSeek API কি দিয়ক
**স্থানীয়ভাবে:**
```bash
export DEEPSEEK_API_KEY="your-api-key-here"
```
""")
# ===============================
# CACHE CHECK AND SUBMIT BUTTON - WITH SUBSCRIPTION CHECK
# ===============================
submit_disabled = not (question.strip() and api_key)
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
if st.button(
"🚀 উত্তৰ দিবলৈ দিয়ক!",
type="primary",
use_container_width=True,
disabled=submit_disabled
):
if not question.strip():
st.error("❌ অনুগ্ৰহ কৰি প্ৰশ্নটো লিখক!")
elif not api_key:
st.error("❌ API কি ছেট আপ কৰক!")
else:
# Check subscription if available
if st.session_state.subscription_manager and st.session_state.get('user_id'):
can_ask, message = st.session_state.subscription_manager.can_ask_question(
st.session_state.user_id
)
if not can_ask:
st.error(f"❌ {message}")
# Show upgrade option for free users
user_plan = st.session_state.get('user_plan')
if user_plan and user_plan.get('plan_type') == 'free':
st.info("💡 Upgrade to Pro for more questions!")
if st.button("View Upgrade Plans", key="upgrade_from_error"):
st.session_state.show_subscription_plans = True
st.rerun()
st.stop()
# Check cache first
cache_key = create_cache_key(question, selected_subject, current_chapter_name)
# Get cache stats for debugging
cache_stats = st.session_state.cache_manager.get_stats()
cached_entry = st.session_state.cache_manager.get(cache_key)
if cached_entry:
# Determine cache source
cache_source = "Memory" if cache_key in st.session_state.cache_manager.memory_cache else "Supabase"
# Log the cached question usage
if st.session_state.subscription_manager and st.session_state.get('user_id'):
st.session_state.subscription_manager.log_question(
user_id=st.session_state.user_id,
question_hash=cache_key,
subject=selected_subject,
chapter=current_chapter_name,
tokens_used=cached_entry.get('tokens', 0),
cached=True
)
# Show cached answer
st.session_state.show_cached_answer = True
st.session_state.cached_answer_data = cached_entry
st.session_state.current_cache_key = cache_key
st.session_state.processing = False
st.session_state.cache_source = cache_source
else:
# Not in cache, proceed with API call
# Log the new question usage
if st.session_state.subscription_manager and st.session_state.get('user_id'):
st.session_state.subscription_manager.log_question(
user_id=st.session_state.user_id,
question_hash=cache_key,
subject=selected_subject,
chapter=current_chapter_name,
cached=False
)
st.session_state.processing = True
st.session_state.current_cache_key = cache_key
# ===============================
# DISPLAY CACHED ANSWER WITH THINKING ANIMATION
# ===============================
if st.session_state.get('show_cached_answer') and st.session_state.get('cached_answer_data'):
cached_data = st.session_state.cached_answer_data
cache_source = st.session_state.get('cache_source', 'Cache')
# Display cached answer with animation
display_cached_answer_with_animation(
cached_data,
question,
selected_subject,
current_chapter_name,
cache_source
)
# Reset flag
st.session_state.show_cached_answer = False
if 'cached_answer_data' in st.session_state:
del st.session_state.cached_answer_data
if 'current_cache_key' in st.session_state:
del st.session_state.current_cache_key
# ===============================
# PROCESS QUESTION WITH STREAMING AND THINKING ANIMATION
# ===============================
if st.session_state.get('processing') and question and api_key:
# Display user question
st.markdown(f"""
<div class="chat-container">
<div style="display: flex; justify-content: flex-end; margin-bottom: 0.3rem;">
<div class="user-bubble">
<div style="font-weight: 600; margin-bottom: 0.2rem;">👤 আপুনি:</div>
<div>{question[:200]}{'...' if len(question) > 200 else ''}</div>
</div>
</div>
""", unsafe_allow_html=True)
# AI answer header with thinking animation initially
st.markdown(f"""
<div style="display: flex; align-items: flex-start; margin-bottom: 0.3rem;">
<div style="margin-right: 0.5rem; font-size: 1.2rem;">🤖</div>
<div style="flex: 1;">
<div class="ai-bubble">
<div style="display: flex; align-items: center; margin-bottom: 0.5rem; padding-bottom: 0.5rem; border-bottom: 2px solid #2196F3;">
<div style="display: flex; align-items: center;">
<div style="background: #2196F3; color: white; padding: 0.2rem 0.5rem; border-radius: 8px;
font-weight: 600; font-size: 0.8rem; margin-right: 0.5rem;">
AI টিউটাৰ
</div>
<div style="font-weight: 600; color: #0d47a1; font-size: 0.9rem;">
{selected_subject}{current_chapter_name}
</div>
</div>
<div style="font-size: 0.75rem; color: #666; background: #e3f2fd; padding: 0.2rem 0.5rem; border-radius: 4px;">
<span style="margin-right: 0.3rem;">⚡</span> Generating...
</div>
</div>
<div id="ai-answer-content" style="color: #333; line-height: 1.5; font-size: 0.95rem;">
<!-- Answer will be streamed here -->
</div>
</div>
</div>
</div>
</div>
""", unsafe_allow_html=True)
# Create a placeholder for the thinking animation
thinking_placeholder = st.empty()
# Show thinking animation
thinking_placeholder.markdown("""
<div class="progress-indicator">
<span>উত্তৰ প্ৰস্তুত কৰি আছো...</span>
<div class="thinking-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
""", unsafe_allow_html=True)
# Get the prompt and stream the response
system_prompt = get_subject_prompt(selected_subject, current_chapter_name, question)
# Stream the response
stream_deepseek_response(system_prompt, question, selected_subject, current_chapter_name)
st.session_state.processing = False
# ===============================
# HISTORY
# ===============================
if st.session_state.history:
st.markdown("---")
st.markdown("#### 📜 আজিৰ প্ৰশ্নাৱলী")
for i, item in enumerate(reversed(st.session_state.history[-5:]), 1):
cache_indicator = " ⚡" if item.get('cached') else " 🤖"
cache_source = f" ({item.get('cache_source', 'API')})" if item.get('cached') else ""
with st.expander(f"প্ৰশ্ন {i}: {item['question']} ({item['timestamp']}{cache_indicator}{cache_source})"):
st.write(f"**বিষয়:** {item['subject']}")
st.write(f"**অধ্যায়:** {item['chapter']}")
st.write(f"**ট'কেন:** {item.get('tokens', 0):,}")
if item.get('cached'):
st.caption(f"⚡ This answer was served from {item.get('cache_source', 'cache')}")
# ===============================
# FOOTER
# ===============================
st.markdown("---")
st.markdown("""
<div style="text-align: center; padding: 0.5rem;">
<h3 style="color: #0d47a1; margin-bottom: 0.5rem;">
🎓 আপোনাৰ সফলতাৰ বাবে মই সদায় আছো!
</h3>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div style="text-align: center; padding: 0.5rem; margin-top: 1rem; color: #1976D2; font-size: 0.8rem;">
<p style="margin: 0;">© 2025 Jajabor AI. All rights reserved.</p>
</div>
""", unsafe_allow_html=True)