amauricunha's picture
Update app.py
202e2b1 verified
Raw
History Blame
11.3 kB
#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)