Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -27,20 +27,218 @@ def fetch_sheet_data(sheet_url):
|
|
27 |
return data
|
28 |
|
29 |
|
|
|
30 |
data = fetch_sheet_data(SHEET_URL)
|
31 |
|
32 |
-
# Load the project using the hardcoded project code
|
33 |
-
current_project = [row for row in data if row["Project Code"] == PROJECT_CODE]
|
34 |
-
if current_project:
|
35 |
-
current_project = current_project[0]
|
36 |
-
prompts = [current_project[f"Prompt{i+1}"] for i in range(4) if current_project.get(f"Prompt{i+1}")]
|
37 |
-
recording_status = [False] * len(prompts) # Initialize recording status for each prompt
|
38 |
-
responses = [None] * len(prompts)
|
39 |
-
else:
|
40 |
-
prompts = []
|
41 |
|
42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
<style>
|
45 |
/* Body Styles */
|
46 |
.gr-container {
|
@@ -64,6 +262,7 @@ custom_html="""
|
|
64 |
padding: 10px 10px;
|
65 |
border-radius: 10px;
|
66 |
font-size:20px;
|
|
|
67 |
}
|
68 |
|
69 |
.gr-audio button[title="Clear"] {
|
@@ -140,7 +339,7 @@ custom_html="""
|
|
140 |
padding: 10px;
|
141 |
background-color: #FF0000; /* Light red background for alert */
|
142 |
}
|
143 |
-
.
|
144 |
font-size: 16px;
|
145 |
color: black;
|
146 |
margin-top: 2px;
|
@@ -184,284 +383,70 @@ custom_html="""
|
|
184 |
}
|
185 |
</style>
|
186 |
"""
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
current_project = None
|
200 |
-
current_prompt_index = 0
|
201 |
-
prompts = []
|
202 |
-
recording_status = []
|
203 |
-
responses = []
|
204 |
-
|
205 |
-
# Load the project using the hardcoded project code
|
206 |
-
current_project = [row for row in data if row["Project Code"] == PROJECT_CODE]
|
207 |
-
if current_project:
|
208 |
-
current_project = current_project[0]
|
209 |
-
prompts = [current_project[f"Prompt{i+1}"] for i in range(4) if current_project.get(f"Prompt{i+1}")]
|
210 |
-
recording_status = [False] * len(prompts) # Initialize recording status for each prompt
|
211 |
-
responses = [None] * len(prompts)
|
212 |
-
else:
|
213 |
-
prompts = []
|
214 |
-
|
215 |
-
# Function to transcribe audio files and save them locally
|
216 |
-
def transcribe_audio(local_file_path):
|
217 |
-
"""Transcribes audio data from a Google Cloud Storage bucket."""
|
218 |
-
|
219 |
-
with open(local_file_path, "rb") as audio_file:
|
220 |
-
content = audio_file.read()
|
221 |
-
|
222 |
-
client = speech.SpeechClient()
|
223 |
-
audio = speech.RecognitionAudio(content=content)
|
224 |
-
config = speech.RecognitionConfig(
|
225 |
-
encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
|
226 |
-
sample_rate_hertz=44100, # Adjust if needed
|
227 |
-
language_code="en-US",
|
228 |
-
)
|
229 |
-
response = client.recognize(config=config, audio=audio)
|
230 |
-
return response.results[0].alternatives[0].transcript if response.results else "No transcription available."
|
231 |
-
|
232 |
-
# function to upload responses and generate transcripts
|
233 |
-
def upload_to_gcs_and_cleanup():
|
234 |
-
try:
|
235 |
-
# Initialize GCS client
|
236 |
-
client = storage.Client(project="story-legacy-442314")
|
237 |
-
bucket = client.bucket(GCS_BUCKET_NAME)
|
238 |
-
|
239 |
-
# Walk through the responses directory
|
240 |
-
for root, _, files in os.walk(f"responses/{PROJECT_CODE}"):
|
241 |
-
for file in files:
|
242 |
-
local_path = os.path.join(root, file)
|
243 |
-
|
244 |
-
# Define the relative path for GCS
|
245 |
-
relative_path = os.path.relpath(local_path, f"responses/{PROJECT_CODE}")
|
246 |
-
gcs_path = f"{PROJECT_CODE}/responses/{relative_path}"
|
247 |
-
|
248 |
-
# Upload the file to GCS
|
249 |
-
blob = bucket.blob(gcs_path)
|
250 |
-
blob.upload_from_filename(local_path)
|
251 |
-
print(f"Uploaded {local_path} to {gcs_path}")
|
252 |
-
|
253 |
-
# Generate the transcript
|
254 |
-
transcript_text = transcribe_audio(local_path)
|
255 |
-
|
256 |
-
# Save the transcript in GCS under the transcripts folder with the new structure
|
257 |
-
transcript_relative_path = relative_path.replace("responses/", "") # Remove "responses/" from path
|
258 |
-
transcript_gcs_path = f"{PROJECT_CODE}/transcripts/{transcript_relative_path.replace(os.path.splitext(file)[1], '.txt')}"
|
259 |
-
transcript_blob = bucket.blob(transcript_gcs_path)
|
260 |
-
transcript_blob.upload_from_string(transcript_text)
|
261 |
-
print(f"Transcript uploaded to {transcript_gcs_path}")
|
262 |
-
|
263 |
-
# Delete the local response file
|
264 |
-
os.remove(local_path)
|
265 |
-
|
266 |
-
# Remove the responses directory after successful upload
|
267 |
-
response_dir = f"responses/{PROJECT_CODE}"
|
268 |
-
if os.path.exists(response_dir):
|
269 |
-
shutil.rmtree(response_dir)
|
270 |
-
print(f"Local directory {response_dir} has been deleted.")
|
271 |
-
|
272 |
-
return True, "All responses and transcripts have been uploaded to GCS, and local files deleted."
|
273 |
-
except Exception as e:
|
274 |
-
print(f"Error during GCS upload: {e}")
|
275 |
-
return False, f"Error during GCS upload: {str(e)}"
|
276 |
-
|
277 |
-
# Background task function
|
278 |
-
def async_upload_to_gcs_and_cleanup():
|
279 |
-
success, message = upload_to_gcs_and_cleanup()
|
280 |
-
print(message)
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
def submit_audio(audio_path):
|
285 |
-
global current_prompt_index
|
286 |
-
print(f"Received audio_path: {audio_path}") # Debugging line to check the value
|
287 |
-
|
288 |
-
# Check if the response has already been submitted
|
289 |
-
if recording_status[current_prompt_index]:
|
290 |
-
return (
|
291 |
-
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div> <div class="prompt-text">{prompts[current_prompt_index]}</div>""",
|
292 |
-
gr.update(value=f"""<div class="sucess-alert">⚠️ Response for Prompt {current_prompt_index + 1} has already been submitted.</div>""", visible=True),
|
293 |
-
)
|
294 |
-
|
295 |
-
# Check if no audio is recorded
|
296 |
-
if audio_path is None:
|
297 |
-
return (
|
298 |
-
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
299 |
-
gr.update(value="", visible=True),
|
300 |
-
)
|
301 |
-
|
302 |
-
try:
|
303 |
-
# Ensure the directory exists and move the file
|
304 |
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
305 |
-
folder_path = f"responses/{PROJECT_CODE}/Prompt{current_prompt_index + 1}"
|
306 |
-
os.makedirs(folder_path, exist_ok=True)
|
307 |
-
file_path = f"{folder_path}/response_{timestamp}.wav"
|
308 |
-
shutil.move(audio_path, file_path)
|
309 |
-
|
310 |
-
# Update recording status and responses
|
311 |
-
recording_status[current_prompt_index] = True
|
312 |
-
responses[current_prompt_index] = file_path
|
313 |
-
|
314 |
-
return (
|
315 |
-
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
316 |
-
gr.update(value=f"""<div class = "sucess-alert">Response for Prompt {current_prompt_index + 1} has been submitted successfully!</div>""", visible=True),
|
317 |
-
)
|
318 |
-
except Exception as e:
|
319 |
-
print(f"Error occurred during submission: {e}") # Debugging line for exception
|
320 |
-
return (
|
321 |
-
f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
322 |
-
gr.update(value=f"""<div class= "fail-alert">⚠️ An error occurred during submission: {str(e)}</div>""", visible=True),
|
323 |
-
)
|
324 |
-
|
325 |
-
def save_and_next():
|
326 |
-
global current_prompt_index
|
327 |
-
if not recording_status[current_prompt_index]:
|
328 |
-
return (
|
329 |
-
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1} of {len(prompts)}:</div><div class="prompt-text">{prompts[current_prompt_index]}</div>""",
|
330 |
-
gr.update(value=f"""<div class="warning-alert">⚠️ Please record and submit your response before moving to the next prompt.</div>""", visible=True),
|
331 |
-
gr.update(visible=True),
|
332 |
-
gr.update(visible=True),
|
333 |
-
gr.update(visible=True),
|
334 |
-
|
335 |
-
)
|
336 |
-
|
337 |
-
if current_prompt_index < len(prompts) - 1:
|
338 |
-
current_prompt_index += 1
|
339 |
-
is_last_prompt = current_prompt_index == len(prompts) - 1
|
340 |
-
next_button_text = "Finish" if is_last_prompt else f"Next Prompt ({current_prompt_index + 2} of {len(prompts)})"
|
341 |
-
return (
|
342 |
-
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1} of {len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
343 |
-
gr.update(value="", visible=False), # Clear the alert message
|
344 |
-
gr.update(value=None, visible=True),
|
345 |
-
gr.update(value=next_button_text, visible=True),
|
346 |
-
gr.update(visible=True),
|
347 |
-
|
348 |
-
)
|
349 |
-
else:
|
350 |
-
# "Finish" button clicked
|
351 |
-
threading.Thread(target=async_upload_to_gcs_and_cleanup, daemon=True).start()
|
352 |
-
|
353 |
-
# print(upload_to_gcs_and_cleanup())
|
354 |
-
# alert_class = "success-alert" if success else "fail-alert"
|
355 |
-
return (
|
356 |
-
"Thank you for your participation in this research, all of your stories have been submitted. We place great value on your stories and will treat them with the respect they deserve.",
|
357 |
-
gr.update(value="", visible=True),
|
358 |
-
gr.update(visible=False),
|
359 |
-
gr.update(visible=False),
|
360 |
-
gr.update(visible=False),
|
361 |
-
|
362 |
-
)
|
363 |
-
|
364 |
-
def erase_and_record():
|
365 |
-
global current_prompt_index
|
366 |
-
# Check if a response exists for the current prompt
|
367 |
-
if responses[current_prompt_index]:
|
368 |
-
# Delete the file
|
369 |
-
try:
|
370 |
-
os.remove(responses[current_prompt_index])
|
371 |
-
except FileNotFoundError:
|
372 |
-
pass # Ignore if the file doesn't exist
|
373 |
-
responses[current_prompt_index] = None # Reset the response
|
374 |
-
recording_status[current_prompt_index] = False # Reset recording status
|
375 |
-
|
376 |
-
# Return updated prompt and alert message
|
377 |
-
return (
|
378 |
-
f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
379 |
-
gr.update(value=None, visible=True), # Reset the audio input
|
380 |
-
gr.update(value=f"""<div class= "sucess-alert">Your previous response has been erased. Please re-record your response.</div>""", visible=True), # Updated alert message
|
381 |
-
)
|
382 |
-
else:
|
383 |
-
return (
|
384 |
-
f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
385 |
-
gr.update(visible=True), # Reset the audio input
|
386 |
-
gr.update(value=f"""<div class= "warning-alert">⚠️ Please record and submit your response first.</div>""", visible=True), # Updated alert message
|
387 |
-
)
|
388 |
-
# Gradio Interface
|
389 |
def gradio_app():
|
390 |
-
|
391 |
-
|
|
|
|
|
392 |
with gr.Row():
|
393 |
-
with gr.Blocks(theme=
|
394 |
-
gr.Markdown(
|
395 |
-
|
|
|
|
|
|
|
|
|
|
|
396 |
with gr.Row():
|
397 |
alert_box = gr.Markdown("", elem_id="alert-box", visible=False)
|
|
|
398 |
with gr.Row():
|
399 |
prompt_display = gr.Markdown(
|
400 |
-
f"""<div class ="prompt-class">Prompt
|
401 |
-
""
|
|
|
402 |
)
|
|
|
403 |
with gr.Row():
|
404 |
# Create a hidden label to dynamically update microphone status
|
405 |
record_audio = gr.Audio(
|
406 |
-
sources="microphone",
|
407 |
-
type="filepath",
|
408 |
label="Record your response",
|
409 |
-
elem_classes=["gr-audio"]
|
410 |
-
|
411 |
-
|
412 |
-
erase_button = gr.Button("Erase & Re - Record Current Prompt", elem_classes=["gr-erase_button"])
|
413 |
-
save_next_button = gr.Button(f"Next Prompt (2 of {len(prompts)})", elem_classes=["gr-next_button"])
|
414 |
|
415 |
-
|
|
|
|
|
416 |
|
417 |
-
|
418 |
-
|
419 |
-
inputs=[],
|
420 |
-
outputs=[
|
421 |
-
prompt_display,
|
422 |
-
alert_box,
|
423 |
-
record_audio,
|
424 |
-
save_next_button,
|
425 |
-
erase_button,
|
426 |
-
|
427 |
-
],
|
428 |
)
|
429 |
erase_button.click(
|
430 |
-
erase_and_record,
|
431 |
-
inputs=[],
|
432 |
-
outputs=[
|
433 |
-
prompt_display,
|
434 |
-
record_audio,
|
435 |
-
alert_box,
|
436 |
-
|
437 |
-
],
|
438 |
)
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
# Load the project using the hardcoded project code
|
449 |
-
current_project = [row for row in data if row["Project Code"] == PROJECT_CODE]
|
450 |
-
if current_project:
|
451 |
-
current_project = current_project[0]
|
452 |
-
prompts = [current_project[f"Prompt{i+1}"] for i in range(4) if current_project.get(f"Prompt{i+1}")]
|
453 |
-
recording_status = [False] * len(prompts)
|
454 |
-
responses = [None] * len(prompts)
|
455 |
-
else:
|
456 |
-
prompts = []
|
457 |
-
|
458 |
-
# Reset the current prompt index
|
459 |
-
current_prompt_index = 0
|
460 |
-
print("State has been initialized.")
|
461 |
-
|
462 |
-
return demo
|
463 |
-
|
464 |
-
# Run the app
|
465 |
-
# reset_state()
|
466 |
app = gradio_app()
|
467 |
-
app.launch()
|
|
|
27 |
return data
|
28 |
|
29 |
|
30 |
+
# Load Google Sheet data
|
31 |
data = fetch_sheet_data(SHEET_URL)
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
+
# Helper function to initialize a new session state
|
35 |
+
def initialize_session_state():
|
36 |
+
current_project = [row for row in data if row["Project Code"] == PROJECT_CODE]
|
37 |
+
if current_project:
|
38 |
+
current_project = current_project[0]
|
39 |
+
prompts = [
|
40 |
+
current_project[f"Prompt{i+1}"] for i in range(4) if current_project.get(f"Prompt{i+1}")
|
41 |
+
]
|
42 |
+
recording_status = [False] * len(prompts)
|
43 |
+
responses = [None] * len(prompts)
|
44 |
+
else:
|
45 |
+
prompts = []
|
46 |
+
recording_status = []
|
47 |
+
responses = []
|
48 |
+
|
49 |
+
return {
|
50 |
+
"current_prompt_index": 0,
|
51 |
+
"prompts": prompts,
|
52 |
+
"recording_status": recording_status,
|
53 |
+
"responses": responses,
|
54 |
+
}
|
55 |
+
|
56 |
+
|
57 |
+
def submit_audio(audio_path, state):
|
58 |
+
current_prompt_index = state["current_prompt_index"]
|
59 |
+
prompts = state["prompts"]
|
60 |
+
recording_status = state["recording_status"]
|
61 |
+
responses = state["responses"]
|
62 |
+
|
63 |
+
# Check if response for current prompt is already recorded
|
64 |
+
if recording_status[current_prompt_index]:
|
65 |
+
|
66 |
+
return (
|
67 |
+
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div>
|
68 |
+
<div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
69 |
+
gr.update(value=responses[current_prompt_index]), # Show already recorded response
|
70 |
+
gr.update(value=f"""<div class="success-alert"> Response for Prompt {current_prompt_index + 1} has been recorded.</div>""", visible=True),
|
71 |
+
state,
|
72 |
+
)
|
73 |
+
|
74 |
+
# Ensure audio is available before saving
|
75 |
+
if audio_path is None:
|
76 |
+
return (
|
77 |
+
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div>
|
78 |
+
<div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
79 |
+
gr.update(value=None), # Reset the audio input
|
80 |
+
gr.update(value="", visible=True),
|
81 |
+
state,
|
82 |
+
)
|
83 |
+
|
84 |
+
try:
|
85 |
+
# Ensure the directory exists and move the file
|
86 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
87 |
+
folder_path = f"responses/{PROJECT_CODE}/Prompt{current_prompt_index + 1}"
|
88 |
+
os.makedirs(folder_path, exist_ok=True)
|
89 |
+
file_path = f"{folder_path}/response_{timestamp}.wav"
|
90 |
+
shutil.move(audio_path, file_path)
|
91 |
+
|
92 |
+
# Update recording status and responses
|
93 |
+
recording_status[current_prompt_index] = True
|
94 |
+
responses[current_prompt_index] = file_path
|
95 |
+
|
96 |
+
# Update state
|
97 |
+
state["recording_status"] = recording_status
|
98 |
+
state["responses"] = responses
|
99 |
+
|
100 |
+
return (
|
101 |
+
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div>
|
102 |
+
<div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
103 |
+
gr.update(value=file_path), # Show the saved response for playback
|
104 |
+
gr.update(value=f"""<div class="success-alert">Response for Prompt {current_prompt_index + 1} has been submitted successfully!</div>""", visible=True),
|
105 |
+
state,
|
106 |
+
)
|
107 |
+
except Exception as e:
|
108 |
+
print(f"Error occurred during submission: {e}")
|
109 |
+
return (
|
110 |
+
f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div>
|
111 |
+
<div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
112 |
+
gr.update(value=None), # Reset the audio input
|
113 |
+
gr.update(value=f"""<div class="fail-alert">⚠️ An error occurred during submission: {str(e)}</div>""", visible=True),
|
114 |
+
state,
|
115 |
+
)
|
116 |
+
|
117 |
+
def erase_and_record(state):
|
118 |
+
current_prompt_index = state["current_prompt_index"]
|
119 |
+
prompts = state["prompts"]
|
120 |
+
recording_status = state["recording_status"]
|
121 |
+
responses = state["responses"]
|
122 |
+
|
123 |
+
# Check if a response exists for the current prompt
|
124 |
+
if responses[current_prompt_index]:
|
125 |
+
try:
|
126 |
+
# Delete the recorded file
|
127 |
+
os.remove(responses[current_prompt_index])
|
128 |
+
except FileNotFoundError:
|
129 |
+
pass # Ignore if the file doesn't exist
|
130 |
+
|
131 |
+
# Reset the response and recording status
|
132 |
+
responses[current_prompt_index] = None
|
133 |
+
recording_status[current_prompt_index] = False
|
134 |
+
|
135 |
+
# Update state
|
136 |
+
state["recording_status"] = recording_status
|
137 |
+
state["responses"] = responses
|
138 |
+
|
139 |
+
return (
|
140 |
+
f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div>
|
141 |
+
<div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
142 |
+
gr.update(value=None, visible=True), # Reset the audio input
|
143 |
+
gr.update(value=f"""<div class="success-alert">Your previous response has been erased. Please re-record your response.</div>""", visible=True),
|
144 |
+
state,
|
145 |
+
)
|
146 |
+
else:
|
147 |
+
return (
|
148 |
+
f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div>
|
149 |
+
<div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
150 |
+
gr.update(visible=True), # Reset the audio input
|
151 |
+
gr.update(value=f"""<div class="warning-alert">⚠️ Please record your response first.</div>""", visible=True),
|
152 |
+
state,
|
153 |
+
)
|
154 |
+
def save_and_next(state):
|
155 |
+
current_prompt_index = state["current_prompt_index"]
|
156 |
+
prompts = state["prompts"]
|
157 |
+
recording_status = state["recording_status"]
|
158 |
+
# Check if the response for the current prompt is recorded
|
159 |
+
if not recording_status[current_prompt_index]:
|
160 |
+
return (
|
161 |
+
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1} of {len(prompts)}:</div><div class="prompt-text">{prompts[current_prompt_index]}</div>""",
|
162 |
+
gr.update(value=f"""<div class="warning-alert">⚠️ Please record your response before moving to the next prompt.</div>""", visible=True),
|
163 |
+
gr.update(value=None, visible=True), # Audio input is still visible
|
164 |
+
gr.update(value=f"Next Prompt ({current_prompt_index + 2} of {len(prompts)})", visible=True), # Update button text
|
165 |
+
gr.update(visible=True), # Erase button should still be visible
|
166 |
+
state,
|
167 |
+
)
|
168 |
+
|
169 |
+
# Increment prompt index if not the last prompt
|
170 |
+
if current_prompt_index < len(prompts) - 1:
|
171 |
+
current_prompt_index += 1
|
172 |
+
state["current_prompt_index"] = current_prompt_index # Update state with new prompt index
|
173 |
+
|
174 |
+
is_last_prompt = current_prompt_index == len(prompts) - 1
|
175 |
+
next_button_text = "Finish" if is_last_prompt else f"Next Prompt ({current_prompt_index + 2} of {len(prompts)})"
|
176 |
+
|
177 |
+
return (
|
178 |
+
f"""<div class ="prompt-class">Prompt {current_prompt_index + 1} of {len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
|
179 |
+
gr.update(value="", visible=False), # Clear the alert message
|
180 |
+
gr.update(value=None, visible=True), # Show the audio input for the next prompt
|
181 |
+
gr.update(value=next_button_text, visible=True), # Update button text
|
182 |
+
gr.update(visible=True), # Keep the Erase button visible until the last prompt
|
183 |
+
state,
|
184 |
+
)
|
185 |
+
else:
|
186 |
+
# "Finish" button clicked
|
187 |
+
threading.Thread(target=async_upload_to_gcs_and_cleanup, daemon=True).start()
|
188 |
+
|
189 |
+
# Hide the "Next" button and "Erase" button after Finish
|
190 |
+
return (
|
191 |
+
"Thank you for your participation in this research, all of your stories have been submitted. We place great value on your stories and will treat them with the respect they deserve.",
|
192 |
+
gr.update(value="", visible=True),
|
193 |
+
gr.update(visible=False), # Hide the audio input
|
194 |
+
gr.update(visible=False), # Hide the "Next" button
|
195 |
+
gr.update(visible=False), # Hide the "Erase" button
|
196 |
+
state,
|
197 |
+
)
|
198 |
+
|
199 |
+
GCS_BUCKET_NAME = "userrecordings"
|
200 |
+
|
201 |
+
# function to upload responses and generate transcripts
|
202 |
+
def upload_to_gcs_and_cleanup():
|
203 |
+
try:
|
204 |
+
# Initialize GCS client
|
205 |
+
client = storage.Client(project="story-legacy-442314")
|
206 |
+
bucket = client.bucket(GCS_BUCKET_NAME)
|
207 |
+
|
208 |
+
# Walk through the responses directory
|
209 |
+
for root, _, files in os.walk(f"responses/{PROJECT_CODE}"):
|
210 |
+
for file in files:
|
211 |
+
local_path = os.path.join(root, file)
|
212 |
+
|
213 |
+
# Define the relative path for GCS
|
214 |
+
relative_path = os.path.relpath(local_path, f"responses/{PROJECT_CODE}")
|
215 |
+
gcs_path = f"{PROJECT_CODE}/responses/{relative_path}"
|
216 |
+
|
217 |
+
# Upload the file to GCS
|
218 |
+
blob = bucket.blob(gcs_path)
|
219 |
+
blob.upload_from_filename(local_path)
|
220 |
+
print(f"Uploaded {local_path} to {gcs_path}")
|
221 |
+
|
222 |
+
# Delete the local response file
|
223 |
+
os.remove(local_path)
|
224 |
+
|
225 |
+
# Remove the responses directory after successful upload
|
226 |
+
response_dir = f"responses/{PROJECT_CODE}"
|
227 |
+
if os.path.exists(response_dir):
|
228 |
+
shutil.rmtree(response_dir)
|
229 |
+
print(f"Local directory {response_dir} has been deleted.")
|
230 |
|
231 |
+
return True, "All responses and transcripts have been uploaded to GCS, and local files deleted."
|
232 |
+
except Exception as e:
|
233 |
+
print(f"Error during GCS upload: {e}")
|
234 |
+
return False, f"Error during GCS upload: {str(e)}"
|
235 |
+
|
236 |
+
# Background task function
|
237 |
+
def async_upload_to_gcs_and_cleanup():
|
238 |
+
success, message = upload_to_gcs_and_cleanup()
|
239 |
+
print(message)
|
240 |
+
|
241 |
+
custom_html="""
|
242 |
<style>
|
243 |
/* Body Styles */
|
244 |
.gr-container {
|
|
|
262 |
padding: 10px 10px;
|
263 |
border-radius: 10px;
|
264 |
font-size:20px;
|
265 |
+
background-color: #dfdf8f
|
266 |
}
|
267 |
|
268 |
.gr-audio button[title="Clear"] {
|
|
|
339 |
padding: 10px;
|
340 |
background-color: #FF0000; /* Light red background for alert */
|
341 |
}
|
342 |
+
.success-alert {
|
343 |
font-size: 16px;
|
344 |
color: black;
|
345 |
margin-top: 2px;
|
|
|
383 |
}
|
384 |
</style>
|
385 |
"""
|
386 |
+
# JavaScript to detect microphone access on page load
|
387 |
+
js_code = """
|
388 |
+
async () => {
|
389 |
+
try {
|
390 |
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
391 |
+
const mic = devices.find(device => device.kind === 'audioinput');
|
392 |
+
return mic ? "Microphone Array / Default" : "No Microphone Found";
|
393 |
+
} catch (err) {
|
394 |
+
return "No Microphone Found";
|
395 |
+
}
|
396 |
+
}
|
397 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
def gradio_app():
|
399 |
+
with gr.Blocks(theme="allenai/gradio-theme",css = custom_html) as demo:
|
400 |
+
# Initialize session state
|
401 |
+
state = gr.State(initialize_session_state())
|
402 |
+
|
403 |
with gr.Row():
|
404 |
+
with gr.Blocks(theme="allenai/gradio-theme",css = custom_html):
|
405 |
+
gr.Markdown(
|
406 |
+
"""# StoryLegacy""", elem_classes=["gr-story_legacy_heading"]
|
407 |
+
)
|
408 |
+
gr.Markdown(
|
409 |
+
f"""# Project: {PROJECT_CODE}""", elem_classes=["gr-project_code"]
|
410 |
+
)
|
411 |
+
|
412 |
with gr.Row():
|
413 |
alert_box = gr.Markdown("", elem_id="alert-box", visible=False)
|
414 |
+
|
415 |
with gr.Row():
|
416 |
prompt_display = gr.Markdown(
|
417 |
+
f"""<div class ="prompt-class">Prompt 1 of {len(state.value['prompts'])}:</div>
|
418 |
+
<div class="prompt-text">{state.value['prompts'][0] if state.value['prompts'] else 'No prompts available.'}</div>""",
|
419 |
+
elem_classes=["gr-prompt"],
|
420 |
)
|
421 |
+
|
422 |
with gr.Row():
|
423 |
# Create a hidden label to dynamically update microphone status
|
424 |
record_audio = gr.Audio(
|
425 |
+
sources="microphone",
|
426 |
+
type="filepath",
|
427 |
label="Record your response",
|
428 |
+
elem_classes=["gr-audio"],
|
429 |
+
)
|
430 |
+
demo.load(js=js_code, inputs=[], outputs=[record_audio])
|
|
|
|
|
431 |
|
432 |
+
with gr.Row():
|
433 |
+
erase_button = gr.Button("Erase & Re-record Current Prompt", elem_classes=["gr-erase_button"])
|
434 |
+
save_next_button = gr.Button(f"Next Prompt (2 of {len(state.value['prompts'])})", elem_classes=["gr-next_button"])
|
435 |
|
436 |
+
record_audio.stop_recording(
|
437 |
+
submit_audio, inputs=[record_audio, state], outputs=[prompt_display, record_audio, alert_box, state]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
438 |
)
|
439 |
erase_button.click(
|
440 |
+
erase_and_record, inputs=state, outputs=[prompt_display, record_audio, alert_box, state]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
441 |
)
|
442 |
+
save_next_button.click(
|
443 |
+
save_and_next, inputs=state, outputs=[prompt_display, alert_box, record_audio, save_next_button,erase_button, state]
|
444 |
+
)
|
445 |
+
|
446 |
+
|
447 |
+
|
448 |
+
return demo
|
449 |
+
|
450 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
451 |
app = gradio_app()
|
452 |
+
app.launch(share=True)
|