sbenfenatti commited on
Commit
f58a75c
·
verified ·
1 Parent(s): 44fc68a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +292 -134
app.py CHANGED
@@ -1,148 +1,306 @@
1
- import os
2
- import io
3
- import base64
4
- import tempfile
5
- import logging
6
- import json
7
- import asyncio
8
-
9
- from fastapi import FastAPI, File, UploadFile, HTTPException
10
  from fastapi.responses import FileResponse, JSONResponse
11
- from dotenv import load_dotenv
12
- from faster_whisper import WhisperModel
 
 
 
 
13
  import google.generativeai as genai
14
- import edge_tts
15
-
 
16
  # ---------- Configuração Inicial ----------
17
- load_dotenv()
18
- CACHE_DIR = os.getenv("HF_HUB_CACHE", "./models_cache")
19
- os.environ["MPLCONFIGDIR"] = os.path.join(CACHE_DIR, "matplotlib")
20
- LOGIN_PASSWORDS = os.getenv("LOGIN_PASSWORDS")
21
- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
22
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  # ---------- Aplicação FastAPI ----------
24
  app = FastAPI()
25
- logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
26
-
 
27
  # ---------- Carregamento de Modelos (no arranque) ----------
28
- whisper_model = None
29
- gemini_model = None
30
-
31
- @app.on_event("startup")
32
- def load_models():
33
- global whisper_model, gemini_model
34
- logging.info("A carregar modelos e clientes de API...")
35
- try:
36
- model_name = "medium"
37
- whisper_model = WhisperModel(model_name, device="cpu", compute_type="int8")
38
- logging.info(f"Modelo faster-whisper '{model_name}' (int8) pronto.")
39
- except Exception as e:
40
- logging.error(f"Falha ao iniciar o modelo faster-whisper: {e}")
41
- raise RuntimeError("Não foi possível carregar o modelo Whisper.") from e
42
-
43
- if GOOGLE_API_KEY:
44
- try:
45
- genai.configure(api_key=GOOGLE_API_KEY)
46
- gemini_model = genai.GenerativeModel("gemini-1.5-flash")
47
- logging.info("Gemini pronto.")
48
- except Exception as e:
49
- logging.error(f"Falha ao iniciar Gemini: {e}")
50
- raise RuntimeError("Não foi possível carregar o modelo Gemini.") from e
51
-
52
- logging.info("Modelos carregados com sucesso.")
53
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  # ---------- Utilidades ----------
55
- def ask_gemini(question: str) -> str:
56
- if not gemini_model:
57
- raise HTTPException(status_code=503, detail="Modelo de linguagem não está disponível.")
58
- prompt = (
59
- "Você é 'SintonIA', um assistente de IA por voz para saúde bucal. "
60
- "Responda de forma empática, clara e segura, em 2-3 frases. "
61
- "NUNCA dê diagnóstico e sempre recomende consulta presencial a um dentista."
62
- )
63
- try:
64
- response = gemini_model.generate_content([prompt, question])
65
- return response.text
66
- except Exception as e:
67
- logging.error(f"Erro no Gemini: {e}")
68
- raise HTTPException(status_code=500, detail="Erro ao gerar a resposta de IA.")
69
-
70
- VOICE = "pt-BR-AntonioNeural"
71
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  async def synthesize(text: str) -> bytes | None:
73
- try:
74
- audio_bytes = b""
75
- communicate = edge_tts.Communicate(text, VOICE)
76
- async for chunk in communicate.stream():
77
- if chunk["type"] == "audio":
78
- audio_bytes += chunk["data"]
79
- return audio_bytes
80
- except Exception as e:
81
- logging.error(f"Erro ao sintetizar áudio com Edge TTS: {e}")
82
- return None
83
-
 
 
 
 
 
 
 
 
84
  # ---------- Rotas (Endpoints) ----------
85
  @app.get("/")
86
- async def read_index():
87
- return FileResponse('index.html')
88
-
 
89
  @app.post("/login")
90
  async def login(request: dict):
91
- if not LOGIN_PASSWORDS:
92
- return {"success": True}
93
- valid_passwords = [p.strip() for p in LOGIN_PASSWORDS.split(',')]
94
- pwd_received = request.get("password", "")
95
- if pwd_received not in valid_passwords:
96
- raise HTTPException(status_code=401, detail="Senha incorreta.")
97
- return {"success": True}
98
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  @app.post("/process-audio")
100
- async def process_audio(audio: UploadFile = File(...)):
101
- if not all([whisper_model, gemini_model]):
102
- raise HTTPException(status_code=503, detail="Um serviço de IA não está disponível.")
103
-
104
- try:
105
- with tempfile.NamedTemporaryFile(delete=True, suffix=".webm") as tmp_file:
106
- content = await audio.read()
107
- tmp_file.write(content)
108
- tmp_file.seek(0)
109
-
110
- # Always transcribe, wrapping glossary in an extra list
111
- glossary = [[
112
- "endodontia", "periodontite", "prótese",
113
- "ibuprofeno", "dipirona", "paracetamol", "naproxeno", "aspirina", "diclofenaco",
114
- "amoxicilina", "amoxicilina+clavulanato", "clindamicina", "azitromicina",
115
- "metronidazol", "penicilina V", "cefalexina",
116
- "tramadol", "codeína"
117
- ]]
118
- segments, _ = whisper_model.transcribe(
119
- tmp_file.name,
120
- language="pt",
121
- initial_prompt=glossary
122
- )
123
- transcribed_parts = [segment.text for segment in segments]
124
- text = "".join(transcribed_parts).strip()
125
- logging.info(f"Texto transcrito: '{text}'")
126
-
127
- except Exception as e:
128
- logging.error(f"Erro na transcrição do faster-whisper: {e}")
129
- text = ""
130
-
131
- if not text:
132
- ai_text = "Desculpe, não entendi o que foi dito. Você poderia repetir, por favor?"
133
- else:
134
- ai_text = ask_gemini(text)
135
-
136
- audio_bytes = None
137
- if ai_text:
138
- audio_bytes = await synthesize(ai_text)
139
-
140
- return JSONResponse(content={
141
- "user_question": text,
142
- "ai_answer": ai_text,
143
- "audio_base64": base64.b64encode(audio_bytes).decode('utf-8') if audio_bytes else None
144
- })
145
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  @app.get("/healthz")
147
- async def health_check():
148
- return {"status": "OK"}
 
 
 
1
+ import os, io, base64, tempfile, logging, json, asyncio
2
+ from fastapi import FastAPI, File, UploadFile, Form, HTTPException
 
 
 
 
 
 
 
3
  from fastapi.responses import FileResponse, JSONResponse
4
+ from dotenv import load
5
+ dotenv
6
+ _
7
+ from faster
8
+ _
9
+ whisper import WhisperModel
10
  import google.generativeai as genai
11
+ import edge
12
+ tts
13
+ _
14
  # ---------- Configuração Inicial ----------
15
+ load
16
+ dotenv()
17
+ _
18
+ CACHE
19
+ _
20
+ DIR = os.getenv("HF
21
+ HUB
22
+ CACHE"
23
+ ,
24
+ "
25
+ ./models
26
+ _
27
+ _
28
+ os.environ["MPLCONFIGDIR"] = os.path.join(CACHE
29
+ _
30
+ cache")
31
+ _
32
+ DIR,
33
+ "matplotlib")
34
+ LOGIN
35
+ _
36
+ PASSWORDS = os.getenv("LOGIN
37
+ _
38
+ GOOGLE
39
+ API
40
+ _
41
+ _
42
+ KEY = os.getenv("GOOGLE
43
+ _
44
+ _
45
+ PASSWORDS")
46
+ API
47
+ KEY")
48
  # ---------- Aplicação FastAPI ----------
49
  app = FastAPI()
50
+ logging.basicConfig(level=logging.INFO, format=
51
+ "%(asctime)s - %(levelname)s -
52
+ %(message)s")
53
  # ---------- Carregamento de Modelos (no arranque) ----------
54
+ whisper
55
+ model = None
56
+ _
57
+ gemini
58
+ model = None
59
+ _
60
+ @app.on
61
+ _
62
+ event("startup")
63
+ def load
64
+ models():
65
+ _
66
+ global whisper
67
+ _
68
+ model, gemini
69
+ model
70
+ _
71
+ logging.info("A carregar modelos e clientes de API...
72
+ ")
73
+ try:
74
+ model
75
+ name =
76
+ "medium"
77
+ _
78
+ whisper
79
+ _
80
+ model = WhisperModel(model
81
+ name, device=
82
+ _
83
+ "cpu"
84
+ ,
85
+ compute
86
+ _
87
+ type=
88
+ "int8")
89
+ logging.info(f"Modelo faster-whisper '{model
90
+ _
91
+ name}' (int8) pronto.
92
+ ")
93
+ except Exception as e:
94
+ logging.error(f"Falha ao iniciar o modelo faster-whisper: {e}")
95
+ raise RuntimeError("Não foi possível carregar o modelo Whisper.
96
+ ") from e
97
+ if GOOGLE
98
+ API
99
+ KEY:
100
+ _
101
+ _
102
+ try:
103
+ genai.configure(api
104
+ _
105
+ key=GOOGLE
106
+ API
107
+ KEY)
108
+ _
109
+ _
110
+ gemini
111
+ _
112
+ model = genai.GenerativeModel("gemini-1.5-flash")
113
+ logging.info("Gemini pronto.
114
+ ")
115
+ except Exception as e:
116
+ logging.error(f"Falha ao iniciar Gemini: {e}")
117
+ raise RuntimeError("Não foi possível carregar o modelo Gemini.
118
+ ") from e
119
+ logging.info("Modelos carregados com sucesso.
120
+ ")
121
  # ---------- Utilidades ----------
122
+ def ask
123
+ _gemini(question: str) -> str:
124
+ if not gemini
125
+ model:
126
+ _
127
+ raise HTTPException(status
128
+ code=503, detail=
129
+ _
130
+ "Modelo de linguagem não está
131
+ disponível.
132
+ ")
133
+ prompt = ("Você é 'SintonIA'
134
+ , um assistente de IA por voz para saúde bucal.
135
+ "
136
+ "Responda de forma empática, clara e segura, em 2-3 frases.
137
+ "
138
+ "NUNCA dê diagnóstico e sempre recomende consulta presencial a um
139
+ dentista.
140
+ ")
141
+ try:
142
+ response = gemini
143
+ _
144
+ model.generate
145
+ _
146
+ content([prompt, question])
147
+ return response.text
148
+ except Exception as e:
149
+ logging.error(f"Erro no Gemini: {e}")
150
+ raise HTTPException(status
151
+ code=500, detail=
152
+ _
153
+ "Erro ao gerar a resposta de IA.
154
+ ")
155
+ VOICE =
156
+ "pt-BR-AntonioNeural"
157
  async def synthesize(text: str) -> bytes | None:
158
+ try:
159
+ audio
160
+ _
161
+ bytes = b""
162
+ communicate = edge
163
+ tts.Communicate(text, VOICE)
164
+ _
165
+ async for chunk in communicate.stream():
166
+ if chunk["type"] ==
167
+ "audio":
168
+ audio
169
+ _
170
+ bytes += chunk["data"]
171
+ return audio
172
+ _
173
+ bytes
174
+ except Exception as e:
175
+ logging.error(f"Erro ao sintetizar áudio com Edge TTS: {e}")
176
+ return None
177
  # ---------- Rotas (Endpoints) ----------
178
  @app.get("/")
179
+ async def read
180
+ index():
181
+ _
182
+ return FileResponse('index.html')
183
  @app.post("/login")
184
  async def login(request: dict):
185
+ if not LOGIN
186
+ PASSWORDS:
187
+ _
188
+ return {"success": True}
189
+ valid
190
+ _passwords = [p.strip() for p in LOGIN
191
+ _
192
+ PASSWORDS.split('
193
+ ,
194
+ ')]
195
+ pwd
196
+ _
197
+ received = request.get("password"
198
+ ,
199
+ "")
200
+ is
201
+ _
202
+ ok = pwd
203
+ received in valid
204
+ _
205
+ _passwords
206
+ if not is
207
+ ok:
208
+ _
209
+ raise HTTPException(status
210
+ code=401, detail=
211
+ "Senha incorreta.
212
+ ")
213
+ _
214
+ return {"success": True}
215
  @app.post("/process-audio")
216
+ async def process
217
+ _
218
+ audio(audio: UploadFile = File(...)):
219
+ if not all([whisper
220
+ _
221
+ model, gemini
222
+ model]):
223
+ _
224
+ raise HTTPException(status
225
+ code=503, detail=
226
+ _
227
+ "Um serviço de IA não está
228
+ disponível.
229
+ ")
230
+ try:
231
+ with tempfile.NamedTemporaryFile(delete=True, suffix=
232
+ "
233
+ .webm") as tmp_
234
+ file:
235
+ content = await audio.read()
236
+ tmp_
237
+ file.write(content)
238
+ tmp_
239
+ file.seek(0)
240
+ if os.path.getsize(tmp_
241
+ file.name) > 1000:
242
+ segments,
243
+ _
244
+ = whisper
245
+ _
246
+ model.transcribe(tmp_
247
+ file.name, language=
248
+ "pt")
249
+ transcribed
250
+ _parts = [segment.text for segment in segments]
251
+ text =
252
+ ""
253
+ .join(transcribed
254
+ _parts).strip()
255
+ logging.info(f"Texto transcrito: '{text}'")
256
+ else:
257
+ text =
258
+ ""
259
+ except Exception as e:
260
+ logging.error(f"Erro na transcrição do faster-whisper: {e}")
261
+ text =
262
+ ""
263
+ if not text:
264
+ ai
265
+ _
266
+ else:
267
+ ai
268
+ text =
269
+ _
270
+ "Desculpe, não entendi o que foi dito. Você poderia repetir, por favor?"
271
+ text = ask
272
+ _gemini(text)
273
+ audio
274
+ _
275
+ if ai
276
+ text:
277
+ _
278
+ audio
279
+ bytes = None
280
+ _
281
+ bytes = await synthesize(ai
282
+ text)
283
+ _
284
+ # --- LÓGICA FINAL: Retorna um JSON com todos os dados ---
285
+ return JSONResponse(content={
286
+ "user
287
+ _question": text,
288
+ "ai
289
+ answer": ai
290
+ text,
291
+ _
292
+ _
293
+ "audio
294
+ base64": base64.b64encode(audio
295
+ _
296
+ _
297
+ bytes).decode('utf-8') if audio
298
+ _
299
+ else None
300
+ bytes
301
+ })
302
  @app.get("/healthz")
303
+ async def health
304
+ check():
305
+ _
306
+ return {"status": "OK"}