#app.py import os import io import json import base64 from PIL import Image from flask import Flask, request, jsonify, send_file from gtts import gTTS from groq import Groq import google.generativeai as genai # --- CONFIGURAÇÃO INICIAL --- app = Flask(__name__) # --- CONFIGURAÇÃO DAS APIS LLM --- genai_client = None groq_client = None # 1. Configuração Gemini try: GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") if GEMINI_API_KEY: genai.configure(api_key=GEMINI_API_KEY) genai_client = genai else: print("AVISO: GEMINI_API_KEY não configurada.") except Exception as e: genai_client = None print(f"ERRO ao inicializar o cliente Gemini: {e}.") # 2. Configuração Groq try: GROQ_API_KEY = os.environ.get("GROQ_API_KEY") if GROQ_API_KEY: groq_client = Groq(api_key=GROQ_API_KEY) else: print("AVISO: GROQ_API_KEY não configurada.") except Exception as e: groq_client = None print(f"ERRO ao inicializar o cliente Groq: {e}.") # --- ROTAS PRINCIPAIS --- @app.route('/tts-proxy', methods=['POST']) def tts_proxy(): data = request.get_json() text = data.get('text', '') if not text: return jsonify({"error": "No text provided"}), 400 try: tts = gTTS(text=text, lang='en', tld='co.uk') mp3_fp = io.BytesIO() tts.write_to_fp(mp3_fp) mp3_fp.seek(0) return send_file(mp3_fp, mimetype='audio/mpeg') except Exception as e: return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500 @app.route('/explain-proxy', methods=['POST']) def explain_proxy(): data = request.get_json() model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1) context_focus = data.get('context_focus', 'General/Social') custom_prompt = data.get('custom_prompt', None) word = data.get('word', '').strip() context = data.get('context', '') for_flashcard = data.get('for_flashcard', False) if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client): return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503 system_instruction_base = f"You are a professional English tutor. The user's study focus is '{context_focus}'. All your responses must be in ENGLISH." try: if custom_prompt: activity_text = get_ai_text_response(model_provider, model_name, system_instruction_base, custom_prompt) return jsonify({"explanation": activity_text}) if not word: return jsonify({"error": "No word selected."}), 400 if for_flashcard: schema = {"type": "object", "properties": {"term": {"type": "string"}, "translation": {"type": "string"}, "context_sentence": {"type": "string"}, "gapped_sentence": {"type": "string"}, "definition": {"type": "string"}}, "required": ["term", "translation", "context_sentence", "gapped_sentence", "definition"]} prompt = f"Analyze '{word}' in context: '{context}'. Generate a JSON for a flashcard. The 'gapped_sentence' must replace '{word}' with '______________'. You must strictly follow the JSON schema and provide valid, non-empty values for all fields." return jsonify(get_ai_text_response(model_provider, model_name, system_instruction_base, prompt, json_schema=schema)) else: # Quick translation logic (not currently used in UI, but kept for potential future use) prompt = f"Analyze '{word}' in context: '{context}'. Provide a one-sentence English explanation, then '---', then the Portuguese translation." parts = get_ai_text_response(model_provider, model_name, system_instruction_base, prompt).split('---', 1) return jsonify({"explanation": parts[0].strip(), "translation": parts[1].strip() if len(parts) > 1 else 'N/A'}) except Exception as e: return jsonify({"error": f"AI analysis failed: {e}"}), 500 # --- NOVA ROTA PARA FEEDBACK DE ATIVIDADES --- @app.route('/activity-feedback', methods=['POST']) def activity_feedback(): data = request.get_json() model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1) context_focus = data.get('context_focus', 'General/Social') original_prompt = data.get('original_prompt', '') user_response = data.get('user_response', '') if not original_prompt or not user_response: return jsonify({"error": "Original prompt and user response are required."}), 400 if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client): return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503 system_instruction = ( "You are an expert English teacher providing feedback. " f"The user's study focus is '{context_focus}'. " "Your entire response MUST be in English. " "Provide clear, constructive feedback on the user's writing. " "Point out grammar, spelling, or style errors. " "Offer a corrected or improved version of their text. " "Structure your feedback with markdown for clarity (e.g., using ### Corrected Version)." ) user_prompt = f"The original task was: \"{original_prompt}\"\n\nHere is the user's response:\n---\n{user_response}\n---\nPlease provide your feedback." try: feedback_text = get_ai_text_response(model_provider, model_name, system_instruction, user_prompt) return jsonify({"feedback": feedback_text}) except Exception as e: return jsonify({"error": f"AI feedback failed: {e}"}), 500 @app.route('/analyze-image', methods=['POST']) def analyze_image(): if not genai_client: return jsonify({"error": "GEMINI_API_KEY not configured."}), 503 data = request.get_json() base64_image = data.get('image') model_value = data.get('model', 'gemini:gemini-2.5-flash-latest') model_name = 'gemini-2.5-flash-latest' if model_value.startswith('gemini:'): model_name = model_value.split(':', 1)[1] if not base64_image: return jsonify({"error": "No image data."}), 400 try: image = Image.open(io.BytesIO(base64.b64decode(base64_image.split(',')[1]))) model = genai_client.GenerativeModel(model_name) schema = { "type": "object", "properties": { "vocabulary": { "type": "array", "items": { "type": "object", "properties": { "term": {"type": "string"}, "definition": {"type": "string"} }, "required": ["term", "definition"] } } }, "required": ["vocabulary"] } prompt = [ "Act as an English teacher. Identify 5-7 key objects/concepts in this image. For each, provide its English name and a simple definition. Return a single JSON object conforming to the schema.", image ] response = model.generate_content(prompt, generation_config={"response_mime_type": "application/json", "response_schema": schema}) return jsonify(json.loads(response.text)['vocabulary']) except Exception as e: return jsonify({"error": f"Image analysis failed: {e}"}), 500 @app.route('/chat-with-ai', methods=['POST']) def chat_with_ai(): if not groq_client: return jsonify({"error": "GROQ_API_KEY not configured."}), 503 data = request.get_json() history, user_message = data.get('history', []), data.get('message', '') if not user_message: return jsonify({"error": "No message."}), 400 try: system = "You are 'Groq Chat', a friendly English tutor. Keep responses concise (1-2 sentences). If the user makes a grammar mistake, gently correct it. Ask questions to keep the conversation flowing. Always respond in English." messages = [{"role": "system", "content": system}] + history + [{"role": "user", "content": user_message}] response = groq_client.chat.completions.create(model="llama-3.1-8b-instant", messages=messages, temperature=0.7) return jsonify({"response": response.choices[0].message.content.strip()}) except Exception as e: return jsonify({"error": f"AI chat failed: {e}"}), 500 @app.route('/pronunciation-feedback', methods=['POST']) def pronunciation_feedback(): if not groq_client: return jsonify({"error": "GROQ_API_KEY not configured."}), 503 data = request.get_json() target_text, user_text = data.get('target_text'), data.get('user_text') if not target_text or not user_text: return jsonify({"error": "Required data missing."}), 400 try: system_instruction = "You are an expert American English pronunciation coach. The user tried to say a target sentence, and their speech was transcribed. Based on the likely pronunciation differences, provide brief, friendly, and actionable feedback in Portuguese. Focus on 1-2 key points. If it's very close, praise the user." user_prompt = f"Target: \"{target_text}\"\nTranscription: \"{user_text}\"\n\nProvide pronunciation feedback." messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": user_prompt}] response = groq_client.chat.completions.create(model="llama-3.1-8b-instant", messages=messages, temperature=0.5) return jsonify({"feedback": response.choices[0].message.content.strip()}) except Exception as e: return jsonify({"error": f"Pronunciation analysis failed: {e}"}), 500 @app.route('/generate-image', methods=['POST']) def generate_image(): if not genai_client: return jsonify({"error": "GEMINI_API_KEY not configured."}), 503 data = request.get_json() prompt = data.get('prompt') if not prompt: return jsonify({"error": "Image prompt is required."}), 400 try: model = genai_client.GenerativeModel(model_name='gemini-2.5-flash-image-preview') response = model.generate_content(prompt) base64_image_data = response.parts[0].inline_data.data return jsonify({"image_base64": base64_image_data}) except Exception as e: return jsonify({"error": f"Image generation failed: {e}"}), 500 # --- FUNÇÃO AUXILIAR E ROTA RAIZ --- def get_ai_text_response(provider, model_name, system_instruction, user_prompt, json_schema=None): if provider == 'gemini': model = genai_client.GenerativeModel(model_name, system_instruction=system_instruction) config = {} if json_schema: config = {"response_mime_type": "application/json", "response_schema": json_schema} response = model.generate_content(user_prompt, generation_config=config) return json.loads(response.text) if json_schema else response.text.strip() elif provider == 'groq': messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": user_prompt}] config = {'response_format': {"type": "json_object"}} if json_schema else {} response = groq_client.chat.completions.create(model=model_name, messages=messages, **config) return json.loads(response.choices[0].message.content) if json_schema else response.choices[0].message.content.strip() raise Exception(f"Unsupported provider: {provider}") @app.route('/') def root(): return send_file('index.html') if __name__ == '__main__': app.run(host='0.0.0.0', port=7860)