Nolist commited on
Commit
536a9b8
·
verified ·
1 Parent(s): 0433a16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -135
app.py CHANGED
@@ -2,7 +2,7 @@
2
  Health Voice Assistant
3
  - Whisper ASR (incremental)
4
  - GPT health guidance (NON-diagnostic)
5
- - Auto Text-to-Speech (autoplay, no file saved)
6
 
7
  ⚠️ GENERAL guidance only – NOT medical advice
8
  """
@@ -14,11 +14,10 @@ import re
14
  import textwrap
15
  import base64
16
  from io import BytesIO
17
- from datetime import datetime
18
 
19
- import requests
20
  import numpy as np
21
  import torch
 
22
  import streamlit as st
23
 
24
  from transformers import pipeline
@@ -26,13 +25,18 @@ from transformers.pipelines.audio_utils import ffmpeg_read
26
  from openai import OpenAI
27
  from gtts import gTTS
28
 
29
- # ================= PAGE CONFIG (MUST BE FIRST) =================
30
  st.set_page_config(page_title="Health Voice Assistant", layout="wide")
31
 
32
  # ================= SESSION STATE =================
33
- for k in ["transcript", "gpt_text", "audio_html", "done"]:
34
- if k not in st.session_state:
35
- st.session_state[k] = None if k != "done" else False
 
 
 
 
 
36
 
37
  # ================= CONFIG =================
38
  MODEL_NAME = "openai/whisper-small.en"
@@ -51,10 +55,11 @@ device = 0 if torch.cuda.is_available() else -1
51
  st.title("🩺 Health Voice-Based Assistant")
52
 
53
  MEDICAL_DISCLAIMER = """
54
- ⚠️ IMPORTANT MEDICAL DISCLAIMER
55
- This assistant is NOT a medical professional.
56
- It does NOT diagnose diseases or prescribe medication.
57
- Information is for GENERAL guidance only.
 
58
  """
59
 
60
  # ================= SIDEBAR =================
@@ -62,12 +67,11 @@ st.sidebar.title("🎓 User Manual")
62
  st.sidebar.markdown(
63
  """
64
  1. Choose input method
65
- 2. Upload audio **or** record directly
66
- 3. Press **Transcribe**
67
- 4. App will:
68
- - Analyze
69
- - Give guidance
70
- - Speak response
71
  """
72
  )
73
  st.sidebar.markdown("---")
@@ -92,7 +96,6 @@ def load_pipeline():
92
  pass
93
  return pipe
94
 
95
-
96
  pipe = load_pipeline()
97
 
98
  # ================= GPT CLIENT =================
@@ -102,11 +105,15 @@ client = OpenAI(api_key=CHATGPT_API)
102
  def format_text(text: str, width: int = 90) -> str:
103
  text = re.sub(r"\s+", " ", text).strip()
104
  sentences = re.split(r"(?<=[.!?])\s+", text)
105
- return "\n\n".join(
106
- textwrap.fill(" ".join(sentences[i : i + 3]), width)
107
- for i in range(0, len(sentences), 3)
108
- )
109
-
 
 
 
 
110
 
111
  def merge_overlap(old: str, new: str, max_words: int = 10) -> str:
112
  if not old:
@@ -117,14 +124,35 @@ def merge_overlap(old: str, new: str, max_words: int = 10) -> str:
117
  return " ".join(a + b[k:])
118
  return old + " " + new
119
 
120
-
121
  def tts_bytes(text: str) -> BytesIO:
122
  mp3 = BytesIO()
123
  gTTS(text=text, lang="en").write_to_fp(mp3)
124
  mp3.seek(0)
125
  return mp3
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
 
 
 
 
 
128
  def run_gpt(transcript: str) -> str:
129
  prompt = f"""
130
  You are a NON-diagnostic health assistant.
@@ -150,34 +178,8 @@ TASK:
150
  )
151
  return res.choices[0].message.content.strip()
152
 
153
-
154
- # ================= FIREBASE =================
155
- def read_firebase_transcribe():
156
- try:
157
- url = f"{FIREBASE_DB_URL}/transcribe.json"
158
- res = requests.get(url, timeout=5)
159
- if res.status_code != 200:
160
- return None
161
-
162
- data = res.json()
163
- if not data:
164
- return None
165
-
166
- text = data.get("text", "").strip()
167
- if not text:
168
- return None
169
-
170
- return {
171
- "text": text,
172
- "length": data.get("length"),
173
- "updated_at": data.get("updated_at"),
174
- }
175
- except Exception:
176
- return None
177
-
178
-
179
  # ================= ASR =================
180
- def transcribe_audio(audio_bytes: bytes):
181
  sr = pipe.feature_extractor.sampling_rate
182
  audio = ffmpeg_read(audio_bytes, sr)
183
 
@@ -190,10 +192,10 @@ def transcribe_audio(audio_bytes: bytes):
190
  chunk_samples = int(CHUNK_SEC * sr)
191
  overlap_samples = int(OVERLAP_SEC * sr)
192
 
 
193
  acc = ""
194
- total = max(1, len(audio) // chunk_samples + 1)
195
 
196
- for i in range(total):
197
  start = max(0, i * chunk_samples - overlap_samples)
198
  end = min(len(audio), (i + 1) * chunk_samples + overlap_samples)
199
 
@@ -203,106 +205,98 @@ def transcribe_audio(audio_bytes: bytes):
203
  "forced_decoder_ids": getattr(pipe, "_forced_decoder_ids", None)
204
  },
205
  )
206
- acc = merge_overlap(acc, result["text"].strip())
207
- yield acc, i + 1, total
208
 
 
 
209
 
210
  # ================= INPUT MODE =================
211
- st.markdown("## 🎤 Audio Input Method")
212
 
213
- input_mode = st.radio(
214
  "Choose input method:",
215
- ["Upload audio file", "Record audio (from Firebase)"],
216
- horizontal=True,
217
  )
218
 
219
  # ================= MODE 2: FIREBASE =================
220
- if input_mode == "Record audio (from Firebase)":
221
- st.info("📡 Checking Firebase for recorded audio...")
 
 
 
 
 
 
 
 
 
 
 
222
 
223
- fb = read_firebase_transcribe()
 
 
 
 
 
 
224
 
225
- # No data -> send user to the recording page (link + meta-refresh fallback)
226
- if fb is None:
227
- st.warning("No recorded data found in Firebase. Please record audio first.")
228
- record_url = "https://nolist-ssp-recordoption.hf.space"
229
  st.markdown(
230
- f"""
231
- If you haven't recorded yet, open the recording page:
232
- <a href="{record_url}" target="_blank" rel="noopener">Open recorder (new tab)</a>
233
- <br><br>
234
- The app will also attempt to redirect you automatically in 3 seconds.
235
- <meta http-equiv="refresh" content="3; url={record_url}">
236
  """,
237
  unsafe_allow_html=True,
238
  )
239
- st.stop()
240
-
241
- # Data exists -> show transcript and a Continue button to run GPT+TTS
242
- st.success("✅ Data received from Firebase")
243
- st.markdown("### 📝 Transcript (from Firebase)")
244
-
245
- st.markdown(f"**Length:** {fb.get('length', 'N/A')}")
246
- if fb.get("updated_at"):
247
- st.markdown(f"**Updated at:** {fb['updated_at']}")
248
-
249
- st.markdown(f"```text\n{format_text(fb['text'])}\n```")
250
-
251
- # Continue button: use same behavior as Upload->Transcribe, but with existing text
252
- if st.button("Continue"):
253
- try:
254
- st.session_state.transcript = fb["text"]
255
- st.session_state.gpt_text = run_gpt(fb["text"])
256
-
257
- voice = tts_bytes(st.session_state.gpt_text)
258
- b64 = base64.b64encode(voice.read()).decode()
259
- st.session_state.audio_html = f"""
260
- <audio autoplay>
261
- <source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
262
- </audio>
263
- """
264
- st.success("Processed: GPT response generated and audio playing.")
265
- except Exception as e:
266
- st.error(f"Error processing Firebase transcript: {e}\n{traceback.format_exc()}")
267
-
268
- st.stop()
269
 
270
  # ================= MODE 1: UPLOAD AUDIO =================
271
- uploaded = st.file_uploader(
272
- "Upload audio (wav, mp3, m4a, ogg, flac)",
273
- type=["wav", "mp3", "m4a", "ogg", "flac"],
274
- )
 
275
 
276
- if uploaded:
277
- audio_bytes = uploaded.read()
278
- st.audio(audio_bytes)
279
-
280
- if st.button("Transcribe"):
281
- try:
282
- placeholder = st.empty()
283
- progress = st.progress(0.0)
284
-
285
- final_text = ""
286
- for txt, i, total in transcribe_audio(audio_bytes):
287
- final_text = txt
288
- placeholder.markdown(f"```text\n{format_text(txt)}\n```")
289
- progress.progress(i / total)
290
-
291
- st.session_state.transcript = final_text
292
- st.session_state.gpt_text = run_gpt(final_text)
293
-
294
- voice = tts_bytes(st.session_state.gpt_text)
295
- b64 = base64.b64encode(voice.read()).decode()
296
- st.session_state.audio_html = f"""
297
- <audio autoplay>
298
- <source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
299
- </audio>
300
- """
 
 
 
 
 
 
 
 
 
301
 
302
- except Exception as e:
303
- st.error(f"{e}\n{traceback.format_exc()}")
304
 
305
- # ================= OUTPUT =================
306
  if st.session_state.transcript:
307
  st.markdown("### 📝 Transcript")
308
  st.markdown(f"```text\n{format_text(st.session_state.transcript)}\n```")
@@ -315,4 +309,4 @@ if st.session_state.audio_html:
315
  st.markdown(st.session_state.audio_html, unsafe_allow_html=True)
316
 
317
  st.markdown("---")
318
- st.markdown("⚠️ NOT medical advice. Seek professionals when needed.")
 
2
  Health Voice Assistant
3
  - Whisper ASR (incremental)
4
  - GPT health guidance (NON-diagnostic)
5
+ - Auto Text-to-Speech (autoplay, no button, no file saved)
6
 
7
  ⚠️ GENERAL guidance only – NOT medical advice
8
  """
 
14
  import textwrap
15
  import base64
16
  from io import BytesIO
 
17
 
 
18
  import numpy as np
19
  import torch
20
+ import requests
21
  import streamlit as st
22
 
23
  from transformers import pipeline
 
25
  from openai import OpenAI
26
  from gtts import gTTS
27
 
28
+ # ================= PAGE CONFIG =================
29
  st.set_page_config(page_title="Health Voice Assistant", layout="wide")
30
 
31
  # ================= SESSION STATE =================
32
+ if "transcript" not in st.session_state:
33
+ st.session_state.transcript = None
34
+ if "gpt_text" not in st.session_state:
35
+ st.session_state.gpt_text = None
36
+ if "audio_html" not in st.session_state:
37
+ st.session_state.audio_html = None
38
+ if "done" not in st.session_state:
39
+ st.session_state.done = False
40
 
41
  # ================= CONFIG =================
42
  MODEL_NAME = "openai/whisper-small.en"
 
55
  st.title("🩺 Health Voice-Based Assistant")
56
 
57
  MEDICAL_DISCLAIMER = """
58
+ ⚠️ **IMPORTANT MEDICAL DISCLAIMER**
59
+
60
+ This assistant is **NOT** a medical professional.
61
+ It does **NOT** diagnose diseases or prescribe medication.
62
+ Information provided is for **GENERAL guidance only**.
63
  """
64
 
65
  # ================= SIDEBAR =================
 
67
  st.sidebar.markdown(
68
  """
69
  1. Choose input method
70
+ 2. Upload audio **or** record via web
71
+ 3. App will automatically:
72
+ - Transcribe (or read transcript)
73
+ - Analyze with GPT
74
+ - Speak the guidance
 
75
  """
76
  )
77
  st.sidebar.markdown("---")
 
96
  pass
97
  return pipe
98
 
 
99
  pipe = load_pipeline()
100
 
101
  # ================= GPT CLIENT =================
 
105
  def format_text(text: str, width: int = 90) -> str:
106
  text = re.sub(r"\s+", " ", text).strip()
107
  sentences = re.split(r"(?<=[.!?])\s+", text)
108
+ paragraphs, buf = [], []
109
+ for s in sentences:
110
+ buf.append(s)
111
+ if len(buf) >= 3:
112
+ paragraphs.append(" ".join(buf))
113
+ buf = []
114
+ if buf:
115
+ paragraphs.append(" ".join(buf))
116
+ return "\n\n".join(textwrap.fill(p, width) for p in paragraphs)
117
 
118
  def merge_overlap(old: str, new: str, max_words: int = 10) -> str:
119
  if not old:
 
124
  return " ".join(a + b[k:])
125
  return old + " " + new
126
 
 
127
  def tts_bytes(text: str) -> BytesIO:
128
  mp3 = BytesIO()
129
  gTTS(text=text, lang="en").write_to_fp(mp3)
130
  mp3.seek(0)
131
  return mp3
132
 
133
+ # ================= FIREBASE =================
134
+ def fetch_firebase_transcript():
135
+ """
136
+ Read transcript text from Firebase Realtime Database
137
+ Path: /transcribe
138
+ """
139
+ try:
140
+ url = f"{FIREBASE_DB_URL}/transcribe.json"
141
+ res = requests.get(url, timeout=5)
142
+ res.raise_for_status()
143
+ data = res.json()
144
+
145
+ if not data:
146
+ return None
147
+
148
+ text = data.get("text", "").strip()
149
+ return text if text else None
150
 
151
+ except Exception as e:
152
+ st.error(f"Firebase read error: {e}")
153
+ return None
154
+
155
+ # ================= GPT =================
156
  def run_gpt(transcript: str) -> str:
157
  prompt = f"""
158
  You are a NON-diagnostic health assistant.
 
178
  )
179
  return res.choices[0].message.content.strip()
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  # ================= ASR =================
182
+ def transcribe(audio_bytes: bytes):
183
  sr = pipe.feature_extractor.sampling_rate
184
  audio = ffmpeg_read(audio_bytes, sr)
185
 
 
192
  chunk_samples = int(CHUNK_SEC * sr)
193
  overlap_samples = int(OVERLAP_SEC * sr)
194
 
195
+ total_chunks = max(1, (len(audio) + chunk_samples - 1) // chunk_samples)
196
  acc = ""
 
197
 
198
+ for i in range(total_chunks):
199
  start = max(0, i * chunk_samples - overlap_samples)
200
  end = min(len(audio), (i + 1) * chunk_samples + overlap_samples)
201
 
 
205
  "forced_decoder_ids": getattr(pipe, "_forced_decoder_ids", None)
206
  },
207
  )
 
 
208
 
209
+ acc = merge_overlap(acc, result["text"].strip())
210
+ yield acc, i + 1, total_chunks
211
 
212
  # ================= INPUT MODE =================
213
+ st.markdown("### 🎤 Input Mode")
214
 
215
+ mode = st.radio(
216
  "Choose input method:",
217
+ ["Upload audio file", "Live recording (via Firebase)"],
 
218
  )
219
 
220
  # ================= MODE 2: FIREBASE =================
221
+ if mode == "Live recording (via Firebase)":
222
+ st.info("Reading transcript from Firebase...")
223
+
224
+ firebase_text = fetch_firebase_transcript()
225
+
226
+ if firebase_text:
227
+ st.success("Transcript found. Processing with AI...")
228
+
229
+ st.session_state.audio_html = None
230
+ st.session_state.transcript = firebase_text
231
+
232
+ with st.spinner("Analyzing with GPT..."):
233
+ st.session_state.gpt_text = run_gpt(firebase_text)
234
 
235
+ voice = tts_bytes(st.session_state.gpt_text)
236
+ b64 = base64.b64encode(voice.read()).decode()
237
+ st.session_state.audio_html = f"""
238
+ <audio autoplay>
239
+ <source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
240
+ </audio>
241
+ """
242
 
243
+ st.session_state.done = True
244
+
245
+ else:
246
+ st.warning("No transcript found. Redirecting to recorder...")
247
  st.markdown(
248
+ """
249
+ <meta http-equiv="refresh" content="0; url=https://nolist-ssp-recordoption.hf.space">
 
 
 
 
250
  """,
251
  unsafe_allow_html=True,
252
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  # ================= MODE 1: UPLOAD AUDIO =================
255
+ if mode == "Upload audio file":
256
+ uploaded = st.file_uploader(
257
+ "Upload audio (wav, mp3, m4a, ogg, flac)",
258
+ type=["wav", "mp3", "m4a", "ogg", "flac"],
259
+ )
260
 
261
+ if uploaded:
262
+ audio_bytes = uploaded.read()
263
+ st.audio(audio_bytes)
264
+
265
+ if st.button("Transcribe"):
266
+ try:
267
+ st.session_state.audio_html = None
268
+
269
+ placeholder = st.empty()
270
+ progress = st.progress(0.0)
271
+
272
+ final_text = ""
273
+
274
+ for txt, i, total in transcribe(audio_bytes):
275
+ final_text = txt
276
+ placeholder.markdown(
277
+ f"```text\n{format_text(txt)}\n```"
278
+ )
279
+ progress.progress(i / total)
280
+
281
+ st.session_state.transcript = final_text
282
+
283
+ with st.spinner("Analyzing with GPT..."):
284
+ st.session_state.gpt_text = run_gpt(final_text)
285
+
286
+ voice = tts_bytes(st.session_state.gpt_text)
287
+ b64 = base64.b64encode(voice.read()).decode()
288
+ st.session_state.audio_html = f"""
289
+ <audio autoplay>
290
+ <source src="data:audio/mp3;base64,{b64}" type="audio/mp3">
291
+ </audio>
292
+ """
293
+
294
+ st.session_state.done = True
295
 
296
+ except Exception as e:
297
+ st.error(f"Failed:\n{e}\n{traceback.format_exc()}")
298
 
299
+ # ================= RENDER RESULT =================
300
  if st.session_state.transcript:
301
  st.markdown("### 📝 Transcript")
302
  st.markdown(f"```text\n{format_text(st.session_state.transcript)}\n```")
 
309
  st.markdown(st.session_state.audio_html, unsafe_allow_html=True)
310
 
311
  st.markdown("---")
312
+ st.markdown("⚠️ **NOT medical advice. Seek professional help when needed.**")