amauricunha commited on
Commit
378b3e3
·
verified ·
1 Parent(s): 8d574fd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +69 -101
app.py CHANGED
@@ -44,13 +44,12 @@ except Exception as e:
44
  # --- ROTA 1: TEXT-TO-SPEECH (TTS) ---
45
  @app.route('/tts-proxy', methods=['POST'])
46
  def tts_proxy():
47
- # ... (código existente sem alterações) ...
48
  data = request.get_json()
49
  text = data.get('text', '')
50
  if not text:
51
  return jsonify({"error": "No text provided"}), 400
52
  try:
53
- tts = gTTS(text=text, lang='en', tld='us')
54
  mp3_fp = io.BytesIO()
55
  tts.write_to_fp(mp3_fp)
56
  mp3_fp.seek(0)
@@ -66,144 +65,113 @@ def explain_proxy():
66
  # ... (código existente sem alterações) ...
67
  data = request.get_json()
68
  model_provider, model_name = data.get('model', 'gemini:gemini-1.5-flash-latest').split(':', 1)
69
- word = data.get('word', '').strip()
70
- context = data.get('context', '')
71
- for_flashcard = data.get('for_flashcard', False)
72
- context_focus = data.get('context_focus', 'General/Social')
73
  custom_prompt = data.get('custom_prompt', None)
74
 
75
- if (model_provider == 'gemini' and not genai_client) or \
76
- (model_provider == 'groq' and not groq_client):
77
  return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503
78
-
79
- system_instruction_base = f"You are a professional English tutor focused on the context '{context_focus}'. All responses must be accurate and relevant to language learning."
80
-
81
  try:
82
  if custom_prompt:
83
- text_response = get_ai_text_response(model_provider, model_name, system_instruction_base, custom_prompt)
84
- return jsonify({"explanation": text_response, "translation": "Activity Generated."})
85
-
86
- if not word:
87
- return jsonify({"error": "No word or phrase selected."}), 400
88
-
89
  if for_flashcard:
90
- flashcard_schema = {
91
- "type": "object", "properties": {
92
- "term": {"type": "string"}, "translation": {"type": "string"},
93
- "context_sentence": {"type": "string"}, "gapped_sentence": {"type": "string"},
94
- "definition": {"type": "string"},
95
- }, "required": ["term", "translation", "context_sentence", "gapped_sentence", "definition"]
96
- }
97
- system_instruction = system_instruction_base + " Your task is to generate a JSON object for an 'intelligent flashcard'."
98
- user_prompt = f"Analyze the term '{word}' in the sentence: '{context}'. Generate a JSON object following the schema. The 'gapped_sentence' must replace '{word}' with '______________'."
99
- card_data = get_ai_text_response(model_provider, model_name, system_instruction, user_prompt, json_schema=flashcard_schema)
100
- return jsonify(card_data)
101
-
102
  else:
103
- system_instruction = system_instruction_base
104
- user_prompt = f"Analyze '{word}' in context: '{context}'. Provide a one-sentence English explanation, then '---', then the Portuguese translation."
105
- text_response = get_ai_text_response(model_provider, model_name, system_instruction, user_prompt)
106
- parts = text_response.split('---', 1)
107
- explanation = parts[0].strip()
108
- translation = parts[1].strip() if len(parts) > 1 else 'Tradução não disponível.'
109
- return jsonify({"explanation": explanation, "translation": translation})
110
-
111
  except Exception as e:
112
- print(f"Erro na análise de IA: {e}")
113
  return jsonify({"error": f"AI analysis failed: {e}"}), 500
114
 
115
  # --- ROTA 3: ANÁLISE DE IMAGEM ---
116
  @app.route('/analyze-image', methods=['POST'])
117
  def analyze_image():
118
  # ... (código existente sem alterações) ...
119
- if not genai_client:
120
- return jsonify({"error": "GEMINI_API_KEY not configured for image analysis."}), 503
121
-
122
- data = request.get_json()
123
- base64_image = data.get('image')
124
- if not base64_image:
125
- return jsonify({"error": "No image data provided."}), 400
126
-
127
  try:
128
- image_data = base64.b64decode(base64_image.split(',')[1])
129
- image = Image.open(io.BytesIO(image_data))
130
  model = genai_client.GenerativeModel('gemini-1.5-flash-latest')
131
-
132
- vocabulary_schema = { "type": "object", "properties": { "vocabulary": { "type": "array", "items": { "type": "object", "properties": { "term": {"type": "string"}, "definition": {"type": "string"} }, "required": ["term", "definition"] } } }, "required": ["vocabulary"] }
133
- prompt = [ "You are an English teacher. Look at this image and identify 5 to 7 key objects or concepts. For each item, provide its English name and a simple one-sentence definition. Return the result as a single JSON object that conforms to the provided schema.", image ]
134
-
135
- response = model.generate_content( prompt, generation_config={ "response_mime_type": "application/json", "response_schema": vocabulary_schema } )
136
- json_response = json.loads(response.text)
137
- return jsonify(json_response['vocabulary'])
138
-
139
  except Exception as e:
140
- print(f"Erro na análise de imagem: {e}")
141
  return jsonify({"error": f"Image analysis failed: {e}"}), 500
142
 
143
-
144
- # --- ROTA 4: CHAT COM IA (NOVO - IDEAL PARA GROQ) ---
145
  @app.route('/chat-with-ai', methods=['POST'])
146
  def chat_with_ai():
147
- if not groq_client:
148
- return jsonify({"error": "GROQ_API_KEY not configured for the chat feature."}), 503
149
-
150
  data = request.get_json()
151
- history = data.get('history', [])
152
- user_message = data.get('message', '')
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- if not user_message:
155
- return jsonify({"error": "No message provided."}), 400
 
156
 
 
 
 
157
  try:
158
- # Instrução de sistema para o tutor de IA
159
- system_instruction = "You are a friendly and encouraging English tutor named 'Groq Chat'. Your goal is to help the user practice their English conversation skills. Keep your responses concise (1-2 sentences). If the user makes a grammar mistake, gently correct it and explain briefly. Ask questions to keep the conversation flowing. Always respond in English."
160
-
161
- # Monta o histórico de mensagens para a API
162
- messages = [{"role": "system", "content": system_instruction}]
163
- messages.extend(history)
164
- messages.append({"role": "user", "content": user_message})
 
165
 
166
- # Chama a API da Groq
167
  response = groq_client.chat.completions.create(
168
- # Usando um modelo rápido, ideal para chat
169
- model="llama-3.1-8b-instant",
170
  messages=messages,
171
- temperature=0.7
172
  )
173
 
174
- ai_response = response.choices[0].message.content.strip()
175
- return jsonify({"response": ai_response})
176
-
177
  except Exception as e:
178
- print(f"Erro no chat com IA: {e}")
179
- return jsonify({"error": f"AI chat failed: {e}"}), 500
180
 
181
-
182
- # --- FUNÇÃO AUXILIAR PARA CHAMADAS DE IA (APENAS TEXTO) ---
183
  def get_ai_text_response(provider, model_name, system_instruction, user_prompt, json_schema=None):
184
  # ... (código existente sem alterações) ...
185
  if provider == 'gemini':
186
- if not genai_client:
187
- raise Exception("Gemini client not initialized.")
188
  model = genai_client.GenerativeModel(model_name)
189
- generation_config = {"response_mime_type": "application/json", "response_schema": json_schema} if json_schema else None
190
- response = model.generate_content(user_prompt, generation_config=generation_config)
191
- return json.loads(response.candidates[0].content.parts[0].text.strip()) if json_schema else response.text.strip()
192
-
193
  elif provider == 'groq':
194
- if not groq_client:
195
- raise Exception("Groq client not initialized.")
196
  messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": user_prompt}]
197
- config_params = {'response_format': {"type": "json_object"}} if json_schema else {}
198
- response = groq_client.chat.completions.create(model=model_name, messages=messages, **config_params)
199
- text_response = response.choices[0].message.content.strip()
200
- return json.loads(text_response) if json_schema else text_response
201
-
202
- else:
203
- raise Exception(f"Unsupported provider: {provider}")
204
-
205
 
206
- # --- ROTA DE INICIALIZAÇÃO ---
207
  @app.route('/')
208
  def root():
209
  return send_file('index.html')
 
44
  # --- ROTA 1: TEXT-TO-SPEECH (TTS) ---
45
  @app.route('/tts-proxy', methods=['POST'])
46
  def tts_proxy():
 
47
  data = request.get_json()
48
  text = data.get('text', '')
49
  if not text:
50
  return jsonify({"error": "No text provided"}), 400
51
  try:
52
+ tts = gTTS(text=text, lang='en', tld='co.uk') # Sotaque britânico para variedade
53
  mp3_fp = io.BytesIO()
54
  tts.write_to_fp(mp3_fp)
55
  mp3_fp.seek(0)
 
65
  # ... (código existente sem alterações) ...
66
  data = request.get_json()
67
  model_provider, model_name = data.get('model', 'gemini:gemini-1.5-flash-latest').split(':', 1)
68
+ word, context = data.get('word', '').strip(), data.get('context', '')
69
+ for_flashcard, context_focus = data.get('for_flashcard', False), data.get('context_focus', 'General/Social')
 
 
70
  custom_prompt = data.get('custom_prompt', None)
71
 
72
+ if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client):
 
73
  return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503
74
+
75
+ system_instruction_base = f"You are a professional English tutor focused on the context '{context_focus}'."
 
76
  try:
77
  if custom_prompt:
78
+ return jsonify({"explanation": get_ai_text_response(model_provider, model_name, system_instruction_base, custom_prompt)})
79
+ if not word: return jsonify({"error": "No word selected."}), 400
 
 
 
 
80
  if for_flashcard:
81
+ 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"]}
82
+ prompt = f"Analyze '{word}' in context: '{context}'. Generate a JSON for a flashcard. The 'gapped_sentence' must replace '{word}' with '______________'."
83
+ return jsonify(get_ai_text_response(model_provider, model_name, system_instruction_base, prompt, json_schema=schema))
 
 
 
 
 
 
 
 
 
84
  else:
85
+ prompt = f"Analyze '{word}' in context: '{context}'. Provide a one-sentence English explanation, then '---', then the Portuguese translation."
86
+ parts = get_ai_text_response(model_provider, model_name, system_instruction_base, prompt).split('---', 1)
87
+ return jsonify({"explanation": parts[0].strip(), "translation": parts[1].strip() if len(parts) > 1 else 'N/A'})
 
 
 
 
 
88
  except Exception as e:
 
89
  return jsonify({"error": f"AI analysis failed: {e}"}), 500
90
 
91
  # --- ROTA 3: ANÁLISE DE IMAGEM ---
92
  @app.route('/analyze-image', methods=['POST'])
93
  def analyze_image():
94
  # ... (código existente sem alterações) ...
95
+ if not genai_client: return jsonify({"error": "GEMINI_API_KEY not configured."}), 503
96
+ data, base64_image = request.get_json(), data.get('image')
97
+ if not base64_image: return jsonify({"error": "No image data."}), 400
 
 
 
 
 
98
  try:
99
+ image = Image.open(io.BytesIO(base64.b64decode(base64_image.split(',')[1])))
 
100
  model = genai_client.GenerativeModel('gemini-1.5-flash-latest')
101
+ schema = { "type": "object", "properties": { "vocabulary": { "type": "array", "items": { "type": "object", "properties": { "term": {"type": "string"}, "definition": {"type": "string"} }, "required": ["term", "definition"] } } }, "required": ["vocabulary"] }
102
+ prompt = [ "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 ]
103
+ response = model.generate_content(prompt, generation_config={"response_mime_type": "application/json", "response_schema": schema})
104
+ return jsonify(json.loads(response.text)['vocabulary'])
 
 
 
 
105
  except Exception as e:
 
106
  return jsonify({"error": f"Image analysis failed: {e}"}), 500
107
 
108
+ # --- ROTA 4: CHAT COM IA ---
 
109
  @app.route('/chat-with-ai', methods=['POST'])
110
  def chat_with_ai():
111
+ # ... (código existente sem alterações) ...
112
+ if not groq_client: return jsonify({"error": "GROQ_API_KEY not configured."}), 503
 
113
  data = request.get_json()
114
+ history, user_message = data.get('history', []), data.get('message', '')
115
+ if not user_message: return jsonify({"error": "No message."}), 400
116
+ try:
117
+ 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."
118
+ messages = [{"role": "system", "content": system}] + history + [{"role": "user", "content": user_message}]
119
+ response = groq_client.chat.completions.create(model="llama-3.1-8b-instant", messages=messages, temperature=0.7)
120
+ return jsonify({"response": response.choices[0].message.content.strip()})
121
+ except Exception as e:
122
+ return jsonify({"error": f"AI chat failed: {e}"}), 500
123
+
124
+ # --- ROTA 5: ANÁLISE DE PRONÚNCIA (NOVO) ---
125
+ @app.route('/pronunciation-feedback', methods=['POST'])
126
+ def pronunciation_feedback():
127
+ if not groq_client:
128
+ return jsonify({"error": "GROQ_API_KEY not configured for pronunciation analysis."}), 503
129
 
130
+ data = request.get_json()
131
+ target_text = data.get('target_text')
132
+ user_text = data.get('user_text') # Texto transcrito do áudio do usuário
133
 
134
+ if not target_text or not user_text:
135
+ return jsonify({"error": "Target and user text are required."}), 400
136
+
137
  try:
138
+ 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 between the target and the transcription, provide brief, friendly, and actionable feedback in Portuguese. Focus on 1-2 key points. If the transcription is very close to the target, praise the user."
139
+
140
+ user_prompt = f"Frase-alvo: \"{target_text}\"\nTranscrição do usuário: \"{user_text}\"\n\nPor favor, forneça o feedback de pronúncia."
141
+
142
+ messages = [
143
+ {"role": "system", "content": system_instruction},
144
+ {"role": "user", "content": user_prompt}
145
+ ]
146
 
 
147
  response = groq_client.chat.completions.create(
148
+ model="llama-3.1-8b-instant", # Modelo rápido para feedback instantâneo
 
149
  messages=messages,
150
+ temperature=0.5
151
  )
152
 
153
+ feedback = response.choices[0].message.content.strip()
154
+ return jsonify({"feedback": feedback})
155
+
156
  except Exception as e:
157
+ print(f"Erro na análise de pronúncia: {e}")
158
+ return jsonify({"error": f"Pronunciation analysis failed: {e}"}), 500
159
 
160
+ # --- FUNÇÃO AUXILIAR E ROTA RAIZ ---
 
161
  def get_ai_text_response(provider, model_name, system_instruction, user_prompt, json_schema=None):
162
  # ... (código existente sem alterações) ...
163
  if provider == 'gemini':
 
 
164
  model = genai_client.GenerativeModel(model_name)
165
+ config = {"response_mime_type": "application/json", "response_schema": json_schema} if json_schema else None
166
+ response = model.generate_content(user_prompt, generation_config=config)
167
+ return json.loads(response.text) if json_schema else response.text
 
168
  elif provider == 'groq':
 
 
169
  messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": user_prompt}]
170
+ config = {'response_format': {"type": "json_object"}} if json_schema else {}
171
+ response = groq_client.chat.completions.create(model=model_name, messages=messages, **config)
172
+ return json.loads(response.choices[0].message.content) if json_schema else response.choices[0].message.content
173
+ raise Exception(f"Unsupported provider: {provider}")
 
 
 
 
174
 
 
175
  @app.route('/')
176
  def root():
177
  return send_file('index.html')