# modules/studentact/claude_recommendations.py import os import anthropic import streamlit as st import logging import time import json from datetime import datetime, timezone # Local imports from ..utils.widget_utils import generate_unique_key from ..database.current_situation_mongo_db import store_current_situation_result logger = logging.getLogger(__name__) # Define text types TEXT_TYPES = { 'es': { 'academic_article': 'artículo académico', 'university_work': 'trabajo universitario', 'general_communication': 'comunicación general' }, 'en': { 'academic_article': 'academic article', 'university_work': 'university work', 'general_communication': 'general communication' }, 'fr': { 'academic_article': 'article académique', 'university_work': 'travail universitaire', 'general_communication': 'communication générale' } } # Cache for recommendations to avoid redundant API calls recommendation_cache = {} def get_recommendation_cache_key(text, metrics, text_type, lang_code): """ Generate a cache key for recommendations. """ # Create a simple hash based on text content and metrics text_hash = hash(text[:1000]) # Only use first 1000 chars for hashing metrics_hash = hash(json.dumps(metrics, sort_keys=True)) return f"{text_hash}_{metrics_hash}_{text_type}_{lang_code}" def format_metrics_for_claude(metrics, lang_code, text_type): """ Format metrics in a way that's readable for Claude """ formatted_metrics = {} for key, value in metrics.items(): if isinstance(value, (int, float)): formatted_metrics[key] = round(value, 2) else: formatted_metrics[key] = value # Add context about what type of text this is text_type_label = TEXT_TYPES.get(lang_code, {}).get(text_type, text_type) formatted_metrics['text_type'] = text_type_label return formatted_metrics def generate_claude_recommendations(text, metrics, text_type, lang_code): """ Generate personalized recommendations using Claude API. """ try: api_key = os.environ.get("ANTHROPIC_API_KEY") if not api_key: logger.error("Claude API key not found in environment variables") return get_fallback_recommendations(lang_code) # Check cache first cache_key = get_recommendation_cache_key(text, metrics, text_type, lang_code) if cache_key in recommendation_cache: logger.info("Using cached recommendations") return recommendation_cache[cache_key] # Format metrics for Claude formatted_metrics = format_metrics_for_claude(metrics, lang_code, text_type) # Determine language for prompt if lang_code == 'es': system_prompt = """Eres un asistente especializado en análisis de textos académicos y comunicación escrita. Tu tarea es analizar el texto del usuario y proporcionar recomendaciones personalizadas. Usa un tono constructivo y específico. Sé claro y directo con tus sugerencias. """ user_prompt = f"""Por favor, analiza este texto de tipo '{formatted_metrics['text_type']}' y proporciona recomendaciones personalizadas para mejorarlo. MÉTRICAS DE ANÁLISIS: {json.dumps(formatted_metrics, indent=2, ensure_ascii=False)} TEXTO A ANALIZAR: {text[:2000]} # Limitamos el texto para evitar exceder tokens Proporciona tu análisis con el siguiente formato: 1. Un resumen breve (2-3 frases) del análisis general 2. 3-4 recomendaciones específicas y accionables (cada una de 1-2 frases) 3. Un ejemplo concreto de mejora tomado del propio texto del usuario 4. Una sugerencia sobre qué herramienta de AIdeaText usar (Análisis Morfosintáctico, Análisis Semántico o Análisis del Discurso) Tu respuesta debe ser concisa y no exceder los 300 palabras.""" else: # Default to English system_prompt = """You are an assistant specialized in analyzing academic texts and written communication. Your task is to analyze the user's text and provide personalized recommendations. Use a constructive and specific tone. Be clear and direct with your suggestions. """ user_prompt = f"""Please analyze this text of type '{formatted_metrics['text_type']}' and provide personalized recommendations to improve it. ANALYSIS METRICS: {json.dumps(formatted_metrics, indent=2, ensure_ascii=False)} TEXT TO ANALYZE: {text[:2000]} # Limiting text to avoid exceeding tokens Provide your analysis with the following format: 1. A brief summary (2-3 sentences) of the general analysis 2. 3-4 specific and actionable recommendations (each 1-2 sentences) 3. A concrete example of improvement taken from the user's own text 4. A suggestion about which AIdeaText tool to use (Morphosyntactic Analysis, Semantic Analysis or Discourse Analysis) Your response should be concise and not exceed 300 words.""" # Initialize Claude client client = anthropic.Anthropic(api_key=api_key) # Call Claude API start_time = time.time() response = client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1024, temperature=0.7, system=system_prompt, messages=[ {"role": "user", "content": user_prompt} ] ) logger.info(f"Claude API call completed in {time.time() - start_time:.2f} seconds") # Extract recommendations recommendations = response.content[0].text # Cache the result recommendation_cache[cache_key] = recommendations return recommendations except Exception as e: logger.error(f"Error generating recommendations with Claude: {str(e)}") return get_fallback_recommendations(lang_code) def get_fallback_recommendations(lang_code): """ Return fallback recommendations if Claude API fails """ if lang_code == 'es': return """ **Análisis General** Tu texto presenta una estructura básica adecuada, pero hay áreas que pueden mejorarse para mayor claridad y cohesión. **Recomendaciones**: - Intenta variar tu vocabulario para evitar repeticiones innecesarias - Considera revisar la longitud de tus oraciones para mantener un mejor ritmo - Asegúrate de establecer conexiones claras entre las ideas principales - Revisa la consistencia en el uso de tiempos verbales **Herramienta recomendada**: Te sugerimos utilizar el Análisis Morfosintáctico para identificar patrones en tu estructura de oraciones. """ else: return """ **General Analysis** Your text presents an adequate basic structure, but there are areas that can be improved for better clarity and cohesion. **Recommendations**: - Try to vary your vocabulary to avoid unnecessary repetition - Consider reviewing the length of your sentences to maintain a better rhythm - Make sure to establish clear connections between main ideas - Check consistency in the use of verb tenses **Recommended tool**: We suggest using Morphosyntactic Analysis to identify patterns in your sentence structure. """ ####################################### def store_recommendations(username, text, metrics, text_type, recommendations): """ Store the recommendations in the database """ try: # Importar la función de almacenamiento de recomendaciones from ..database.claude_recommendations_mongo_db import store_claude_recommendation # Guardar usando la nueva función especializada result = store_claude_recommendation( username=username, text=text, metrics=metrics, text_type=text_type, recommendations=recommendations ) logger.info(f"Recommendations stored successfully: {result}") return result except Exception as e: logger.error(f"Error storing recommendations: {str(e)}") return False ########################################## ########################################## def display_personalized_recommendations(text, metrics, text_type, lang_code, t): """ Display personalized recommendations based on text analysis """ try: # Generate recommendations recommendations = generate_claude_recommendations(text, metrics, text_type, lang_code) # Format and display recommendations in a nice container st.markdown("### 📝 " + t.get('recommendations_title', 'Personalized Recommendations')) with st.container(): st.markdown(f"""
{recommendations}
""", unsafe_allow_html=True) # Add prompt to use assistant st.info("💡 **" + t.get('assistant_prompt', 'For further improvement:') + "** " + t.get('assistant_message', 'Open the virtual assistant (powered by Claude AI) in the upper left corner by clicking the arrow next to the logo.')) # Add save button col1, col2, col3 = st.columns([1,1,1]) with col2: if st.button( t.get('save_button', 'Save Analysis'), key=generate_unique_key("claude_recommendations", "save"), type="primary", use_container_width=True ): if 'username' in st.session_state: success = store_recommendations( st.session_state.username, text, metrics, text_type, recommendations ) if success: st.success(t.get('save_success', 'Analysis saved successfully')) else: st.error(t.get('save_error', 'Error saving analysis')) else: st.error(t.get('login_required', 'Please log in to save analysis')) except Exception as e: logger.error(f"Error displaying recommendations: {str(e)}") st.error(t.get('recommendations_error', 'Error generating recommendations. Please try again later.'))