cryogenic22 commited on
Commit
ee76500
·
verified ·
1 Parent(s): 509178e

Update src/components/ai_tutor.py

Browse files
Files changed (1) hide show
  1. src/components/ai_tutor.py +154 -121
src/components/ai_tutor.py CHANGED
@@ -1,126 +1,148 @@
1
  import streamlit as st
2
- from src.services.ai_service import AITutorService
3
- from src.utils.session import get_tutor_context
4
  from datetime import datetime
5
- import platform
6
- import base64
7
- from openai import OpenAI
8
- import tempfile
9
- import os
10
  import anthropic
 
 
11
 
12
- class AITutor:
13
  def __init__(self):
14
- self.service = AITutorService()
15
- self.voice_enabled = False
16
- self.tts_mode = None # 'local' or 'openai'
17
- self.openai_enabled = False # Initialize the flag
18
- self.initialize_openai() # Initialize OpenAI first
19
- self.initialize_speech_components() # Then initialize speech components
20
-
21
- def initialize_openai(self):
22
- """Initialize OpenAI client"""
23
  try:
24
- # Get API key from environment variable
25
- api_key = os.getenv('OPENAI_API_KEY')
 
 
 
 
26
  if not api_key:
27
- st.warning("OpenAI API key not found in environment variables. Voice features will be limited.")
28
- self.openai_enabled = False
 
 
 
29
  return
30
 
31
- self.openai_client = OpenAI(api_key=api_key)
32
- self.openai_enabled = True
33
-
34
  except Exception as e:
35
- st.warning("Error initializing OpenAI client. Voice features will be limited.")
36
- self.openai_enabled = False
37
-
38
- def initialize_speech_components(self):
39
- """Initialize text-to-speech with fallback options"""
40
- if 'tts_engine' not in st.session_state:
41
- try:
42
- import pyttsx3
43
-
44
- # Try local TTS first
45
- system = platform.system()
46
- if system == "Windows":
47
- st.session_state.tts_engine = pyttsx3.init(driverName='sapi5')
48
- elif system == "Darwin": # macOS
49
- st.session_state.tts_engine = pyttsx3.init(driverName='nsss')
50
- else: # Linux
51
- st.session_state.tts_engine = pyttsx3.init()
52
-
53
- # Configure voice properties
54
- st.session_state.tts_engine.setProperty('rate', 150)
55
- st.session_state.tts_engine.setProperty('volume', 0.9)
56
-
57
- try:
58
- voices = st.session_state.tts_engine.getProperty('voices')
59
- female_voice = next((voice for voice in voices if 'female' in voice.name.lower()), voices[0])
60
- st.session_state.tts_engine.setProperty('voice', female_voice.id)
61
- except Exception:
62
- pass # Use default voice if female voice setting fails
63
-
64
- self.voice_enabled = True
65
- self.tts_mode = 'local'
66
- except Exception as e:
67
- st.info("Local text-to-speech unavailable. Falling back to OpenAI TTS.")
68
- st.session_state.tts_engine = None
69
- if self.openai_enabled:
70
- self.voice_enabled = True
71
- self.tts_mode = 'openai'
72
- else:
73
- self.voice_enabled = False
74
- self.tts_mode = None
75
-
76
- def speak_with_openai(self, text: str):
77
- """Generate speech using OpenAI's TTS"""
 
 
 
 
 
 
 
 
 
 
78
  try:
79
- # Generate speech using OpenAI
80
- response = self.openai_client.audio.speech.create(
81
- model="tts-1",
82
- voice="alloy", # or "nova", "shimmer", "echo", "fable"
83
- input=text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  )
85
-
86
- # Create a temporary file to store the audio
87
- with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as tmp_file:
88
- response.stream_to_file(tmp_file.name)
89
-
90
- # Create audio player in Streamlit
91
- audio_file = open(tmp_file.name, 'rb')
92
- audio_bytes = audio_file.read()
93
- st.audio(audio_bytes, format='audio/mp3')
94
-
95
- # Clean up
96
- audio_file.close()
97
- os.unlink(tmp_file.name)
98
-
 
99
  except Exception as e:
100
- st.error(f"Error during OpenAI speech synthesis: {e}")
101
-
102
- def speak(self, text: str):
103
- """Make the AI tutor speak using available TTS method"""
104
- if not self.voice_enabled:
105
- return
106
-
107
- if self.tts_mode == 'local':
108
- try:
109
- import threading
110
- def speak_text():
111
- st.session_state.tts_engine.say(text)
112
- st.session_state.tts_engine.runAndWait()
113
-
114
- thread = threading.Thread(target=speak_text)
115
- thread.start()
116
- except Exception as e:
117
- st.error(f"Error during local speech synthesis: {e}")
118
- if self.openai_enabled:
119
- st.info("Falling back to OpenAI TTS...")
120
- self.speak_with_openai(text)
121
-
122
- elif self.tts_mode == 'openai':
123
- self.speak_with_openai(text)
124
 
125
  def display_chat_interface(self):
126
  """Display the enhanced chat interface with avatar"""
@@ -129,13 +151,15 @@ class AITutor:
129
  # Voice controls with TTS mode indicator
130
  col1, col2 = st.columns([3, 2])
131
  with col1:
132
- if self.voice_enabled:
133
- voice_active = st.toggle("Enable Voice", value=False, key="voice_active")
134
- else:
135
- voice_active = False
 
136
  with col2:
137
- if self.tts_mode:
138
- st.info(f"Using {self.tts_mode.upper()} TTS")
 
139
 
140
  # Display avatar
141
  self.service.display_avatar(state='neutral')
@@ -162,9 +186,9 @@ class AITutor:
162
  st.write(message["content"])
163
  if message["role"] == "assistant":
164
  self.service.display_avatar(state='happy')
165
- if voice_active and message.get('speak', True):
166
- self.speak(message["content"])
167
- message['speak'] = False
168
 
169
  # Chat input
170
  if prompt := st.text_input("Ask your question...", key="chat_input"):
@@ -183,7 +207,7 @@ class AITutor:
183
  })
184
 
185
  # Generate and display AI response
186
- response = self.service.generate_response(user_input, context['current_topic'])
187
 
188
  # Add AI response
189
  context['chat_history'].append({
@@ -196,9 +220,18 @@ class AITutor:
196
  self.service.display_avatar(state='happy')
197
  st.experimental_rerun()
198
 
 
 
 
 
 
 
 
 
 
199
  def main():
200
  """Main Streamlit application entry point"""
201
- st.set_page_config(page_title="AI Tutor", page_icon="🤖")
202
 
203
  ai_tutor = AITutor()
204
  ai_tutor.display_chat_interface()
 
1
  import streamlit as st
 
 
2
  from datetime import datetime
 
 
 
 
 
3
  import anthropic
4
+ import os
5
+ from typing import Dict
6
 
7
+ class AITutorService:
8
  def __init__(self):
9
+ self._client = None
10
+ self.initialize_client()
11
+ self._load_avatar_assets()
12
+
13
+ def initialize_client(self):
14
+ """Initialize Anthropic client with better error handling"""
 
 
 
15
  try:
16
+ # First try getting API key from Streamlit secrets
17
+ api_key = None
18
+ if hasattr(st, 'secrets') and 'ANTHROPIC_API_KEY' in st.secrets:
19
+ api_key = st.secrets['ANTHROPIC_API_KEY']
20
+
21
+ # If not in secrets, try environment variable
22
  if not api_key:
23
+ api_key = os.getenv('ANTHROPIC_API_KEY')
24
+
25
+ if not api_key:
26
+ st.error("Anthropic API key not found. Please set ANTHROPIC_API_KEY in .streamlit/secrets.toml or environment variables.")
27
+ st.info("For testing, you can set this up locally by creating a .streamlit/secrets.toml file with:\nANTHROPIC_API_KEY='your-key-here'")
28
  return
29
 
30
+ self._client = anthropic.Anthropic(api_key=api_key)
31
+
 
32
  except Exception as e:
33
+ st.error(f"Failed to initialize Anthropic client: {str(e)}")
34
+ self._client = None
35
+
36
+ def _load_avatar_assets(self):
37
+ """Initialize avatar states and assets"""
38
+ self.avatar_states = {
39
+ 'neutral': """
40
+ <svg width="100" height="100" viewBox="0 0 100 100">
41
+ <circle cx="50" cy="50" r="45" fill="#4A90E2"/>
42
+ <circle cx="35" cy="40" r="5" fill="white"/>
43
+ <circle cx="65" cy="40" r="5" fill="white"/>
44
+ <path d="M 30 65 Q 50 65 70 65" stroke="white" fill="none" stroke-width="3"/>
45
+ </svg>
46
+ """,
47
+ 'thinking': """
48
+ <svg width="100" height="100" viewBox="0 0 100 100">
49
+ <circle cx="50" cy="50" r="45" fill="#4A90E2"/>
50
+ <circle cx="35" cy="40" r="5" fill="white"/>
51
+ <circle cx="65" cy="40" r="5" fill="white"/>
52
+ <path d="M 30 60 Q 50 70 70 60" stroke="white" fill="none" stroke-width="3"/>
53
+ </svg>
54
+ """,
55
+ 'happy': """
56
+ <svg width="100" height="100" viewBox="0 0 100 100">
57
+ <circle cx="50" cy="50" r="45" fill="#4A90E2"/>
58
+ <circle cx="35" cy="40" r="5" fill="white"/>
59
+ <circle cx="65" cy="40" r="5" fill="white"/>
60
+ <path d="M 30 60 Q 50 80 70 60" stroke="white" fill="none" stroke-width="3"/>
61
+ </svg>
62
+ """
63
+ }
64
+
65
+ @property
66
+ def client(self):
67
+ """Property to safely access the Anthropic client"""
68
+ if self._client is None:
69
+ raise ValueError("Anthropic client not properly initialized")
70
+ return self._client
71
+
72
+ def display_avatar(self, state: str = 'neutral'):
73
+ """Display the AI tutor avatar"""
74
+ avatar_svg = self.avatar_states.get(state, self.avatar_states['neutral'])
75
+ st.markdown(f"""
76
+ <div style="display: flex; justify-content: center; margin: 20px 0;">
77
+ {avatar_svg}
78
+ </div>
79
+ """, unsafe_allow_html=True)
80
+
81
+ def generate_response(self, user_input: str) -> str:
82
+ """Generate response using Claude with better error handling"""
83
+ if not self._client:
84
+ return "I apologize, but I'm not properly connected right now. Please check the API configuration."
85
+
86
  try:
87
+ # Get session context
88
+ context = st.session_state.get('tutor_context', {})
89
+ current_topic = context.get('current_topic')
90
+
91
+ # Build system prompt
92
+ system_prompt = """You are an expert tutor helping students learn.
93
+ Give clear, step-by-step explanations with examples.
94
+ Focus on the current topic if specified."""
95
+
96
+ if current_topic and current_topic != "All Topics":
97
+ system_prompt += f"\nYou are currently tutoring about {current_topic}."
98
+
99
+ # Generate response
100
+ response = self.client.messages.create(
101
+ model="claude-3-opus-20240229",
102
+ temperature=0.7,
103
+ max_tokens=1024,
104
+ system=system_prompt,
105
+ messages=[{
106
+ "role": "user",
107
+ "content": user_input
108
+ }]
109
  )
110
+
111
+ # Get response text
112
+ response_text = response.content[0].text
113
+
114
+ # Update metrics
115
+ self.update_engagement_metrics(user_input, response_text)
116
+
117
+ return response_text
118
+
119
+ except anthropic.APIError as e:
120
+ st.error(f"API Error: {str(e)}")
121
+ return "I encountered an API error. Please try again in a moment."
122
+ except anthropic.APIConnectionError as e:
123
+ st.error(f"Connection Error: {str(e)}")
124
+ return "I'm having trouble connecting to my API. Please check your internet connection."
125
  except Exception as e:
126
+ st.error(f"Unexpected error: {str(e)}")
127
+ return "I encountered an unexpected error. Please try again."
128
+
129
+ def update_engagement_metrics(self, user_input: str, response: str):
130
+ """Update engagement metrics"""
131
+ if 'tutor_context' not in st.session_state:
132
+ st.session_state.tutor_context = {
133
+ 'engagement_metrics': []
134
+ }
135
+
136
+ metrics = st.session_state.tutor_context['engagement_metrics']
137
+ metrics.append({
138
+ 'timestamp': datetime.now().isoformat(),
139
+ 'sentiment_score': min(1.0, len(user_input) / 100.0),
140
+ 'interaction_length': len(user_input)
141
+ })
142
+
143
+ class AITutor:
144
+ def __init__(self):
145
+ self.service = AITutorService()
 
 
 
 
146
 
147
  def display_chat_interface(self):
148
  """Display the enhanced chat interface with avatar"""
 
151
  # Voice controls with TTS mode indicator
152
  col1, col2 = st.columns([3, 2])
153
  with col1:
154
+ st.write("Voice features not implemented yet.")
155
+ # if self.voice_enabled:
156
+ # voice_active = st.toggle("Enable Voice", value=False, key="voice_active")
157
+ # else:
158
+ # voice_active = False
159
  with col2:
160
+ st.write("TTS mode not implemented yet.")
161
+ # if self.tts_mode:
162
+ # st.info(f"Using {self.tts_mode.upper()} TTS")
163
 
164
  # Display avatar
165
  self.service.display_avatar(state='neutral')
 
186
  st.write(message["content"])
187
  if message["role"] == "assistant":
188
  self.service.display_avatar(state='happy')
189
+ # if voice_active and message.get('speak', True):
190
+ # self.speak(message["content"])
191
+ # message['speak'] = False
192
 
193
  # Chat input
194
  if prompt := st.text_input("Ask your question...", key="chat_input"):
 
207
  })
208
 
209
  # Generate and display AI response
210
+ response = self.service.generate_response(user_input)
211
 
212
  # Add AI response
213
  context['chat_history'].append({
 
220
  self.service.display_avatar(state='happy')
221
  st.experimental_rerun()
222
 
223
+ def get_tutor_context() -> Dict:
224
+ """Get or initialize tutor context"""
225
+ if 'tutor_context' not in st.session_state:
226
+ st.session_state.tutor_context = {
227
+ 'current_topic': None,
228
+ 'chat_history': []
229
+ }
230
+ return st.session_state.tutor_context
231
+
232
  def main():
233
  """Main Streamlit application entry point"""
234
+ st.set_page_config(page_title="EduAI Platform", page_icon="🎓")
235
 
236
  ai_tutor = AITutor()
237
  ai_tutor.display_chat_interface()