amauricunha commited on
Commit
81e20b3
·
verified ·
1 Parent(s): 9f4bb94

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -98
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import os
2
  import io
3
  import json
@@ -23,9 +24,12 @@ 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:
@@ -82,41 +86,35 @@ def tts_proxy():
82
 
83
  # --- ROTA 2: TRADUÇÃO RÁPIDA E CONTEXTO (IA) - USANDO GEMINI OU GROQ ---
84
 
85
- def get_ai_response(provider, model_name, system_instruction, custom_prompt=None, json_schema=None):
86
  """Função auxiliar para rotear a requisição LLM para o provedor selecionado."""
87
 
88
- # O prompt base é o system_instruction, a menos que custom_prompt seja fornecido
89
- prompt_to_use = custom_prompt if custom_prompt else system_instruction
90
-
91
  if provider == 'gemini':
92
  if not genai_client:
93
  raise Exception("Gemini client not initialized. GEMINI_API_KEY is missing.")
94
 
95
- contents=[prompt_to_use]
96
 
97
  # Configuração para resposta JSON estruturada
98
- config = types.GenerateContentConfig()
99
  if json_schema:
100
- config = types.GenerateContentConfig(
101
  response_mime_type="application/json",
102
  response_schema=json_schema
103
  )
104
 
105
- response = genai_client.models.generate_content(
106
- model=model_name,
107
- contents=contents,
108
- config=config
109
  )
110
 
111
  if json_schema:
112
- # Retorna o JSON parsado
113
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
114
  json_text = response.candidates[0].content.parts[0].text.strip()
115
  return json.loads(json_text)
116
  else:
117
  raise Exception("Gemini returned empty or invalid JSON content.")
118
  else:
119
- # Retorna o texto simples
120
  return response.text.strip()
121
 
122
  elif provider == 'groq':
@@ -125,10 +123,9 @@ def get_ai_response(provider, model_name, system_instruction, custom_prompt=None
125
 
126
  messages = [
127
  {"role": "system", "content": system_instruction},
128
- {"role": "user", "content": prompt_to_use}
129
  ]
130
 
131
- # Configuração para resposta JSON
132
  config_params = {}
133
  if json_schema:
134
  config_params['response_format'] = {"type": "json_object"}
@@ -138,14 +135,11 @@ def get_ai_response(provider, model_name, system_instruction, custom_prompt=None
138
  messages=messages,
139
  **config_params
140
  )
141
-
142
  text_response = response.choices[0].message.content.strip()
143
 
144
  if json_schema:
145
- # Retorna o JSON parsado
146
  return json.loads(text_response)
147
  else:
148
- # Retorna o texto simples
149
  return text_response
150
 
151
  else:
@@ -154,134 +148,98 @@ def get_ai_response(provider, model_name, system_instruction, custom_prompt=None
154
 
155
  @app.route('/explain-proxy', methods=['POST'])
156
  def explain_proxy():
157
- """
158
- Usa o modelo LLM selecionado para fornecer explicação, tradução, ou gerar atividades.
159
- """
160
  data = request.get_json()
161
  word = data.get('word', '').strip()
162
- context = data.get('context', '') # Contexto local da frase
163
  for_flashcard = data.get('for_flashcard', False)
164
- # Novos parâmetros do frontend
165
- model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash').split(':', 1)
166
- context_focus = data.get('context_focus', 'General/Social') # Novo: contexto profissional/social
167
- custom_prompt = data.get('custom_prompt', None) # Novo: prompt para geração de atividades
168
 
169
- # 1. Checagem de disponibilidade e chaves
170
  if model_provider == 'gemini' and not genai_client:
171
- return jsonify({"error": "GEMINI_API_KEY not configured. Cannot perform AI analysis."}), 503
172
  if model_provider == 'groq' and not groq_client:
173
- return jsonify({"error": "GROQ_API_KEY not configured. Cannot perform AI analysis."}), 503
174
 
175
- # Define a instrução do sistema base para todas as tarefas de IA
176
- system_instruction_base = f"You are a professional English tutor and linguist focused on the context '{context_focus}'. All responses must be highly accurate and relevant to language learning."
177
 
178
- # Se for uma requisição de Geração de Atividade (custom_prompt), use a instrução direta
179
  if custom_prompt:
180
  try:
181
- # O get_ai_response usa o custom_prompt como conteúdo principal
182
  text_response = get_ai_response(
183
- model_provider,
184
- model_name,
185
  system_instruction=system_instruction_base,
186
- custom_prompt=custom_prompt,
187
  json_schema=None
188
  )
189
- # Para atividades, retornamos a resposta simples no campo 'explanation'
190
  return jsonify({
191
  "explanation": text_response,
192
- "translation": "Activity Generated." # Marcador para o frontend
193
  })
194
  except Exception as e:
195
  print(f"Erro na geração de atividade: {e}")
196
  return jsonify({"error": f"AI activity generation failed: {e}"}), 500
197
 
198
-
199
- # 2. Requisições de Contextualização (Clique Duplo/Flashcard)
200
  if not word:
201
  return jsonify({"error": "No word or phrase selected."}), 400
202
 
203
- # Define o schema JSON para Flashcards
204
  flashcard_schema = {
205
- "type": "OBJECT",
206
  "properties": {
207
- "term": {"type": "STRING", "description": "The selected word or phrase."},
208
- "explanation": {"type": "STRING", "description": "A clear English definition or explanation."},
209
- "translation": {"type": "STRING", "description": "The accurate Portuguese translation of the term/phrase."},
210
- "example": {"type": "STRING", "description": "A new, helpful example sentence using the term/phrase, specifically tailored to the context of the user."},
 
211
  },
212
- "required": ["term", "explanation", "translation", "example"]
213
  }
214
 
215
  try:
216
  if for_flashcard:
217
- # 2a. Requisição para Flashcard (JSON Estruturado)
218
- system_instruction = system_instruction_base + f" Analyze the term '{word}' in context: '{context}'. You must generate a single, highly relevant example sentence, and provide a clear, simple English definition. Translate the term/phrase accurately to Portuguese. The output MUST be a JSON object conforming to the provided schema."
 
 
 
 
 
 
 
 
 
219
 
220
- # Para Groq, adicionamos a descrição do schema no prompt
221
- if model_provider == 'groq':
222
- system_instruction += f" The JSON object must follow this structure: {json.dumps(flashcard_schema)}"
223
-
224
  card_data = get_ai_response(
225
- model_provider,
226
- model_name,
227
- system_instruction,
228
- json_schema=flashcard_schema # Usa o schema definido
229
  )
230
-
231
- # Verifica e retorna o JSON
232
- if card_data and card_data.get('term') and card_data.get('translation') and card_data.get('example'):
233
- return jsonify(card_data)
234
- else:
235
- raise Exception("AI returned empty or invalid structured data.")
236
 
237
- else:
238
- # 2b. Requisição para Modal (Explicação e Tradução Simples - Clique Duplo)
239
- prompt = system_instruction_base + f" Analyze the term '{word}' in 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."
240
 
241
  text_response = get_ai_response(
242
- model_provider,
243
- model_name,
244
- prompt,
245
- json_schema=None # Não é JSON estruturado
246
  )
247
-
248
  parts = text_response.split('---', 1)
249
-
250
  explanation = parts[0].strip()
251
  translation = parts[1].strip() if len(parts) > 1 else 'Tradução não disponível.'
252
 
253
- return jsonify({
254
- "explanation": explanation,
255
- "translation": translation
256
- })
257
 
258
  except Exception as e:
259
  print(f"Erro na análise de IA: {e}")
260
  return jsonify({"error": f"AI analysis failed: {e}"}), 500
261
 
262
-
263
  # --- ROTA DE INICIALIZAÇÃO E ARQUIVOS ESTÁTICOS ---
264
 
265
  @app.route('/')
266
- def serve_index():
267
- """
268
- Serve o arquivo index.html principal.
269
- Necessário para o Hugging Face Spaces que usa a arquitetura Flask.
270
- """
271
- try:
272
- # Tenta servir o index.html, que é o frontend
273
- return app.send_static_file('index.html')
274
- except Exception:
275
- # Se não encontrar index.html, serve uma mensagem de erro simples
276
- return "Frontend file (index.html) not found. Please ensure index.html is in the same directory as app.py.", 500
277
-
278
- # Se estiver usando o Flask, ele procura por arquivos estáticos na pasta 'static'.
279
- # Como o Hugging Face coloca tudo na raiz do app, podemos configurá-lo para servir
280
- # a partir do diretório atual se necessário, mas para esta aplicação simples
281
- # e o index.html na raiz, o método 'serve_index' acima é o mais direto.
282
-
283
- # Inicializa o servidor Flask
284
  if __name__ == '__main__':
285
- # Define o static_folder como o diretório atual para que o send_static_file funcione
286
- app.static_folder = os.path.dirname(os.path.abspath(__file__))
287
  app.run(host='0.0.0.0', port=7860)
 
1
+ #app.py
2
  import os
3
  import io
4
  import json
 
24
 
25
  # 1. Configuração Gemini
26
  try:
27
+ # Use os Secrets do Hugging Face para armazenar suas chaves
28
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
29
  if GEMINI_API_KEY:
30
+ # Apenas inicializa o cliente se a chave existir
31
+ genai.configure(api_key=GEMINI_API_KEY)
32
+ genai_client = genai
33
  else:
34
  print("AVISO: GEMINI_API_KEY não configurada. Gemini estará desativado para análise.")
35
  except Exception as e:
 
86
 
87
  # --- ROTA 2: TRADUÇÃO RÁPIDA E CONTEXTO (IA) - USANDO GEMINI OU GROQ ---
88
 
89
+ def get_ai_response(provider, model_name, system_instruction, user_prompt, json_schema=None):
90
  """Função auxiliar para rotear a requisição LLM para o provedor selecionado."""
91
 
 
 
 
92
  if provider == 'gemini':
93
  if not genai_client:
94
  raise Exception("Gemini client not initialized. GEMINI_API_KEY is missing.")
95
 
96
+ model = genai_client.GenerativeModel(model_name)
97
 
98
  # Configuração para resposta JSON estruturada
99
+ generation_config = None
100
  if json_schema:
101
+ generation_config = genai.GenerationConfig(
102
  response_mime_type="application/json",
103
  response_schema=json_schema
104
  )
105
 
106
+ response = model.generate_content(
107
+ user_prompt,
108
+ generation_config=generation_config
 
109
  )
110
 
111
  if json_schema:
 
112
  if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
113
  json_text = response.candidates[0].content.parts[0].text.strip()
114
  return json.loads(json_text)
115
  else:
116
  raise Exception("Gemini returned empty or invalid JSON content.")
117
  else:
 
118
  return response.text.strip()
119
 
120
  elif provider == 'groq':
 
123
 
124
  messages = [
125
  {"role": "system", "content": system_instruction},
126
+ {"role": "user", "content": user_prompt}
127
  ]
128
 
 
129
  config_params = {}
130
  if json_schema:
131
  config_params['response_format'] = {"type": "json_object"}
 
135
  messages=messages,
136
  **config_params
137
  )
 
138
  text_response = response.choices[0].message.content.strip()
139
 
140
  if json_schema:
 
141
  return json.loads(text_response)
142
  else:
 
143
  return text_response
144
 
145
  else:
 
148
 
149
  @app.route('/explain-proxy', methods=['POST'])
150
  def explain_proxy():
 
 
 
151
  data = request.get_json()
152
  word = data.get('word', '').strip()
153
+ context = data.get('context', '')
154
  for_flashcard = data.get('for_flashcard', False)
155
+ model_provider, model_name = data.get('model', 'gemini:gemini-1.5-flash').split(':', 1)
156
+ context_focus = data.get('context_focus', 'General/Social')
157
+ custom_prompt = data.get('custom_prompt', None)
 
158
 
 
159
  if model_provider == 'gemini' and not genai_client:
160
+ return jsonify({"error": "GEMINI_API_KEY not configured."}), 503
161
  if model_provider == 'groq' and not groq_client:
162
+ return jsonify({"error": "GROQ_API_KEY not configured."}), 503
163
 
164
+ 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."
 
165
 
 
166
  if custom_prompt:
167
  try:
 
168
  text_response = get_ai_response(
169
+ model_provider, model_name,
 
170
  system_instruction=system_instruction_base,
171
+ user_prompt=custom_prompt,
172
  json_schema=None
173
  )
 
174
  return jsonify({
175
  "explanation": text_response,
176
+ "translation": "Activity Generated."
177
  })
178
  except Exception as e:
179
  print(f"Erro na geração de atividade: {e}")
180
  return jsonify({"error": f"AI activity generation failed: {e}"}), 500
181
 
 
 
182
  if not word:
183
  return jsonify({"error": "No word or phrase selected."}), 400
184
 
185
+ # Schema JSON aprimorado para o Flashcard Inteligente
186
  flashcard_schema = {
187
+ "type": "object",
188
  "properties": {
189
+ "term": {"type": "string", "description": "The selected English word or phrase."},
190
+ "translation": {"type": "string", "description": "The accurate Portuguese translation to be used as a hint."},
191
+ "context_sentence": {"type": "string", "description": "The original, complete English sentence from the context."},
192
+ "gapped_sentence": {"type": "string", "description": "The context sentence with the term replaced by '______________'."},
193
+ "definition": {"type": "string", "description": "A concise, clear English definition of the term."},
194
  },
195
+ "required": ["term", "translation", "context_sentence", "gapped_sentence", "definition"]
196
  }
197
 
198
  try:
199
  if for_flashcard:
200
+ # Prompt detalhado para gerar o flashcard
201
+ system_instruction = system_instruction_base + " Your task is to generate a JSON object for an 'intelligent flashcard'."
202
+ user_prompt = f"""
203
+ Analyze the term '{word}' found in the following sentence: '{context}'.
204
+ Based on this, generate a JSON object that follows the required schema.
205
+ - 'term' should be the exact word/phrase '{word}'.
206
+ - 'translation' must be its most accurate Portuguese translation in this context.
207
+ - 'context_sentence' is the full original sentence.
208
+ - 'gapped_sentence' is the original sentence, but replacing '{word}' with '______________'.
209
+ - 'definition' is a clear, single-sentence definition in English.
210
+ """
211
 
 
 
 
 
212
  card_data = get_ai_response(
213
+ model_provider, model_name,
214
+ system_instruction, user_prompt,
215
+ json_schema=flashcard_schema
 
216
  )
217
+ return jsonify(card_data)
 
 
 
 
 
218
 
219
+ else: # Requisição para o Modal (Clique Duplo)
220
+ system_instruction = system_instruction_base
221
+ user_prompt = f"Analyze the term '{word}' in context: '{context}'. Provide a clear, one-sentence English explanation, followed by its Portuguese translation, separated by '---'."
222
 
223
  text_response = get_ai_response(
224
+ model_provider, model_name,
225
+ system_instruction, user_prompt,
226
+ json_schema=None
 
227
  )
 
228
  parts = text_response.split('---', 1)
 
229
  explanation = parts[0].strip()
230
  translation = parts[1].strip() if len(parts) > 1 else 'Tradução não disponível.'
231
 
232
+ return jsonify({"explanation": explanation, "translation": translation})
 
 
 
233
 
234
  except Exception as e:
235
  print(f"Erro na análise de IA: {e}")
236
  return jsonify({"error": f"AI analysis failed: {e}"}), 500
237
 
 
238
  # --- ROTA DE INICIALIZAÇÃO E ARQUIVOS ESTÁTICOS ---
239
 
240
  @app.route('/')
241
+ def root():
242
+ return send_file('index.html')
243
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  if __name__ == '__main__':
 
 
245
  app.run(host='0.0.0.0', port=7860)