Spaces:
Runtime error
Runtime error
| """ | |
| Content generator specifically for ReadingPractice.jsx component | |
| Handles all reading passage generation, text analysis, and reading-specific features | |
| """ | |
| import os | |
| import json | |
| import requests | |
| import logging | |
| from typing import List, Dict, Optional | |
| from dotenv import load_dotenv | |
| # Import Google's Generative AI library | |
| try: | |
| import google.generativeai as genai | |
| except ImportError: | |
| genai = None | |
| # Load environment variables | |
| load_dotenv() | |
| # Set up logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Get API keys from environment | |
| ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY") | |
| OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") | |
| DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY") | |
| GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") | |
| # Initialize Gemini if available | |
| if genai and GEMINI_API_KEY: | |
| genai.configure(api_key=GEMINI_API_KEY) | |
| class ReadingPracticeGenerator: | |
| """Content generator specifically for ReadingPractice.jsx component""" | |
| def __init__(self): | |
| # Determine which AI provider to use | |
| self.has_gemini = bool(genai and GEMINI_API_KEY) | |
| self.has_deepseek = bool(DEEPSEEK_API_KEY) | |
| self.has_anthropic = bool(ANTHROPIC_API_KEY) | |
| self.has_openai = bool(OPENAI_API_KEY) | |
| print(f"ReadingPracticeGenerator initialized - Gemini: {self.has_gemini}, DeepSeek: {self.has_deepseek}, Anthropic: {self.has_anthropic}, OpenAI: {self.has_openai}") | |
| if not any([self.has_gemini, self.has_deepseek, self.has_anthropic, self.has_openai]): | |
| logger.warning("No API keys found. Using mock content generation instead.") | |
| def generate_content(self, prompt: str) -> str: | |
| """Generate content using available AI provider""" | |
| if self.has_gemini: | |
| return self._generate_with_gemini(prompt) | |
| elif self.has_deepseek: | |
| return self._generate_with_deepseek(prompt) | |
| elif self.has_anthropic: | |
| return self._generate_with_anthropic(prompt) | |
| elif self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_gemini(self, prompt: str) -> str: | |
| """Generate content using Google's Gemini API""" | |
| try: | |
| genai.configure(api_key=GEMINI_API_KEY) | |
| # Use component-specific model for reading content generation | |
| model_name = os.environ.get('READING_CONTENT_MODEL', 'gemini-2.5-flash') | |
| model = genai.GenerativeModel(model_name) | |
| response = model.generate_content( | |
| prompt, | |
| generation_config={ | |
| "temperature": 0.7, | |
| "top_p": 0.95, | |
| "top_k": 40, | |
| "max_output_tokens": 1000, | |
| } | |
| ) | |
| if response and hasattr(response, 'text'): | |
| return response.text | |
| elif response and hasattr(response, 'parts'): | |
| return response.parts[0].text | |
| else: | |
| raise ValueError("Unexpected response format from Gemini API") | |
| except Exception as e: | |
| logger.error(f"Error generating with Gemini: {str(e)}") | |
| if self.has_deepseek: | |
| return self._generate_with_deepseek(prompt) | |
| elif self.has_anthropic: | |
| return self._generate_with_anthropic(prompt) | |
| elif self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_deepseek(self, prompt: str) -> str: | |
| """Generate content using DeepSeek API""" | |
| try: | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {DEEPSEEK_API_KEY}" | |
| } | |
| payload = { | |
| "model": "deepseek-chat", | |
| "messages": [ | |
| {"role": "system", "content": "You are a speech therapy assistant that creates reading practice materials for fluency training."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| "temperature": 0.7, | |
| "max_tokens": 1000 | |
| } | |
| response = requests.post( | |
| "https://api.deepseek.com/v1/chat/completions", | |
| json=payload, | |
| headers=headers | |
| ) | |
| response.raise_for_status() | |
| result = response.json() | |
| return result["choices"][0]["message"]["content"].strip() | |
| except Exception as e: | |
| logger.error(f"Error generating content with DeepSeek: {str(e)}") | |
| if self.has_anthropic: | |
| return self._generate_with_anthropic(prompt) | |
| elif self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_anthropic(self, prompt: str) -> str: | |
| """Generate content using Anthropic's Claude API""" | |
| try: | |
| headers = { | |
| "Content-Type": "application/json", | |
| "X-Api-Key": ANTHROPIC_API_KEY, | |
| "anthropic-version": "2023-06-01" | |
| } | |
| data = { | |
| "model": "claude-3-haiku-20240307", | |
| "messages": [ | |
| {"role": "user", "content": prompt} | |
| ], | |
| "max_tokens": 1000, | |
| "temperature": 0.7 | |
| } | |
| response = requests.post( | |
| "https://api.anthropic.com/v1/messages", | |
| headers=headers, | |
| json=data | |
| ) | |
| response.raise_for_status() | |
| result = response.json() | |
| return result["content"][0]["text"] | |
| except Exception as e: | |
| logger.error(f"Error generating content with Anthropic: {str(e)}") | |
| if self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_openai(self, prompt: str) -> str: | |
| """Generate content using OpenAI's API""" | |
| try: | |
| headers = { | |
| "Authorization": f"Bearer {OPENAI_API_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| data = { | |
| "model": "gpt-3.5-turbo", | |
| "messages": [ | |
| {"role": "system", "content": "You are a speech therapy assistant that creates reading practice materials for fluency training."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| "max_tokens": 1000, | |
| "temperature": 0.7 | |
| } | |
| response = requests.post( | |
| "https://api.openai.com/v1/chat/completions", | |
| headers=headers, | |
| json=data | |
| ) | |
| response.raise_for_status() | |
| result = response.json() | |
| return result["choices"][0]["message"]["content"].strip() | |
| except Exception as e: | |
| logger.error(f"Error generating content with OpenAI: {str(e)}") | |
| return self._generate_mock_content(prompt) | |
| def _generate_mock_content(self, prompt: str) -> str: | |
| """Fallback mock content when no AI provider is available""" | |
| # Extract topic if possible | |
| topic = "general topics" | |
| if "about " in prompt: | |
| parts = prompt.split("about ") | |
| if len(parts) > 1: | |
| topic_part = parts[1].split(".")[0] | |
| topic = topic_part.strip() | |
| return f"""Here's a reading passage about {topic}. This contains various words designed for speech therapy practice. Focus on maintaining smooth airflow and clear articulation as you read. Try reading at different speeds to improve your control. Pay attention to the natural rhythm of the sentences. This passage is designed for fluency shaping practice and can be used with various speech techniques.""" | |
| def generate_reading_passage( | |
| topic: str, | |
| difficulty: str = "medium", | |
| format_type: str = "paragraph", | |
| vowel_focus: Optional[List[str]] = None, | |
| consonant_focus: Optional[List[str]] = None, | |
| sentence_count: Optional[int] = None, | |
| technique: Optional[str] = None | |
| ) -> str: | |
| """ | |
| Generate a reading passage specifically for ReadingPractice.jsx component | |
| Args: | |
| topic: The subject of the passage | |
| difficulty: Complexity level (easy, medium, hard) | |
| format_type: Paragraph, sentences, or story | |
| vowel_focus: List of vowel sounds to emphasize | |
| consonant_focus: List of consonant sounds to emphasize | |
| sentence_count: Number of sentences (for sentences format) | |
| technique: Speech technique being practiced (normal, prolonged, gentle_onset, etc.) | |
| Returns: | |
| Generated text passage optimized for reading practice | |
| """ | |
| generator = ReadingPracticeGenerator() | |
| if not sentence_count and format_type == "sentences": | |
| sentence_count = 5 | |
| reading_level = { | |
| "easy": "simple vocabulary and short sentences (1st-2nd grade level)", | |
| "medium": "moderate vocabulary and sentence structure (3rd-5th grade level)", | |
| "hard": "more complex vocabulary and varied sentence structure (6th-8th grade level)" | |
| }.get(difficulty, "moderate vocabulary and sentence structure") | |
| # Reading-specific technique guidance | |
| technique_guidance = "" | |
| if technique: | |
| if technique == "prolonged" or technique == "prolonged_speech": | |
| technique_guidance = "Include words with long vowel sounds (like 'afternoon', 'beautiful', 'wonderful') that can be stretched out for prolonged speech practice. Use flowing, continuous phrases." | |
| elif technique == "gentle_onset" or technique == "easy_onset": | |
| technique_guidance = "Include many words that begin with vowels (apple, elephant, ocean) or soft consonants (mellow, nice, warm) for gentle/easy onset practice." | |
| elif technique == "light_contacts": | |
| technique_guidance = "Include words with consonant clusters and plosive sounds (practice, butterfly, wonderful) for light articulatory contact practice." | |
| elif technique == "continuous_phonation": | |
| technique_guidance = "Create phrases that flow together smoothly with minimal pauses, emphasizing connected speech patterns." | |
| elif technique == "rhythmic": | |
| technique_guidance = "Use natural rhythm patterns and include some repeated phrases or parallel structures for rhythmic speaking practice." | |
| vowel_instruction = "" | |
| if vowel_focus and len(vowel_focus) > 0: | |
| vowel_str = ", ".join([f"'{v}'" for v in vowel_focus]) | |
| vowel_instruction = f"Emphasize words containing these vowel sounds: {vowel_str}. Include multiple words with these sounds throughout the passage." | |
| consonant_instruction = "" | |
| if consonant_focus and len(consonant_focus) > 0: | |
| consonant_str = ", ".join([f"'{c}'" for c in consonant_focus]) | |
| consonant_instruction = f"Emphasize words containing these consonant sounds: {consonant_str}. Target these sounds in various word positions." | |
| format_instruction = { | |
| "paragraph": f"Create a cohesive, well-structured paragraph about {topic} with 4-6 sentences that flow naturally for reading practice.", | |
| "sentences": f"Create {sentence_count} individual, standalone sentences about {topic} that are perfect for sentence-by-sentence reading practice.", | |
| "story": f"Create a short story about {topic} with a clear beginning, middle, and end that engages the reader while providing good reading practice material." | |
| }.get(format_type, f"Write about {topic}") | |
| prompt = f""" | |
| {format_instruction} | |
| Requirements for ReadingPractice.jsx component: | |
| - Use {reading_level} | |
| - Make the content engaging and appropriate for speech therapy reading practice | |
| - Ensure smooth flow and natural rhythm for oral reading | |
| - Include varied sentence structures to challenge different aspects of fluency | |
| {vowel_instruction} | |
| {consonant_instruction} | |
| {technique_guidance} | |
| The text will be used for: | |
| 1. Oral reading practice with fluency techniques | |
| 2. Speech rhythm and pacing exercises | |
| 3. Articulation and pronunciation practice | |
| 4. Confidence building through successful reading experiences | |
| Provide only the text passage with no additional explanations, headers, or commentary. | |
| """ | |
| try: | |
| content = generator.generate_content(prompt) | |
| if not content or len(content.strip()) < 10: | |
| logger.error("AI returned empty or very short content") | |
| return generate_fallback_reading_passage(topic, format_type, sentence_count) | |
| return content.strip() | |
| except Exception as e: | |
| logger.error(f"Error generating reading passage: {str(e)}") | |
| return generate_fallback_reading_passage(topic, format_type, sentence_count) | |
| def generate_fallback_reading_passage( | |
| topic: str, | |
| format_type: str, | |
| sentence_count: Optional[int] = 5 | |
| ) -> str: | |
| """Generate high-quality fallback reading passages when AI generation fails""" | |
| # Topic-specific content optimized for reading practice | |
| reading_topics = { | |
| "technology": [ | |
| "Smartphones have revolutionized how we communicate and access information in our daily lives.", | |
| "Artificial intelligence is being integrated into many everyday devices, making them smarter and more helpful.", | |
| "Cloud computing allows us to store vast amounts of data remotely and access it from anywhere.", | |
| "Virtual reality creates immersive experiences that transport us to different worlds for gaming and education.", | |
| "Robotics is advancing rapidly in manufacturing, healthcare, and even household assistance.", | |
| "Social media platforms connect people across the globe, sharing experiences and information instantly.", | |
| "Electric vehicles are becoming more popular as we work toward sustainable transportation solutions." | |
| ], | |
| "travel": [ | |
| "Paris attracts millions of visitors each year to see the magnificent Eiffel Tower and world-famous Louvre Museum.", | |
| "Japan's efficient bullet trains make traveling between major cities incredibly fast and comfortable for tourists.", | |
| "The Great Barrier Reef in Australia is the world's largest coral reef system, teeming with colorful marine life.", | |
| "Machu Picchu reveals the impressive engineering skills of the ancient Inca civilization high in the Andes Mountains.", | |
| "Venice is famous for its romantic canals, with gondolas and water taxis as the main forms of transportation.", | |
| "The Northern Lights create spectacular natural displays of dancing colors across the Arctic night sky.", | |
| "African safaris offer incredible opportunities to observe lions, elephants, and giraffes in their natural habitat." | |
| ], | |
| "nature": [ | |
| "Elephants are highly intelligent animals with complex social structures and remarkable long-term memories.", | |
| "Dolphins communicate using sophisticated series of clicks, whistles, and expressive body movements.", | |
| "Chameleons possess the amazing ability to change colors to match their surroundings and regulate body temperature.", | |
| "Eagles have incredibly sharp eyesight that allows them to spot small prey from tremendous distances.", | |
| "Octopuses have three hearts and demonstrate remarkable intelligence by solving complex puzzles and problems.", | |
| "Butterflies undergo complete metamorphosis, transforming from caterpillars into beautiful flying creatures.", | |
| "Rainforests contain more species of plants and animals than any other ecosystem on our planet." | |
| ], | |
| "science": [ | |
| "Gravity is the fundamental force that pulls objects toward each other throughout the entire universe.", | |
| "Photosynthesis is the amazing process that plants use to convert sunlight into energy for growth.", | |
| "DNA contains all the genetic instructions necessary for the development and functioning of living organisms.", | |
| "The periodic table organizes all known chemical elements according to their properties and atomic structure.", | |
| "Light travels at approximately 186,000 miles per second through the vacuum of space.", | |
| "The water cycle continuously moves water between oceans, atmosphere, and land through evaporation and precipitation.", | |
| "Renewable energy sources like solar and wind power offer sustainable alternatives to fossil fuels." | |
| ], | |
| "food": [ | |
| "Italian cuisine features fresh ingredients like tomatoes, basil, and mozzarella in dishes like pizza and pasta.", | |
| "Japanese cooking emphasizes simplicity and natural flavors, with sushi being one of its most famous exports.", | |
| "Mediterranean diets include healthy olive oil, fresh vegetables, fish, and whole grains for balanced nutrition.", | |
| "French pastry chefs create delicate croissants, éclairs, and macarons with precise techniques and artistry.", | |
| "Mexican food combines bold spices, fresh herbs, and vibrant peppers in dishes like tacos and enchiladas.", | |
| "Thai cuisine balances sweet, sour, salty, and spicy flavors in aromatic curries and stir-fried dishes.", | |
| "Farm-to-table restaurants prioritize locally sourced, seasonal ingredients for the freshest possible meals." | |
| ] | |
| } | |
| # Get topic-specific sentences or create generic ones | |
| sentences = reading_topics.get(topic.lower(), [ | |
| f"{topic.title()} is a fascinating subject that continues to evolve and develop over time.", | |
| f"Many people are interested in learning more about {topic} through books, articles, and online resources.", | |
| f"Experts and researchers in {topic} often share their knowledge through lectures, presentations, and publications.", | |
| f"The history and development of {topic} reveals interesting patterns, innovations, and breakthroughs.", | |
| f"Modern advances and discoveries in {topic} have significantly changed how we understand many aspects of life.", | |
| f"Students and professionals studying {topic} develop valuable skills, perspectives, and specialized knowledge.", | |
| f"The future of {topic} will likely bring exciting new discoveries, innovations, and practical applications." | |
| ]) | |
| if format_type == "sentences": | |
| return "\n\n".join(sentences[:sentence_count]) | |
| elif format_type == "story": | |
| return f"""Once upon a time, there was a curious student who became deeply interested in {topic}. They discovered that {sentences[0].lower()} As they learned more through careful study, they found that {sentences[1].lower()} Their dedicated research showed them that {sentences[2].lower()} | |
| The most surprising thing they learned was that {sentences[3].lower()} Eventually, they became quite knowledgeable and began sharing their expertise with friends and colleagues, explaining how {sentences[4].lower()} Everyone was impressed by how much they had learned about {topic} and the passion they showed for the subject. | |
| Their journey of discovery taught them that learning about {topic} opens up a whole new world of understanding and appreciation.""" | |
| else: # paragraph | |
| return " ".join(sentences[:5]) | |
| def analyze_reading_difficulty(text: str) -> Dict[str, any]: | |
| """ | |
| Analyze the difficulty level of a reading passage for ReadingPractice.jsx | |
| Returns metrics useful for the reading practice component | |
| """ | |
| import re | |
| # Basic text analysis | |
| sentences = re.split(r'[.!?]+', text) | |
| sentences = [s.strip() for s in sentences if s.strip()] | |
| words = text.split() | |
| total_words = len(words) | |
| total_sentences = len(sentences) | |
| if total_sentences == 0: | |
| return {"error": "No sentences found"} | |
| # Calculate metrics | |
| avg_words_per_sentence = total_words / total_sentences | |
| avg_chars_per_word = sum(len(word.strip('.,!?";:')) for word in words) / total_words if words else 0 | |
| # Count syllables (rough estimate) | |
| def count_syllables(word): | |
| word = word.lower().strip('.,!?";:') | |
| vowels = 'aeiouy' | |
| syllable_count = 0 | |
| prev_was_vowel = False | |
| for char in word: | |
| if char in vowels: | |
| if not prev_was_vowel: | |
| syllable_count += 1 | |
| prev_was_vowel = True | |
| else: | |
| prev_was_vowel = False | |
| return max(1, syllable_count) | |
| total_syllables = sum(count_syllables(word) for word in words) | |
| avg_syllables_per_word = total_syllables / total_words if words else 0 | |
| # Determine difficulty level | |
| if avg_words_per_sentence <= 8 and avg_syllables_per_word <= 1.3: | |
| difficulty = "easy" | |
| elif avg_words_per_sentence <= 15 and avg_syllables_per_word <= 1.7: | |
| difficulty = "medium" | |
| else: | |
| difficulty = "hard" | |
| return { | |
| "difficulty_level": difficulty, | |
| "total_words": total_words, | |
| "total_sentences": total_sentences, | |
| "avg_words_per_sentence": round(avg_words_per_sentence, 1), | |
| "avg_chars_per_word": round(avg_chars_per_word, 1), | |
| "avg_syllables_per_word": round(avg_syllables_per_word, 2), | |
| "estimated_reading_time_seconds": total_words * 0.5, # Rough estimate | |
| "suitable_for_techniques": get_suitable_techniques(difficulty, avg_words_per_sentence) | |
| } | |
| def get_suitable_techniques(difficulty: str, avg_words_per_sentence: float) -> List[str]: | |
| """Recommend which speech techniques work well with this reading level""" | |
| techniques = ["normal"] | |
| if difficulty == "easy": | |
| techniques.extend(["gentle_onset", "prolonged_speech", "light_contacts"]) | |
| elif difficulty == "medium": | |
| techniques.extend(["prolonged_speech", "continuous_phonation", "rhythmic"]) | |
| else: # hard | |
| techniques.extend(["continuous_phonation", "rhythmic"]) | |
| if avg_words_per_sentence > 12: | |
| techniques.append("chunking") # For longer sentences | |
| return techniques | |
| # Export the main function for backward compatibility | |
| __all__ = ['generate_reading_passage', 'analyze_reading_difficulty', 'ReadingPracticeGenerator'] |