amauricunha commited on
Commit
0638ab8
·
verified ·
1 Parent(s): 78727e0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +139 -97
app.py CHANGED
@@ -1,139 +1,181 @@
1
- # Importações necessárias.
2
- # flask: Para criar o servidor web e as rotas.
3
- # gTTS: Para gerar o áudio TTS gratuitamente (requer internet, mas não chave API).
4
- # os: Para gerenciar arquivos temporários.
5
- # google.genai: Para a contextualização e tradução da IA (requer GEMINI_API_KEY).
 
6
 
7
  from flask import Flask, request, jsonify, send_file
8
  from gtts import gTTS
9
  from google import genai
10
  from google.genai import types
11
- import os
12
- import io
13
- import json
14
 
15
  # Inicializa o Flask
16
- app = Flask(__name__, static_folder='.', static_url_path='')
17
 
18
- # Configuração da API Key (Deve ser lida do secret GEMINI_API_KEY no Hugging Face)
19
- # O código abaixo tenta ler a chave da variável de ambiente.
20
- # Se a chave não estiver configurada no Secret, o endpoint /explain-proxy falhará,
21
- # mas o /tts-proxy (gTTS) ainda funcionará.
22
  try:
23
- API_KEY = os.environ.get("GEMINI_API_KEY")
24
- if API_KEY:
25
- client = genai.Client(api_key=API_KEY)
26
  else:
27
- print("AVISO: GEMINI_API_KEY não configurada. A funcionalidade de Tradução/Explicação (IA) não funcionará.")
 
28
  except Exception as e:
29
- print(f"Erro ao inicializar o cliente Gemini: {e}")
30
- client = None
31
 
 
 
32
 
33
- # Rota para servir o arquivo HTML principal (Frontend)
34
- @app.route('/')
35
- def serve_index():
36
- """Serve o arquivo index.html, que contém a interface do usuário."""
37
- # Garante que o index.html seja servido como a página inicial
38
- return app.send_static_file('index.html')
39
 
 
40
 
41
- # --- ROTA 1: TEXT-TO-SPEECH (TTS) - Usando gTTS ---
42
  @app.route('/tts-proxy', methods=['POST'])
43
  def tts_proxy():
44
- """Gera áudio MP3 a partir do texto usando a biblioteca gTTS."""
 
 
 
 
 
 
 
 
 
 
 
 
45
  try:
46
- data = request.get_json()
47
- text = data.get('text', '').strip()
48
-
49
- if not text:
50
- return jsonify({"error": "Texto não fornecido."}), 400
51
-
52
- # O gTTS não suporta chunks longos, por isso ele é chamado diretamente.
53
- # Ele faz a requisição à internet por conta própria.
54
- tts = gTTS(text=text, lang='en', tld='us')
55
-
56
- # Salva o áudio em um buffer de bytes na memória (mais rápido que salvar em disco)
57
  mp3_fp = io.BytesIO()
58
  tts.write_to_fp(mp3_fp)
59
  mp3_fp.seek(0)
60
 
61
- # Retorna o arquivo MP3 como uma resposta de streaming
62
  return send_file(
63
  mp3_fp,
64
- mimetype='audio/mp3',
65
  as_attachment=True,
66
  download_name='audio.mp3'
67
  )
68
 
69
  except Exception as e:
70
- print(f"Erro no TTS gTTS: {e}")
71
- return jsonify({"error": f"Erro interno ao gerar áudio: {e}"}), 500
72
 
73
 
74
- # --- ROTA 2: EXPLICAÇÃO E TRADUÇÃO DA IA - Usando google-genai ---
 
75
  @app.route('/explain-proxy', methods=['POST'])
76
  def explain_proxy():
77
- """Usa a IA Gemini para obter explicação em EN e tradução em PT de uma palavra."""
78
- if not client:
79
- return jsonify({
80
- "explanation": "Funcionalidade desativada. A chave GEMINI_API_KEY não foi configurada no Hugging Face Secrets.",
81
- "translation": "Funcionalidade desativada. Por favor, configure a chave API para usar este recurso."
82
- }), 503
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  try:
85
- data = request.get_json()
86
- word = data.get('word', '').strip()
87
- context = data.get('context', '').strip()
88
-
89
- if not word:
90
- return jsonify({"error": "Palavra não fornecida para análise."}), 400
91
-
92
- # Prompt de IA estruturado para garantir a resposta em formato JSON
93
- system_prompt = (
94
- "Você é um tutor de inglês especializado em vocabulário e contexto. Sua tarefa é analisar uma "
95
- "palavra e fornecer uma explicação em inglês e uma tradução em português baseada no contexto. "
96
- "Sua resposta DEVE ser um objeto JSON com as chaves 'explanation' e 'translation'."
97
- )
98
-
99
- user_prompt = (
100
- f"Analise a palavra: '{word}'. "
101
- f"O contexto em que ela foi encontrada é: \"{context}\". "
102
- "Forneça uma explicação concisa em inglês de como a palavra é usada e a melhor tradução para o português DENTRO DESSE CONTEXTO. "
103
- "Responda APENAS com o objeto JSON."
104
- )
105
-
106
- response = client.models.generate_content(
107
- model='gemini-2.5-flash',
108
- contents=user_prompt,
109
- config=types.GenerateContentConfig(
110
- system_instruction=system_prompt,
111
- response_mime_type="application/json",
112
- response_schema={
113
- "type": "OBJECT",
114
- "properties": {
115
- "explanation": {"type": "STRING", "description": "Explicação concisa em inglês sobre o uso e significado da palavra no contexto fornecido."},
116
- "translation": {"type": "STRING", "description": "Tradução para o português da palavra ou frase no contexto fornecido."}
117
- },
118
- "propertyOrdering": ["explanation", "translation"]
119
- }
120
  )
121
- )
122
-
123
- # O modelo retorna uma string JSON, precisamos fazer o parse
124
- json_content = response.candidates[0].content.parts[0].text
125
- ai_result = json.loads(json_content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- return jsonify(ai_result)
 
 
 
128
 
129
- except genai.errors.APIError as e:
130
- print(f"Erro na API Gemini: {e}")
131
- return jsonify({"explanation": "Erro ao conectar à API Gemini. Chave inválida ou limite excedido.", "translation": "Erro na conexão da IA."}), 500
132
  except Exception as e:
133
- print(f"Erro no explain-proxy: {e}")
134
- return jsonify({"explanation": "Erro interno do servidor ao processar a solicitação.", "translation": "Erro interno do servidor."}), 500
 
135
 
 
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  if __name__ == '__main__':
138
- # Define o host para 0.0.0.0 para ser acessível dentro do ambiente Hugging Face
139
- app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
 
 
1
+ import os
2
+ import io
3
+ import json
4
+ import uuid
5
+ import tempfile
6
+ 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
+
13
+ # --- CONFIGURAÇÃO INICIAL ---
 
14
 
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 ---
37
 
 
38
  @app.route('/tts-proxy', methods=['POST'])
39
  def tts_proxy():
40
+ """
41
+ Gera áudio MP3 a partir de texto usando a biblioteca gTTS (gratuita)
42
+ e retorna o áudio como um arquivo.
43
+ """
44
+ data = request.get_json()
45
+ text = data.get('text', '')
46
+
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)
61
 
62
+ # Retorna o arquivo de áudio MP3
63
  return send_file(
64
  mp3_fp,
65
+ mimetype='audio/mpeg',
66
  as_attachment=True,
67
  download_name='audio.mp3'
68
  )
69
 
70
  except Exception as e:
71
+ print(f"Erro no gTTS: {e}")
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()
145
+ translation = parts[1].strip() if len(parts) > 1 else 'Tradução não disponível.'
146
 
147
+ return jsonify({
148
+ "explanation": explanation,
149
+ "translation": translation
150
+ })
151
 
 
 
 
152
  except Exception as e:
153
+ print(f"Erro na análise de IA: {e}")
154
+ return jsonify({"error": f"AI analysis failed: {e}"}), 500
155
+
156
 
157
+ # --- ROTA DE INICIALIZAÇÃO E ARQUIVOS ESTÁTICOS ---
158
 
159
+ @app.route('/')
160
+ def serve_index():
161
+ """
162
+ Serve o arquivo index.html principal.
163
+ Necessário para o Hugging Face Spaces que usa a arquitetura Flask.
164
+ """
165
+ try:
166
+ # Tenta servir o index.html, que é o frontend
167
+ return app.send_static_file('index.html')
168
+ except Exception:
169
+ # Se não encontrar index.html, serve uma mensagem de erro simples
170
+ return "Frontend file (index.html) not found. Please ensure index.html is in the same directory as app.py.", 500
171
+
172
+ # Se estiver usando o Flask, ele procura por arquivos estáticos na pasta 'static'.
173
+ # Como o Hugging Face coloca tudo na raiz do app, podemos configurá-lo para servir
174
+ # a partir do diretório atual se necessário, mas para esta aplicação simples
175
+ # e o index.html na raiz, o método 'serve_index' acima é o mais direto.
176
+
177
+ # Inicializa o servidor Flask
178
  if __name__ == '__main__':
179
+ # Define o static_folder como o diretório atual para que o send_static_file funcione
180
+ app.static_folder = os.path.dirname(os.path.abspath(__file__))
181
+ app.run(host='0.0.0.0', port=7860)