amauricunha commited on
Commit
194f0cb
·
verified ·
1 Parent(s): ce218e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +131 -54
app.py CHANGED
@@ -7,6 +7,8 @@ import requests
7
 
8
  from flask import Flask, request, jsonify, send_file
9
  from gtts import gTTS
 
 
10
  from google import genai
11
  from google.genai import types
12
 
@@ -15,22 +17,29 @@ from google.genai import types
15
  # Inicializa o Flask
16
  app = Flask(__name__)
17
 
18
- # --- CONFIGURAÇÃO DA API GEMINI ---
19
- # Tenta carregar a chave API da Gemini das variáveis de ambiente (Secrets do Hugging Face).
20
- # Se a chave não for encontrada, o cliente Gemini não será inicializado e o endpoint de tradução falhará.
 
 
21
  try:
22
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
23
  if GEMINI_API_KEY:
24
  genai_client = genai.Client(api_key=GEMINI_API_KEY)
25
  else:
26
- genai_client = None
27
- print("AVISO: GEMINI_API_KEY não configurada. A funcionalidade de Tradução/Contexto (clique duplo) será desativada.")
28
  except Exception as e:
29
- genai_client = None
30
- print(f"ERRO ao inicializar o cliente Gemini: {e}. A funcionalidade de IA será desativada.")
31
 
32
- # Modelo para contextualização de texto e tradução
33
- GEMINI_MODEL = "gemini-2.5-flash"
 
 
 
 
 
 
 
34
 
35
 
36
  # --- ROTA 1: TEXT-TO-SPEECH (TTS) - USANDO gTTS GRATUITO ---
@@ -47,14 +56,11 @@ def tts_proxy():
47
  if not text:
48
  return jsonify({"error": "No text provided"}), 400
49
 
50
- # O gTTS não suporta o controle de velocidade granular via API, mas é o melhor
51
- # compromisso para um serviço gratuito.
52
-
53
  try:
54
- # Cria um objeto gTTS
55
  tts = gTTS(text=text, lang='en', tld='us') # TLD 'us' para sotaque Americano
56
 
57
- # Salva o áudio em um buffer de bytes na memória (Melhor que salvar no disco)
58
  mp3_fp = io.BytesIO()
59
  tts.write_to_fp(mp3_fp)
60
  mp3_fp.seek(0)
@@ -72,73 +78,144 @@ def tts_proxy():
72
  return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500
73
 
74
 
75
- # --- ROTA 2: TRADUÇÃO RÁPIDA E CONTEXTO (IA) - USANDO GEMINI (REQUER CHAVE) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  @app.route('/explain-proxy', methods=['POST'])
78
  def explain_proxy():
79
  """
80
- Usa o modelo Gemini para fornecer explicação em inglês e tradução em português
81
- para uma palavra/frase em contexto.
82
- Pode retornar em JSON simples para o modal ou em JSON estruturado para flashcards.
83
  """
84
  data = request.get_json()
85
  word = data.get('word', '').strip()
86
  context = data.get('context', '')
87
- for_flashcard = data.get('for_flashcard', False) # True se for para criar um flashcard
 
 
88
 
89
- if not genai_client:
90
- return jsonify({"error": "GEMINI_API_KEY not configured. Cannot perform AI analysis."}), 503
91
-
92
  if not word:
93
  return jsonify({"error": "No word or phrase selected."}), 400
94
 
95
- # Define o System Instruction base
96
- system_instruction_base = f"You are a professional English tutor and linguist. Your task is to analyze the user-provided English word or phrase: '{word}' in its context: '{context}'. All responses must be highly accurate and relevant to language learning. Only output the final JSON object."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  try:
99
  if for_flashcard:
100
  # 1. Requisição para Flashcard (JSON Estruturado)
101
  system_instruction = system_instruction_base + " You must generate a single, highly relevant example sentence using the word/phrase, and provide a clear, simple English definition. Translate the word/phrase accurately to Portuguese. The output MUST be a JSON object conforming to the provided schema."
102
 
103
- response = genai_client.models.generate_content(
104
- model=GEMINI_MODEL,
105
- contents=[system_instruction],
106
- config=types.GenerateContentConfig(
107
- response_mime_type="application/json",
108
- response_schema={
109
- "type": "OBJECT",
110
- "properties": {
111
- "term": {"type": "STRING", "description": "The selected word or phrase."},
112
- "explanation": {"type": "STRING", "description": "A clear English definition or explanation."},
113
- "translation": {"type": "STRING", "description": "The accurate Portuguese translation of the term/phrase."},
114
- "example": {"type": "STRING", "description": "A new, helpful example sentence using the term/phrase."},
115
- },
116
- "required": ["term", "explanation", "translation", "example"]
117
- }
118
- )
119
  )
120
 
121
- # O retorno do Gemini é uma string JSON, que precisamos analisar
122
- if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
123
- json_text = response.candidates[0].content.parts[0].text.strip()
124
- # O cliente no frontend espera "term", "translation", "example"
125
- # O campo "explanation" é a definição em inglês.
126
- card_data = json.loads(json_text)
127
  return jsonify(card_data)
128
  else:
129
- raise Exception("AI returned empty or invalid content for flashcard.")
130
 
131
  else:
132
  # 2. Requisição para Modal (Explicação e Tradução Simples)
133
- prompt = f"For the term '{word}' in the context: '{context}', provide a clear, one-sentence English explanation/definition, followed by an exact Portuguese translation, separated by a distinct marker like '---'.\nExample Format:\nDefinition in English.\n---\nTradução em Português."
134
 
135
- response = genai_client.models.generate_content(
136
- model=GEMINI_MODEL,
137
- contents=[prompt],
138
- config=types.GenerateContentConfig()
 
139
  )
140
-
141
- text_response = response.text.strip()
142
  parts = text_response.split('---', 1)
143
 
144
  explanation = parts[0].strip()
 
7
 
8
  from flask import Flask, request, jsonify, send_file
9
  from gtts import gTTS
10
+ # Importa Groq e Google GenAI
11
+ from groq import Groq
12
  from google import genai
13
  from google.genai import types
14
 
 
17
  # Inicializa o Flask
18
  app = Flask(__name__)
19
 
20
+ # --- CONFIGURAÇÃO DAS APIS LLM ---
21
+ genai_client = None
22
+ groq_client = None
23
+
24
+ # 1. Configuração Gemini
25
  try:
26
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
27
  if GEMINI_API_KEY:
28
  genai_client = genai.Client(api_key=GEMINI_API_KEY)
29
  else:
30
+ print("AVISO: GEMINI_API_KEY não configurada. Gemini estará desativado para análise.")
 
31
  except Exception as e:
32
+ print(f"ERRO ao inicializar o cliente Gemini: {e}. Funcionalidade de IA desativada.")
 
33
 
34
+ # 2. Configuração Groq
35
+ try:
36
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
37
+ if GROQ_API_KEY:
38
+ groq_client = Groq(api_key=GROQ_API_KEY)
39
+ else:
40
+ print("AVISO: GROQ_API_KEY não configurada. Groq estará desativado.")
41
+ except Exception as e:
42
+ print(f"ERRO ao inicializar o cliente Groq: {e}. Funcionalidade de IA desativada.")
43
 
44
 
45
  # --- ROTA 1: TEXT-TO-SPEECH (TTS) - USANDO gTTS GRATUITO ---
 
56
  if not text:
57
  return jsonify({"error": "No text provided"}), 400
58
 
 
 
 
59
  try:
60
+ # Cria um objeto gTTS (sem controle de velocidade)
61
  tts = gTTS(text=text, lang='en', tld='us') # TLD 'us' para sotaque Americano
62
 
63
+ # Salva o áudio em um buffer de bytes na memória
64
  mp3_fp = io.BytesIO()
65
  tts.write_to_fp(mp3_fp)
66
  mp3_fp.seek(0)
 
78
  return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500
79
 
80
 
81
+ # --- ROTA 2: TRADUÇÃO RÁPIDA E CONTEXTO (IA) - USANDO GEMINI OU GROQ ---
82
+
83
+ def get_ai_response(provider, model_name, system_instruction, json_schema=None):
84
+ """Função auxiliar para rotear a requisição LLM para o provedor selecionado."""
85
+
86
+ if provider == 'gemini':
87
+ if not genai_client:
88
+ raise Exception("Gemini client not initialized. GEMINI_API_KEY is missing.")
89
+
90
+ # O Gemini usa um prompt simples com schema
91
+ contents=[system_instruction]
92
+
93
+ # Configuração para resposta JSON estruturada (usada para flashcards)
94
+ config = types.GenerateContentConfig()
95
+ if json_schema:
96
+ config = types.GenerateContentConfig(
97
+ response_mime_type="application/json",
98
+ response_schema=json_schema
99
+ )
100
+
101
+ response = genai_client.models.generate_content(
102
+ model=model_name,
103
+ contents=contents,
104
+ config=config
105
+ )
106
+
107
+ if json_schema:
108
+ # Retorna o JSON parsado
109
+ if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
110
+ json_text = response.candidates[0].content.parts[0].text.strip()
111
+ return json.loads(json_text)
112
+ else:
113
+ raise Exception("Gemini returned empty or invalid JSON content.")
114
+ else:
115
+ # Retorna o texto simples
116
+ return response.text.strip()
117
+
118
+ elif provider == 'groq':
119
+ if not groq_client:
120
+ raise Exception("Groq client not initialized. GROQ_API_KEY is missing.")
121
+
122
+ # Para Groq, a instrução do sistema é o conteúdo
123
+ messages = [
124
+ {"role": "system", "content": system_instruction}
125
+ ]
126
+
127
+ # Configuração para resposta JSON
128
+ config_params = {}
129
+ if json_schema:
130
+ config_params['response_format'] = {"type": "json_object"}
131
+
132
+ response = groq_client.chat.completions.create(
133
+ model=model_name,
134
+ messages=messages,
135
+ **config_params
136
+ )
137
+
138
+ text_response = response.choices[0].message.content.strip()
139
+
140
+ if json_schema:
141
+ # Retorna o JSON parsado
142
+ return json.loads(text_response)
143
+ else:
144
+ # Retorna o texto simples
145
+ return text_response
146
+
147
+ else:
148
+ raise Exception(f"Unsupported provider: {provider}")
149
+
150
 
151
  @app.route('/explain-proxy', methods=['POST'])
152
  def explain_proxy():
153
  """
154
+ Usa o modelo LLM selecionado para fornecer explicação e tradução.
 
 
155
  """
156
  data = request.get_json()
157
  word = data.get('word', '').strip()
158
  context = data.get('context', '')
159
+ for_flashcard = data.get('for_flashcard', False)
160
+ # Novo: Captura o provedor e o modelo selecionados
161
+ model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash').split(':', 1)
162
 
 
 
 
163
  if not word:
164
  return jsonify({"error": "No word or phrase selected."}), 400
165
 
166
+ if model_provider == 'gemini' and not genai_client:
167
+ return jsonify({"error": "GEMINI_API_KEY not configured. Cannot perform AI analysis."}), 503
168
+ if model_provider == 'groq' and not groq_client:
169
+ return jsonify({"error": "GROQ_API_KEY not configured. Cannot perform AI analysis."}), 503
170
+
171
+
172
+ system_instruction_base = f"You are a professional English tutor and linguist. Your task is to analyze the user-provided English word or phrase: '{word}' in its context: '{context}'. All responses must be highly accurate and relevant to language learning."
173
+
174
+ # Define o schema JSON para Flashcards
175
+ flashcard_schema = {
176
+ "type": "OBJECT",
177
+ "properties": {
178
+ "term": {"type": "STRING", "description": "The selected word or phrase."},
179
+ "explanation": {"type": "STRING", "description": "A clear English definition or explanation."},
180
+ "translation": {"type": "STRING", "description": "The accurate Portuguese translation of the term/phrase."},
181
+ "example": {"type": "STRING", "description": "A new, helpful example sentence using the term/phrase."},
182
+ },
183
+ "required": ["term", "explanation", "translation", "example"]
184
+ }
185
 
186
  try:
187
  if for_flashcard:
188
  # 1. Requisição para Flashcard (JSON Estruturado)
189
  system_instruction = system_instruction_base + " You must generate a single, highly relevant example sentence using the word/phrase, and provide a clear, simple English definition. Translate the word/phrase accurately to Portuguese. The output MUST be a JSON object conforming to the provided schema."
190
 
191
+ # Para Groq, adicionamos a descrição do schema no prompt
192
+ if model_provider == 'groq':
193
+ system_instruction += f" The JSON object must follow this structure: {json.dumps(flashcard_schema)}"
194
+
195
+ card_data = get_ai_response(
196
+ model_provider,
197
+ model_name,
198
+ system_instruction,
199
+ json_schema=flashcard_schema if model_provider == 'gemini' else flashcard_schema # Schema passado para Gemini, ou estrutura para Groq
 
 
 
 
 
 
 
200
  )
201
 
202
+ # Verifica se os campos essenciais estão presentes no retorno do LLM
203
+ if card_data and card_data.get('term') and card_data.get('translation') and card_data.get('example'):
 
 
 
 
204
  return jsonify(card_data)
205
  else:
206
+ raise Exception("AI returned empty or invalid structured data.")
207
 
208
  else:
209
  # 2. Requisição para Modal (Explicação e Tradução Simples)
210
+ prompt = system_instruction_base + f" Provide a clear, one-sentence English explanation/definition, followed by an exact Portuguese translation, separated by a distinct marker like '---'.\nExample Format:\nDefinition in English.\n---\nTradução em Português."
211
 
212
+ text_response = get_ai_response(
213
+ model_provider,
214
+ model_name,
215
+ prompt,
216
+ json_schema=None # Não é JSON estruturado
217
  )
218
+
 
219
  parts = text_response.split('---', 1)
220
 
221
  explanation = parts[0].strip()