phani50101 commited on
Commit
658517f
·
verified ·
1 Parent(s): cff6fb6

new updates with stremming

Browse files
Files changed (1) hide show
  1. app.py +190 -172
app.py CHANGED
@@ -3,7 +3,7 @@ import gradio as gr
3
  import pandas as pd
4
  import openvino_genai
5
  from huggingface_hub import snapshot_download
6
- from threading import Lock
7
  import os
8
  import numpy as np
9
  import requests
@@ -14,10 +14,12 @@ import openvino as ov
14
  import librosa
15
  from googleapiclient.discovery import build
16
  import gc
17
- import tempfile
18
  from PyPDF2 import PdfReader
19
  from docx import Document
20
  import textwrap
 
 
 
21
 
22
  # Google API configuration
23
  GOOGLE_API_KEY = "AIzaSyAo-1iW5MEZbc53DlEldtnUnDaYuTHUDH4"
@@ -34,7 +36,8 @@ class UnifiedAISystem:
34
  self.mistral_pipe = None
35
  self.internvl_pipe = None
36
  self.whisper_pipe = None
37
- self.current_document_text = None # Store document content
 
38
  self.initialize_models()
39
 
40
  def initialize_models(self):
@@ -108,7 +111,65 @@ class UnifiedAISystem:
108
  except Exception as e:
109
  return False, f"❌ Error processing document: {str(e)}"
110
 
111
- def analyze_student_data(self, query):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  """Analyze student data using AI with streaming"""
113
  if not query or not query.strip():
114
  yield "⚠️ Please enter a valid question"
@@ -131,23 +192,9 @@ class UnifiedAISystem:
131
  4. Actionable recommendations
132
 
133
  Format the output with clear headings"""
134
-
135
- optimized_config = openvino_genai.GenerationConfig(
136
- max_new_tokens=500,
137
- temperature=0.3,
138
- top_p=0.9,
139
- streaming=True
140
- )
141
-
142
- full_response = ""
143
- try:
144
- with self.pipe_lock:
145
- token_iterator = self.mistral_pipe.generate(prompt, optimized_config, streaming=True)
146
- for token in token_iterator:
147
- full_response += token
148
- yield full_response
149
- except Exception as e:
150
- yield f"❌ Error during analysis: {str(e)}"
151
 
152
  def _prepare_data_summary(self, df):
153
  """Summarize the uploaded data"""
@@ -157,7 +204,7 @@ class UnifiedAISystem:
157
  return summary
158
 
159
  def analyze_image(self, image, url, prompt):
160
- """Analyze image with InternVL model"""
161
  try:
162
  if image is not None:
163
  image_source = image
@@ -182,7 +229,9 @@ class UnifiedAISystem:
182
  output = self.internvl_pipe.generate(prompt, image=image_tensor, max_new_tokens=100)
183
  self.internvl_pipe.finish_chat()
184
 
 
185
  return output
 
186
  except Exception as e:
187
  return f"❌ Error: {str(e)}"
188
 
@@ -255,10 +304,15 @@ class UnifiedAISystem:
255
  print(f"Transcription error: {e}")
256
  return "❌ Transcription failed - please try again"
257
 
258
- def generate_lesson_plan(self, topic, duration, additional_instructions=""):
259
  """Generate a lesson plan based on document content"""
 
 
 
 
260
  if not self.current_document_text:
261
- return "⚠️ Please upload and process a document first"
 
262
 
263
  prompt = f"""As an expert educator, create a focused lesson plan using the provided content.
264
 
@@ -288,19 +342,9 @@ class UnifiedAISystem:
288
  - Keep objectives measurable
289
  - Use only document resources
290
  - Make page references specific"""
291
-
292
-
293
- optimized_config = openvino_genai.GenerationConfig(
294
- max_new_tokens=1200,
295
- temperature=0.4,
296
- top_p=0.85
297
- )
298
-
299
- try:
300
- with self.pipe_lock:
301
- return self.mistral_pipe.generate(prompt, optimized_config)
302
- except Exception as e:
303
- return f"❌ Error generating lesson plan: {str(e)}"
304
 
305
  def fetch_images(self, query: str, num: int = DEFAULT_NUM_IMAGES) -> list:
306
  """Fetch unique images by requesting different result pages"""
@@ -337,28 +381,6 @@ class UnifiedAISystem:
337
  print(f"Error in image fetching: {e}")
338
  return []
339
 
340
- def stream_answer(self, message: str, max_tokens: int) -> str:
341
- """Stream tokens with typing indicator"""
342
- optimized_config = openvino_genai.GenerationConfig(
343
- max_new_tokens=max_tokens,
344
- temperature=0.7,
345
- top_p=0.9,
346
- streaming=True
347
- )
348
-
349
- full_response = ""
350
- try:
351
- with self.pipe_lock:
352
- token_iterator = self.mistral_pipe.generate(message, optimized_config, streaming=True)
353
- for token in token_iterator:
354
- full_response += token
355
- yield full_response
356
- # Periodic garbage collection
357
- if len(full_response) % 20 == 0:
358
- gc.collect()
359
- except Exception as e:
360
- yield f"❌ Error: {str(e)}"
361
-
362
  # Initialize global object
363
  ai_system = UnifiedAISystem()
364
 
@@ -601,7 +623,7 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
601
  image_mode = gr.Checkbox(label="🖼️ Image Analysis", value=False, elem_classes="mode-checkbox")
602
  lesson_mode = gr.Checkbox(label="📝 Lesson Planning", value=False, elem_classes="mode-checkbox")
603
 
604
- # Dynamic input fields
605
  with gr.Column() as chat_inputs:
606
  include_images = gr.Checkbox(label="Include Visuals", value=True)
607
  user_input = gr.Textbox(
@@ -627,6 +649,7 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
627
  visible=True
628
  )
629
 
 
630
  with gr.Column(visible=False) as student_inputs:
631
  file_upload = gr.File(label="CSV/Excel File", file_types=[".csv", ".xlsx"], type="filepath")
632
  student_question = gr.Textbox(
@@ -636,6 +659,7 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
636
  )
637
  student_status = gr.Markdown("No file loaded")
638
 
 
639
  with gr.Column(visible=False) as image_inputs:
640
  image_upload = gr.Image(type="pil", label="Upload Image")
641
  image_url = gr.Textbox(
@@ -649,7 +673,7 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
649
  elem_id="question-input"
650
  )
651
 
652
- # Lesson planning section
653
  with gr.Column(visible=False) as lesson_inputs:
654
  gr.Markdown("### 📚 Lesson Planning")
655
  doc_upload = gr.File(
@@ -685,12 +709,6 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
685
  mic_btn = gr.Button("Transcribe Voice", variant="secondary")
686
  mic = gr.Audio(sources=["microphone"], type="numpy", label="Voice Input")
687
 
688
- processing = gr.HTML("""
689
- <div style="display: none;">
690
- <div class="processing">🔮 Processing your request...</div>
691
- </div>
692
- """)
693
-
694
  # Event handlers
695
  def toggle_modes(chat, student, image, lesson):
696
  return [
@@ -714,14 +732,15 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
714
  """Render chat history with images and proper formatting"""
715
  rendered = []
716
  for user_msg, bot_msg, image_links in history:
717
- # Apply proper styling to messages
718
  user_html = f"<div class='user-msg'>{user_msg}</div>"
719
 
720
- # Special formatting for lesson plans
721
- if "Lesson Plan:" in bot_msg:
722
- bot_html = f"<div class='lesson-plan'>{bot_msg}</div>"
 
 
723
  else:
724
- bot_html = f"<div class='bot-msg'>{bot_msg}</div>"
725
 
726
  # Add images if available
727
  if image_links:
@@ -734,106 +753,94 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
734
  rendered.append((user_html, bot_html))
735
  return rendered
736
 
737
- def respond(message, chat_hist, chat, student, image, lesson,
738
  tokens, student_q, image_q, image_upload, image_url,
739
- include_visuals, num_imgs):
740
- # If in lesson planning mode, skip this handler
741
- if lesson:
742
- return chat_hist, message
743
-
744
- # Determine the actual question based on mode
745
- if chat:
746
- actual_question = message
747
- elif student:
748
- actual_question = student_q
 
749
  elif image:
750
- actual_question = image_q
 
 
 
 
751
  else:
752
- actual_question = message
753
 
754
- # Immediately show user question in chat
755
  typing_html = "<div class='typing-indicator'><div class='typing-dot'></div><div class='typing-dot'></div><div class='typing-dot'></div></div>"
756
- chat_hist.append((actual_question, typing_html, []))
757
- yield render_history(chat_hist), ""
758
-
759
- if chat:
760
- # General chat mode
761
- full_response = ""
762
- for chunk in ai_system.stream_answer(message, tokens):
763
- full_response = chunk
764
- # Update with current response
765
- chat_hist[-1] = (actual_question, full_response, [])
766
- yield render_history(chat_hist), ""
767
-
768
- # Fetch images if requested
769
- image_links = []
770
- if include_visuals and num_imgs > 0:
771
- image_links = ai_system.fetch_images(message, num_imgs)
772
-
773
- # Update with final response and images
774
- chat_hist[-1] = (actual_question, full_response, image_links)
775
- yield render_history(chat_hist), ""
776
-
777
- elif student:
778
- # Student analytics mode
779
- if ai_system.current_df is None:
780
- chat_hist[-1] = (actual_question, "⚠️ Please upload a student data file first", [])
781
- yield render_history(chat_hist), ""
782
- else:
783
- response = ""
784
- for chunk in ai_system.analyze_student_data(student_q):
785
- response = chunk
786
- chat_hist[-1] = (actual_question, response, [])
787
- yield render_history(chat_hist), ""
788
 
789
- elif image:
790
- # Image analysis mode
791
- if not image_upload and not image_url:
792
- chat_hist[-1] = (actual_question, "⚠️ Please upload an image or enter a URL", [])
793
- yield render_history(chat_hist), ""
794
- else:
795
- try:
796
- result = ai_system.analyze_image(image_upload, image_url, image_q)
797
- chat_hist[-1] = (actual_question, result, [])
798
- yield render_history(chat_hist), ""
799
- except Exception as e:
800
- error_msg = f"❌ Error analyzing image: {str(e)}"
801
- chat_hist[-1] = (actual_question, error_msg, [])
802
- yield render_history(chat_hist), ""
803
-
804
- # Trim history if too long
805
- if len(chat_hist) > MAX_HISTORY_TURNS:
806
- chat_hist = chat_hist[-MAX_HISTORY_TURNS:]
807
-
808
- yield render_history(chat_hist), ""
809
-
810
- def generate_lesson_plan(topic, duration, instructions, chat_hist):
811
- if not topic:
812
- return chat_hist, "⚠️ Please enter a lesson topic"
813
 
814
- # Show processing message
815
- processing_msg = "<div class='typing-indicator'><div class='typing-dot'></div><div class='typing-dot'></div><div class='typing-dot'></div></div>"
816
- chat_hist.append((f"Generate lesson plan for: {topic}", processing_msg, []))
817
- yield render_history(chat_hist), ""
818
 
819
- # Generate the plan
820
- plan = ai_system.generate_lesson_plan(topic, duration, instructions)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821
 
822
- # Format with proper headings
823
- formatted_plan = f"""
824
- <div class='lesson-plan'>
825
- <div class='lesson-title'>📝 Lesson Plan: {topic} ({duration} periods)</div>
826
- {plan}
827
- </div>
828
- """
829
 
830
- # Update chat history with final plan
831
- chat_hist[-1] = (
832
- f"Generate lesson plan for: {topic}",
833
- formatted_plan,
834
- []
835
- )
836
- yield render_history(chat_hist), ""
837
 
838
  # Mode toggles
839
  chat_mode.change(fn=toggle_modes, inputs=[chat_mode, student_mode, image_mode, lesson_mode],
@@ -851,10 +858,6 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
851
  # Document upload handler
852
  doc_upload.change(fn=process_document, inputs=doc_upload, outputs=doc_status)
853
 
854
- # Voice transcription
855
- def transcribe_audio(audio):
856
- return ai_system.transcribe(audio)
857
-
858
  mic_btn.click(fn=transcribe_audio, inputs=mic, outputs=user_input)
859
 
860
  # Submit handler
@@ -863,17 +866,32 @@ with gr.Blocks(css=css, title="Unified EDU Assistant") as demo:
863
  inputs=[
864
  user_input, chat_state, chat_mode, student_mode, image_mode, lesson_mode,
865
  max_tokens, student_question, image_question, image_upload, image_url,
866
- include_images, num_images
 
867
  ],
868
- outputs=[chatbot, user_input]
869
  )
870
 
871
  # Lesson plan generation button
872
  generate_btn.click(
873
- fn=generate_lesson_plan,
874
- inputs=[topic_input, duration_input, additional_instructions, chat_state],
875
- outputs=[chatbot, topic_input]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876
  )
877
 
878
  if __name__ == "__main__":
879
- demo.launch(share=True, debug=True)
 
3
  import pandas as pd
4
  import openvino_genai
5
  from huggingface_hub import snapshot_download
6
+ from threading import Lock, Event
7
  import os
8
  import numpy as np
9
  import requests
 
14
  import librosa
15
  from googleapiclient.discovery import build
16
  import gc
 
17
  from PyPDF2 import PdfReader
18
  from docx import Document
19
  import textwrap
20
+ from queue import Queue, Empty
21
+ from concurrent.futures import ThreadPoolExecutor
22
+ from typing import Generator
23
 
24
  # Google API configuration
25
  GOOGLE_API_KEY = "AIzaSyAo-1iW5MEZbc53DlEldtnUnDaYuTHUDH4"
 
36
  self.mistral_pipe = None
37
  self.internvl_pipe = None
38
  self.whisper_pipe = None
39
+ self.current_document_text = None
40
+ self.generation_executor = ThreadPoolExecutor(max_workers=3)
41
  self.initialize_models()
42
 
43
  def initialize_models(self):
 
111
  except Exception as e:
112
  return False, f"❌ Error processing document: {str(e)}"
113
 
114
+ def generate_text_stream(self, prompt: str, max_tokens: int) -> Generator[str, None, None]:
115
+ """Unified text generation with queued token streaming"""
116
+ start_time = time.time()
117
+ response_queue = Queue()
118
+ completion_event = Event()
119
+ error = [None] # Use list to capture exception from thread
120
+
121
+ optimized_config = openvino_genai.GenerationConfig(
122
+ max_new_tokens=max_tokens,
123
+ temperature=0.3,
124
+ top_p=0.9,
125
+ streaming=True,
126
+ streaming_interval=5 # Batch tokens in groups of 5
127
+ )
128
+
129
+ def callback(tokens): # Accepts multiple tokens
130
+ response_queue.put("".join(tokens))
131
+ return openvino_genai.StreamingStatus.RUNNING
132
+
133
+ def generate():
134
+ try:
135
+ with self.pipe_lock:
136
+ self.mistral_pipe.generate(prompt, optimized_config, callback)
137
+ except Exception as e:
138
+ error[0] = str(e)
139
+ finally:
140
+ completion_event.set()
141
+
142
+ # Submit generation task to executor
143
+ self.generation_executor.submit(generate)
144
+
145
+ accumulated = []
146
+ token_count = 0
147
+ last_gc = time.time()
148
+
149
+ while not completion_event.is_set() or not response_queue.empty():
150
+ if error[0]:
151
+ yield f"❌ Error: {error[0]}"
152
+ print(f"Stream generation time: {time.time() - start_time:.2f} seconds")
153
+ return
154
+
155
+ try:
156
+ token_batch = response_queue.get(timeout=0.1)
157
+ accumulated.append(token_batch)
158
+ token_count += len(token_batch)
159
+ yield "".join(accumulated)
160
+
161
+ # Periodic garbage collection
162
+ if time.time() - last_gc > 2.0:
163
+ gc.collect()
164
+ last_gc = time.time()
165
+ except Empty:
166
+ continue
167
+
168
+ print(f"Generated {token_count} tokens in {time.time() - start_time:.2f} seconds "
169
+ f"({token_count/(time.time() - start_time):.2f} tokens/sec)")
170
+ yield "".join(accumulated)
171
+
172
+ def analyze_student_data(self, query, max_tokens=500):
173
  """Analyze student data using AI with streaming"""
174
  if not query or not query.strip():
175
  yield "⚠️ Please enter a valid question"
 
192
  4. Actionable recommendations
193
 
194
  Format the output with clear headings"""
195
+
196
+ # Use unified streaming generator
197
+ yield from self.generate_text_stream(prompt, max_tokens)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  def _prepare_data_summary(self, df):
200
  """Summarize the uploaded data"""
 
204
  return summary
205
 
206
  def analyze_image(self, image, url, prompt):
207
+ """Analyze image with InternVL model (synchronous, no streaming)"""
208
  try:
209
  if image is not None:
210
  image_source = image
 
229
  output = self.internvl_pipe.generate(prompt, image=image_tensor, max_new_tokens=100)
230
  self.internvl_pipe.finish_chat()
231
 
232
+ # output is a VLMDecodedResults; rest of the code expects a string
233
  return output
234
+
235
  except Exception as e:
236
  return f"❌ Error: {str(e)}"
237
 
 
304
  print(f"Transcription error: {e}")
305
  return "❌ Transcription failed - please try again"
306
 
307
+ def generate_lesson_plan(self, topic, duration, additional_instructions="", max_tokens=1200):
308
  """Generate a lesson plan based on document content"""
309
+ if not topic:
310
+ yield "⚠️ Please enter a lesson topic"
311
+ return
312
+
313
  if not self.current_document_text:
314
+ yield "⚠️ Please upload and process a document first"
315
+ return
316
 
317
  prompt = f"""As an expert educator, create a focused lesson plan using the provided content.
318
 
 
342
  - Keep objectives measurable
343
  - Use only document resources
344
  - Make page references specific"""
345
+
346
+ # Use unified streaming generator
347
+ yield from self.generate_text_stream(prompt, max_tokens)
 
 
 
 
 
 
 
 
 
 
348
 
349
  def fetch_images(self, query: str, num: int = DEFAULT_NUM_IMAGES) -> list:
350
  """Fetch unique images by requesting different result pages"""
 
381
  print(f"Error in image fetching: {e}")
382
  return []
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  # Initialize global object
385
  ai_system = UnifiedAISystem()
386
 
 
623
  image_mode = gr.Checkbox(label="🖼️ Image Analysis", value=False, elem_classes="mode-checkbox")
624
  lesson_mode = gr.Checkbox(label="📝 Lesson Planning", value=False, elem_classes="mode-checkbox")
625
 
626
+ # Dynamic input fields (General Chat by default)
627
  with gr.Column() as chat_inputs:
628
  include_images = gr.Checkbox(label="Include Visuals", value=True)
629
  user_input = gr.Textbox(
 
649
  visible=True
650
  )
651
 
652
+ # Student inputs
653
  with gr.Column(visible=False) as student_inputs:
654
  file_upload = gr.File(label="CSV/Excel File", file_types=[".csv", ".xlsx"], type="filepath")
655
  student_question = gr.Textbox(
 
659
  )
660
  student_status = gr.Markdown("No file loaded")
661
 
662
+ # Image analysis inputs
663
  with gr.Column(visible=False) as image_inputs:
664
  image_upload = gr.Image(type="pil", label="Upload Image")
665
  image_url = gr.Textbox(
 
673
  elem_id="question-input"
674
  )
675
 
676
+ # Lesson planning inputs
677
  with gr.Column(visible=False) as lesson_inputs:
678
  gr.Markdown("### 📚 Lesson Planning")
679
  doc_upload = gr.File(
 
709
  mic_btn = gr.Button("Transcribe Voice", variant="secondary")
710
  mic = gr.Audio(sources=["microphone"], type="numpy", label="Voice Input")
711
 
 
 
 
 
 
 
712
  # Event handlers
713
  def toggle_modes(chat, student, image, lesson):
714
  return [
 
732
  """Render chat history with images and proper formatting"""
733
  rendered = []
734
  for user_msg, bot_msg, image_links in history:
 
735
  user_html = f"<div class='user-msg'>{user_msg}</div>"
736
 
737
+ # Ensure bot_msg is a string before checking substrings
738
+ bot_text = str(bot_msg)
739
+
740
+ if "Lesson Plan:" in bot_text:
741
+ bot_html = f"<div class='lesson-plan'>{bot_text}</div>"
742
  else:
743
+ bot_html = f"<div class='bot-msg'>{bot_text}</div>"
744
 
745
  # Add images if available
746
  if image_links:
 
753
  rendered.append((user_html, bot_html))
754
  return rendered
755
 
756
+ def respond(message, history, chat, student, image, lesson,
757
  tokens, student_q, image_q, image_upload, image_url,
758
+ include_visuals, num_imgs, topic, duration, additional):
759
+ """
760
+ 1. Use actual_message (depending on mode) instead of raw `message`.
761
+ 2. Convert any non‐string Bot response (like VLMDecodedResults) to str().
762
+ 3. Disable the input box during streaming, then re-enable it at the end.
763
+ """
764
+ updated_history = list(history)
765
+
766
+ # Determine which prompt to actually send
767
+ if student:
768
+ actual_message = student_q
769
  elif image:
770
+ actual_message = image_q
771
+ elif lesson:
772
+ actual_message = f"Generate lesson plan for: {topic} ({duration} periods)"
773
+ if additional:
774
+ actual_message += f"\nAdditional: {additional}"
775
  else:
776
+ actual_message = message
777
 
778
+ # Add a “typing” placeholder entry using actual_message
779
  typing_html = "<div class='typing-indicator'><div class='typing-dot'></div><div class='typing-dot'></div><div class='typing-dot'></div></div>"
780
+ updated_history.append((actual_message, typing_html, []))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
 
782
+ # First yield: clear & disable the input box while streaming
783
+ yield render_history(updated_history), gr.update(value="", interactive=False), updated_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
 
785
+ full_response = ""
786
+ images = []
 
 
787
 
788
+ try:
789
+ if chat:
790
+ # General chat mode → streaming
791
+ for chunk in ai_system.generate_text_stream(actual_message, tokens):
792
+ full_response = chunk
793
+ updated_history[-1] = (actual_message, full_response, [])
794
+ yield render_history(updated_history), gr.update(value="", interactive=False), updated_history
795
+
796
+ if include_visuals:
797
+ images = ai_system.fetch_images(actual_message, num_imgs)
798
+
799
+ elif student:
800
+ # Student analytics mode → streaming
801
+ if ai_system.current_df is None:
802
+ full_response = "⚠️ Please upload a student data file first"
803
+ else:
804
+ for chunk in ai_system.analyze_student_data(student_q, tokens):
805
+ full_response = chunk
806
+ updated_history[-1] = (actual_message, full_response, [])
807
+ yield render_history(updated_history), gr.update(value="", interactive=False), updated_history
808
+
809
+ elif image:
810
+ # Image analysis mode → synchronous
811
+ if (not image_upload) and (not image_url):
812
+ full_response = "⚠️ Please upload an image or enter a URL"
813
+ else:
814
+ # ai_system.analyze_image(...) returns a VLMDecodedResults, not a string
815
+ result_obj = ai_system.analyze_image(image_upload, image_url, image_q)
816
+ full_response = str(result_obj)
817
+
818
+ elif lesson:
819
+ # Lesson planning mode → streaming
820
+ if not topic:
821
+ full_response = "⚠️ Please enter a lesson topic"
822
+ else:
823
+ duration = int(duration) if duration else 5
824
+ for chunk in ai_system.generate_lesson_plan(topic, duration, additional, tokens):
825
+ full_response = chunk
826
+ updated_history[-1] = (actual_message, full_response, [])
827
+ yield render_history(updated_history), gr.update(value="", interactive=False), updated_history
828
+
829
+ # Final update: put in images (if any), trim history, and re-enable input
830
+ updated_history[-1] = (actual_message, full_response, images)
831
+ if len(updated_history) > MAX_HISTORY_TURNS:
832
+ updated_history = updated_history[-MAX_HISTORY_TURNS:]
833
 
834
+ except Exception as e:
835
+ error_msg = f"❌ Error: {str(e)}"
836
+ updated_history[-1] = (actual_message, error_msg, [])
 
 
 
 
837
 
838
+ # Final yield: clear & re-enable the input box
839
+ yield render_history(updated_history), gr.update(value="", interactive=True), updated_history
840
+
841
+ # Voice transcription
842
+ def transcribe_audio(audio):
843
+ return ai_system.transcribe(audio)
 
844
 
845
  # Mode toggles
846
  chat_mode.change(fn=toggle_modes, inputs=[chat_mode, student_mode, image_mode, lesson_mode],
 
858
  # Document upload handler
859
  doc_upload.change(fn=process_document, inputs=doc_upload, outputs=doc_status)
860
 
 
 
 
 
861
  mic_btn.click(fn=transcribe_audio, inputs=mic, outputs=user_input)
862
 
863
  # Submit handler
 
866
  inputs=[
867
  user_input, chat_state, chat_mode, student_mode, image_mode, lesson_mode,
868
  max_tokens, student_question, image_question, image_upload, image_url,
869
+ include_images, num_images,
870
+ topic_input, duration_input, additional_instructions
871
  ],
872
+ outputs=[chatbot, user_input, chat_state]
873
  )
874
 
875
  # Lesson plan generation button
876
  generate_btn.click(
877
+ fn=respond,
878
+ inputs=[
879
+ gr.Textbox(value="Generate lesson plan", visible=False), # Hidden message
880
+ chat_state,
881
+ chat_mode, student_mode, image_mode, lesson_mode,
882
+ max_tokens,
883
+ gr.Textbox(visible=False), # student_q
884
+ gr.Textbox(visible=False), # image_q
885
+ gr.Image(visible=False), # image_upload
886
+ gr.Textbox(visible=False), # image_url
887
+ gr.Checkbox(visible=False), # include_visuals
888
+ gr.Slider(visible=False), # num_imgs
889
+ topic_input, # Pass topic
890
+ duration_input, # Pass duration
891
+ additional_instructions # Pass additional instructions
892
+ ],
893
+ outputs=[chatbot, user_input, chat_state]
894
  )
895
 
896
  if __name__ == "__main__":
897
+ demo.launch(share=True, debug=True)