cp557 commited on
Commit
557cacf
·
verified ·
1 Parent(s): 7f8dde5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -29
app.py CHANGED
@@ -1,12 +1,10 @@
1
  #!/usr/bin/env python3
2
  """
3
  Launches a single page Gradio app that:
4
- 1. Accepts a topic/question.
5
  2. Generates slide markdown + TTS for each slide.
6
  3. Lets the user page through slides and hear the narration.
7
-
8
- Requires: generate_slideshow.py in the same directory and a valid
9
- GEMINI_KEY in your environment.
10
  """
11
 
12
  import asyncio
@@ -30,6 +28,19 @@ custom_css = """
30
  .input-row {
31
  margin-bottom: 10px;
32
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  .demo-row {
34
  margin-bottom: 20px;
35
  display: flex;
@@ -122,11 +133,14 @@ function animateSlideTransition() {
122
  }
123
  """
124
 
125
- async def generate_presentation_async(topic: str, session_id=None):
126
  """Async version: Generate slides, audio, and images for all slides; initialise UI with slide 0."""
127
  topic = (topic or "").strip()
128
- # Empty topic check is now handled in _run_with_new_session
129
- # Notification is shown from the button click handler
 
 
 
130
 
131
  # Create or get a session ID
132
  if session_id is None:
@@ -137,8 +151,8 @@ async def generate_presentation_async(topic: str, session_id=None):
137
  active_sessions[session_id] = {}
138
  active_sessions[session_id]["temp_dir"] = tempfile.mkdtemp(prefix=f"gradio_session_{session_id}_")
139
 
140
- # Call the async version with the session ID
141
- slides, audio_files, slide_images = await generate_slideshow_with_audio_async(topic, session_id=session_id)
142
 
143
  # Basic sanity - keep list lengths aligned for audio
144
  if len(audio_files) < len(slides):
@@ -160,10 +174,10 @@ async def generate_presentation_async(topic: str, session_id=None):
160
  return slides, audio_files, slide_images, 0, slides[0], audio_files[0], initial_image, progress_text, session_id
161
 
162
 
163
- def generate_presentation(topic: str, session_id=None):
164
  """Synchronous wrapper for the async presentation generator."""
165
  # Run the async function and handle empty topic case
166
- return asyncio.run(generate_presentation_async(topic, session_id=session_id))
167
 
168
 
169
  def next_slide(slides, audio, images, idx, session_id):
@@ -306,10 +320,14 @@ atexit.register(cleanup_all_sessions)
306
  dino_audio_cache = {}
307
  dino_image_cache = {}
308
 
309
- def load_rise_fall_slideshow():
310
  """Load cached dinosaur slideshow demo and hide the demo button and instruction."""
311
  global dino_audio_cache, dino_image_cache
312
 
 
 
 
 
313
  # Clear any previous cache
314
  dino_audio_cache = {}
315
  dino_image_cache = {}
@@ -420,6 +438,22 @@ with gr.Blocks(
420
  )
421
 
422
  with gr.Column(elem_classes="container"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  # First row for topic and generate button - increased horizontal width
424
  with gr.Row(elem_classes="input-row"):
425
  topic_box = gr.Textbox(
@@ -460,7 +494,7 @@ with gr.Blocks(
460
  session_state = gr.State(None)
461
 
462
  # Wiring
463
- def prepare_for_generation(topic, session_id):
464
  """First step: clear the view and prepare for generation"""
465
  # First check if the topic is empty
466
  if not (topic or "").strip():
@@ -475,9 +509,34 @@ with gr.Blocks(
475
  False # should_generate
476
  )
477
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
  # Validate the topic using the Gemini Flash input guard
479
- if not validate_topic(topic):
480
- gr.Info("Please enter a valid topic or question.")
 
 
 
 
 
 
 
 
 
 
 
 
481
  return (
482
  [], [], [], 0, "", None, None, "", session_id,
483
  gr.update(visible=True), # Show topic box
@@ -501,7 +560,7 @@ with gr.Blocks(
501
  True # should_generate
502
  )
503
 
504
- def _run_with_new_session(topic, session_id, should_generate):
505
  """Second step: actually generate the slideshow"""
506
  if not should_generate:
507
  # This case should ideally not be hit if UI updates from prepare_for_generation are correct
@@ -516,15 +575,27 @@ with gr.Blocks(
516
  False # should_generate (though this output isn't strictly used here, keeping tuple size consistent)
517
  )
518
 
519
- results = generate_presentation(topic, session_id)
520
- return (*results,
521
- gr.update(visible=False),
522
- gr.update(visible=False),
523
- gr.update(value="Generate", interactive=True),
524
- gr.update(visible=False),
525
- gr.update(visible=False),
526
- True # should_generate (maintaining tuple size, actual value less critical here)
527
- )
 
 
 
 
 
 
 
 
 
 
 
 
528
 
529
  # Two-step process for generation
530
  # 1. First clear the UI & validate
@@ -532,7 +603,7 @@ with gr.Blocks(
532
 
533
  gen_btn.click(
534
  prepare_for_generation,
535
- inputs=[topic_box, session_state],
536
  outputs=[
537
  slides_state,
538
  audio_state,
@@ -552,7 +623,7 @@ with gr.Blocks(
552
  ]
553
  ).then( # 2. Then (conditionally) generate the slideshow
554
  _run_with_new_session,
555
- inputs=[topic_box, session_state, should_generate_state],
556
  outputs=[
557
  slides_state,
558
  audio_state,
@@ -587,11 +658,11 @@ with gr.Blocks(
587
  # Load cached demo slideshow
588
  demo_btn.click(
589
  load_rise_fall_slideshow,
590
- inputs=[],
591
  outputs=[slides_state, audio_state, images_state, index_state, slide_markdown, audio_player, title_image,
592
  progress_indicator, session_state, demo_btn, demo_instruction],
593
  )
594
 
595
 
596
  if __name__ == "__main__":
597
- demo.launch()
 
1
  #!/usr/bin/env python3
2
  """
3
  Launches a single page Gradio app that:
4
+ 1. Accepts a topic/question and Gemini API key.
5
  2. Generates slide markdown + TTS for each slide.
6
  3. Lets the user page through slides and hear the narration.
7
+ Requires: generate_slideshow.py in the same directory
 
 
8
  """
9
 
10
  import asyncio
 
28
  .input-row {
29
  margin-bottom: 10px;
30
  }
31
+ .api-key-section {
32
+ margin-bottom: 20px;
33
+ padding: 15px;
34
+ border: 1px solid #ddd;
35
+ border-radius: 8px;
36
+ background-color: #f9f9f9;
37
+ }
38
+ .api-key-note {
39
+ font-size: 0.9em;
40
+ color: #666;
41
+ margin-top: 5px;
42
+ line-height: 1.5;
43
+ }
44
  .demo-row {
45
  margin-bottom: 20px;
46
  display: flex;
 
133
  }
134
  """
135
 
136
+ async def generate_presentation_async(topic: str, api_key: str, session_id=None):
137
  """Async version: Generate slides, audio, and images for all slides; initialise UI with slide 0."""
138
  topic = (topic or "").strip()
139
+ api_key = (api_key or "").strip()
140
+
141
+ # Validate API key
142
+ if not api_key:
143
+ raise gr.Error("Please enter your Gemini API key")
144
 
145
  # Create or get a session ID
146
  if session_id is None:
 
151
  active_sessions[session_id] = {}
152
  active_sessions[session_id]["temp_dir"] = tempfile.mkdtemp(prefix=f"gradio_session_{session_id}_")
153
 
154
+ # Call the async version with the session ID and API key
155
+ slides, audio_files, slide_images = await generate_slideshow_with_audio_async(topic, api_key, session_id=session_id)
156
 
157
  # Basic sanity - keep list lengths aligned for audio
158
  if len(audio_files) < len(slides):
 
174
  return slides, audio_files, slide_images, 0, slides[0], audio_files[0], initial_image, progress_text, session_id
175
 
176
 
177
+ def generate_presentation(topic: str, api_key: str, session_id=None):
178
  """Synchronous wrapper for the async presentation generator."""
179
  # Run the async function and handle empty topic case
180
+ return asyncio.run(generate_presentation_async(topic, api_key, session_id=session_id))
181
 
182
 
183
  def next_slide(slides, audio, images, idx, session_id):
 
320
  dino_audio_cache = {}
321
  dino_image_cache = {}
322
 
323
+ def load_rise_fall_slideshow(api_key):
324
  """Load cached dinosaur slideshow demo and hide the demo button and instruction."""
325
  global dino_audio_cache, dino_image_cache
326
 
327
+ # API key is required even for demo
328
+ if not api_key:
329
+ raise gr.Error("Please enter your Gemini API key first")
330
+
331
  # Clear any previous cache
332
  dino_audio_cache = {}
333
  dino_image_cache = {}
 
438
  )
439
 
440
  with gr.Column(elem_classes="container"):
441
+ # API Key section
442
+ with gr.Group(elem_classes="api-key-section"):
443
+ api_key_input = gr.Textbox(
444
+ label="Gemini API Key",
445
+ placeholder="Enter your Gemini API key here",
446
+ type="password",
447
+ scale=1
448
+ )
449
+ gr.HTML(
450
+ """<div class="api-key-note">
451
+ <strong>Note:</strong> You need a Gemini API key with billing enabled to use this app.<br>
452
+ Get your API key from <a href="https://aistudio.google.com/app/apikey" target="_blank">Google AI Studio</a>.<br>
453
+ <em>Important: Make sure to enable billing on your Google Cloud account for API access.</em>
454
+ </div>"""
455
+ )
456
+
457
  # First row for topic and generate button - increased horizontal width
458
  with gr.Row(elem_classes="input-row"):
459
  topic_box = gr.Textbox(
 
494
  session_state = gr.State(None)
495
 
496
  # Wiring
497
+ def prepare_for_generation(topic, api_key, session_id):
498
  """First step: clear the view and prepare for generation"""
499
  # First check if the topic is empty
500
  if not (topic or "").strip():
 
509
  False # should_generate
510
  )
511
 
512
+ # Check if API key is provided
513
+ if not (api_key or "").strip():
514
+ gr.Info("Please enter your Gemini API key.")
515
+ return (
516
+ [], [], [], 0, "", None, None, "", session_id,
517
+ gr.update(visible=True), # Show topic box
518
+ gr.update(visible=True), # Show generate button
519
+ gr.update(value="Generate", interactive=True), # Reset button state
520
+ gr.update(visible=True), # Show dinosaur button
521
+ gr.update(visible=True), # Show instruction
522
+ False # should_generate
523
+ )
524
+
525
  # Validate the topic using the Gemini Flash input guard
526
+ try:
527
+ if not validate_topic(topic, api_key):
528
+ gr.Info("Please enter a valid topic or question.")
529
+ return (
530
+ [], [], [], 0, "", None, None, "", session_id,
531
+ gr.update(visible=True), # Show topic box
532
+ gr.update(visible=True), # Show generate button
533
+ gr.update(value="Generate", interactive=True), # Reset button state
534
+ gr.update(visible=True), # Show dinosaur button
535
+ gr.update(visible=True), # Show instruction
536
+ False # should_generate
537
+ )
538
+ except Exception as e:
539
+ gr.Error(f"Error validating topic: {str(e)}. Please check your API key.")
540
  return (
541
  [], [], [], 0, "", None, None, "", session_id,
542
  gr.update(visible=True), # Show topic box
 
560
  True # should_generate
561
  )
562
 
563
+ def _run_with_new_session(topic, api_key, session_id, should_generate):
564
  """Second step: actually generate the slideshow"""
565
  if not should_generate:
566
  # This case should ideally not be hit if UI updates from prepare_for_generation are correct
 
575
  False # should_generate (though this output isn't strictly used here, keeping tuple size consistent)
576
  )
577
 
578
+ try:
579
+ results = generate_presentation(topic, api_key, session_id)
580
+ return (*results,
581
+ gr.update(visible=False),
582
+ gr.update(visible=False),
583
+ gr.update(value="Generate", interactive=True),
584
+ gr.update(visible=False),
585
+ gr.update(visible=False),
586
+ True # should_generate (maintaining tuple size, actual value less critical here)
587
+ )
588
+ except Exception as e:
589
+ gr.Error(f"Error generating slideshow: {str(e)}")
590
+ return (
591
+ [], [], [], 0, "Error generating slideshow. Please check your API key and try again.", None, None, "", session_id,
592
+ gr.update(visible=True),
593
+ gr.update(visible=True),
594
+ gr.update(value="Generate", interactive=True),
595
+ gr.update(visible=True),
596
+ gr.update(visible=True),
597
+ False
598
+ )
599
 
600
  # Two-step process for generation
601
  # 1. First clear the UI & validate
 
603
 
604
  gen_btn.click(
605
  prepare_for_generation,
606
+ inputs=[topic_box, api_key_input, session_state],
607
  outputs=[
608
  slides_state,
609
  audio_state,
 
623
  ]
624
  ).then( # 2. Then (conditionally) generate the slideshow
625
  _run_with_new_session,
626
+ inputs=[topic_box, api_key_input, session_state, should_generate_state],
627
  outputs=[
628
  slides_state,
629
  audio_state,
 
658
  # Load cached demo slideshow
659
  demo_btn.click(
660
  load_rise_fall_slideshow,
661
+ inputs=[api_key_input],
662
  outputs=[slides_state, audio_state, images_state, index_state, slide_markdown, audio_player, title_image,
663
  progress_indicator, session_state, demo_btn, demo_instruction],
664
  )
665
 
666
 
667
  if __name__ == "__main__":
668
+ demo.launch()