MonaHamid commited on
Commit
c91f71a
Β·
verified Β·
1 Parent(s): 32fdfa1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +721 -0
app.py ADDED
@@ -0,0 +1,721 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import tempfile
3
+ import soundfile as sf
4
+ import gradio as gr
5
+ import json
6
+ import random
7
+ import os
8
+ import logging
9
+ from google.api_core.exceptions import GoogleAPIError, NotFound, PermissionDenied
10
+ from google import genai
11
+ from google.genai import types
12
+
13
+ from langchain.output_parsers import StructuredOutputParser, ResponseSchema
14
+ from langchain.prompts import PromptTemplate
15
+ from langchain.chains import LLMChain
16
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
17
+ from langchain_community.vectorstores import Chroma
18
+ from langchain_core.prompts import ChatPromptTemplate
19
+ from langchain_core.documents import Document
20
+ from langchain_core.output_parsers import StrOutputParser
21
+ from langchain_google_genai import ChatGoogleGenerativeAI
22
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
23
+ from langchain_core.runnables import RunnablePassthrough
24
+ from langchain.retrievers.multi_query import MultiQueryRetriever
25
+ from langchain.chains.retrieval import create_retrieval_chain
26
+ from langchain_core.output_parsers import JsonOutputParser
27
+ from langchain.chains.combine_documents import create_stuff_documents_chain
28
+
29
+ # βœ… Hugging Face environment secret
30
+ API_KEY = os.environ.get("GOOGLE_API_KEY")
31
+ client = genai.Client(api_key=API_KEY)
32
+
33
+
34
+ # ------------- Global State -------------
35
+ full_transcript = []
36
+ lecture_index = []
37
+ vector_db = None
38
+ rag_chain = None
39
+ current_correct_answer = ""
40
+
41
+ # ------------- Transcript State Management -------------
42
+ def get_full_transcript_text():
43
+ return " ".join(full_transcript)
44
+
45
+ def clear_transcript_data():
46
+ global full_transcript
47
+ full_transcript = []
48
+ print("Transcript data cleared.")
49
+ return ""
50
+
51
+ # ------------- Gemini STT Function -------------
52
+ def transcribe_audio_chunk(audio_path):
53
+ try:
54
+ client = genai.Client(api_key=API_KEY)
55
+ uploaded_file = client.files.upload(file=audio_path)
56
+ print(f"βœ… File uploaded. URI: {uploaded_file.uri}, Name: {uploaded_file.name}")
57
+
58
+ prompt = (
59
+ "Please perform speech-to-text transcription for the provided audio file. "
60
+ "Output the transcribed text followed by the key points as a numbered list. "
61
+ "Do not use any JSON formattingβ€”just return plain text."
62
+ )
63
+
64
+ print("πŸš€ Sending transcription request to Gemini...")
65
+ response = client.models.generate_content(
66
+ model='gemini-2.0-flash',
67
+ contents=[prompt, uploaded_file]
68
+ )
69
+
70
+ if not response.candidates:
71
+ block_reason = response.prompt_feedback.block_reason if response.prompt_feedback else "Unknown"
72
+ return f"Transcription failed. Block Reason: {block_reason}"
73
+
74
+ candidate = response.candidates[0]
75
+ if hasattr(candidate.content, 'parts') and candidate.content.parts:
76
+ transcript = candidate.content.parts[0].text
77
+ print("βœ… Transcription successful.")
78
+ return transcript
79
+ else:
80
+ return "Error: Failed to parse transcription response."
81
+
82
+ except PermissionDenied as e:
83
+ return f"❌ Permission Denied: {e.message}"
84
+ except NotFound as e:
85
+ return f"❌ Resource Not Found: {e.message}"
86
+ except GoogleAPIError as e:
87
+ return f"❌ API Error: {e.message}"
88
+ except Exception as e:
89
+ return f"❌ Unexpected Error: {str(e)}"
90
+
91
+ # ------------- Formatting the Output -------------
92
+ def format_transcription_result(result_text):
93
+ return result_text
94
+
95
+ # ------------- Gradio Transcription Handler -------------
96
+ def handle_transcription_request(audio_file):
97
+ if audio_file is None:
98
+ return "", get_full_transcript_text(), gr.update(value=None), "Transcription not initiated.", "Input declined. No audio file provided."
99
+
100
+ transcript_text = transcribe_audio_chunk(audio_file)
101
+ formatted_chunk = format_transcription_result(transcript_text)
102
+ full_transcript.append(formatted_chunk)
103
+
104
+ return (
105
+ formatted_chunk,
106
+ get_full_transcript_text(),
107
+ gr.update(value=None),
108
+ "Transcription successful.",
109
+ "Input accepted. Audio file is being processed."
110
+ )
111
+
112
+ def handle_clear_transcript():
113
+ clear_transcript_data()
114
+ return "", "Transcript cleared."
115
+
116
+
117
+ # ------------- Chunking, Embedding, Vector DB & RAG -------------
118
+
119
+ def chunk_transcript(text, chunk_size: int = 800, overlap_size: int = 150):
120
+ # Optionally, you could call: text = correct_transcript_errors(text)
121
+ document = [Document(page_content=text)]
122
+ splitter = RecursiveCharacterTextSplitter(
123
+ chunk_size=chunk_size,
124
+ chunk_overlap=overlap_size
125
+ )
126
+ chunks = splitter.split_documents(documents=document)
127
+ print(f"File split into {len(chunks)} chunks.")
128
+ return chunks
129
+
130
+ def create_vector_db(text_chunks, collection_name="transcription-rag"):
131
+ global vector_db
132
+ try:
133
+ embeddings = GoogleGenerativeAIEmbeddings(model="models/text-embedding-004", google_api_key=API_KEY)
134
+ vector_db = Chroma.from_documents(
135
+ documents=text_chunks,
136
+ embedding=embeddings,
137
+ collection_name=collection_name,
138
+ persist_directory="/content/chroma_db" # Ephemeral persist directory.
139
+ )
140
+ print(f"Vector DB created with collection_name: {collection_name}")
141
+ return vector_db
142
+ except Exception as e:
143
+ raise Exception(f"Error creating vector DB: {str(e)}")
144
+
145
+ def setup_rag_chain(vector_db):
146
+ if not vector_db:
147
+ raise ValueError("Vector DB not initialized!")
148
+
149
+ try:
150
+ llm = ChatGoogleGenerativeAI(
151
+ model="gemini-2.0-flash",
152
+ temperature=0.1,
153
+ max_tokens=None,
154
+ timeout=None,
155
+ max_retries=2,
156
+ google_api_key=API_KEY
157
+ )
158
+
159
+ # Few-shot query rewriting prompt
160
+ query_prompt = PromptTemplate.from_template("""
161
+ You are an AI assistant that helps rephrase queries.
162
+
163
+ Example 1:
164
+ Original Question: Who is Master Sito?
165
+ Alternative Queries:
166
+ 1. According to the transcript, what is Master Sito's role?
167
+ 2. What does the transcript state about Master Sito?
168
+ 3. How is Master Sito described in the lecture?
169
+
170
+ Example 2:
171
+ Original Question: Who is Master Sito?
172
+ Even if the transcript contains a minor typo (e.g., 'Master Ceto'),
173
+ assume the intended name is Master Sito.
174
+
175
+ Now, given the original question: {question}
176
+ Generate three alternative queries:
177
+ """)
178
+
179
+ retriever = MultiQueryRetriever.from_llm(
180
+ retriever=vector_db.as_retriever(search_kwargs={"k": 4}),
181
+ llm=llm,
182
+ prompt=query_prompt
183
+ )
184
+
185
+ # Main prompt for answering with grounding and few-shot examples
186
+ main_template = """
187
+ You are an educational assistant. Answer the user's question based solely on the transcript context provided.
188
+ Disregard minor transcription errors (for example, if the transcript has "Master Ceto" but context indicates it should be "Master Sito").
189
+ If the answer is explicitly stated, provide it exactly. Otherwise, reply "I don’t know."
190
+
191
+ Few-shot examples:
192
+ ---------------------
193
+ Transcript Example 1:
194
+ "Master Sito said: 'Face life with humor.'"
195
+ Q: What did Master Sito say about life?
196
+ A: Face life with humor.
197
+ ---------------------
198
+ Transcript Example 2:
199
+ "According to the lecture, Master Sito is a monk living in seclusion."
200
+ Q: Who is Master Sito?
201
+ A: He is a monk.
202
+ ---------------------
203
+ Now, using the transcript below:
204
+ Transcript:
205
+ {context}
206
+
207
+ Question: {question}
208
+ Answer:
209
+ """
210
+
211
+ prompt = ChatPromptTemplate.from_template(template=main_template)
212
+
213
+ chain = (
214
+ {"context": retriever, "question": RunnablePassthrough()}
215
+ | prompt
216
+ | llm
217
+ | StrOutputParser()
218
+ )
219
+
220
+ print("RAG chain setup complete!")
221
+ return chain
222
+
223
+ except Exception as e:
224
+ raise Exception(f"Error setting up RAG chain: {str(e)}")
225
+
226
+ def handle_indexing_request(transcript_text):
227
+ global vector_db, rag_chain
228
+ if not transcript_text or len(transcript_text.strip()) == 0:
229
+ return "⚠️ Transcript is empty. Please transcribe or paste something first."
230
+ try:
231
+ chunks = chunk_transcript(transcript_text)
232
+ vector_db = create_vector_db(chunks)
233
+ rag_chain = setup_rag_chain(vector_db)
234
+ return f"βœ… Indexing complete. {len(chunks)} chunks indexed."
235
+ except Exception as e:
236
+ return f"❌ Indexing failed: {str(e)}"
237
+
238
+ def query(chain, question: str):
239
+ if not chain:
240
+ print("RAG chain not initialized!")
241
+ try:
242
+ return chain.invoke(question)
243
+ except Exception as e:
244
+ raise Exception(f"Error processing query: {str(e)}")
245
+
246
+ def answer_query_using_rag(user_query):
247
+ global rag_chain
248
+ if not rag_chain:
249
+ return "⚠️ Please index the transcript first."
250
+ try:
251
+ result = query(rag_chain, user_query)
252
+ return f"πŸ’¬ {result}"
253
+ except Exception as e:
254
+ return f"❌ Error: {str(e)}"
255
+ #-------------Quiz Generation with few shot prompting---------------------------------------------
256
+ def setup_quiz_chain():
257
+ try:
258
+ llm_quiz = ChatGoogleGenerativeAI(
259
+ model="gemini-2.0-flash",
260
+ temperature=0.1,
261
+ # Consider setting a reasonable max_tokens limit, e.g., max_tokens=1024
262
+ max_tokens=None,
263
+ # Consider setting an explicit timeout, e.g., timeout=120
264
+ timeout=None,
265
+ max_retries=2,
266
+ google_api_key=API_KEY
267
+ )
268
+
269
+ quiz_template = """
270
+ You are an educational assistant. Your task is to generate 5 multiple-choice quiz questions based only on the transcript provided below.
271
+ Please return the output strictly as valid JSON. Do not include any introductory text or markdown formatting around the JSON object.
272
+ The JSON should be a list containing 5 objects, each following this format:
273
+
274
+ {{
275
+ "question": "Your quiz question here.",
276
+ "options": ["Option A", "Option B", "Option C", "Option D"],
277
+ "answer": "The correct option (must exactly match one of the options)"
278
+ }}
279
+
280
+ Transcript:
281
+ {transcript}
282
+
283
+ JSON Output:
284
+ """ # Added "JSON Output:" hint and refined instructions slightly
285
+
286
+ quiz_prompt = PromptTemplate.from_template(quiz_template)
287
+ # For standard JSON:
288
+ parser = JsonOutputParser()
289
+
290
+ # Update the chain to use the JsonOutputParser
291
+ chain = (
292
+ {"transcript": RunnablePassthrough()}
293
+ | quiz_prompt
294
+ | llm_quiz
295
+ | parser # <-- Use JsonOutputParser instead of StrOutputParser
296
+ )
297
+ print("Quiz chain setup complete!")
298
+ return chain
299
+
300
+ except Exception as e:
301
+ raise Exception(f"Error setting up Quiz chain: {str(e)}")
302
+
303
+
304
+ # --- Global Quiz State ---
305
+ quiz_state = None
306
+
307
+ # --- Function to Generate Quiz ---
308
+ def generate_quiz(transcript: str):
309
+ global quiz_state
310
+ if not transcript or transcript.strip() == "":
311
+ return "⚠️ Please provide a transcript.", [], "No quiz generated."
312
+ try:
313
+ chain = setup_quiz_chain()
314
+ output = chain.invoke({"transcript": transcript})
315
+ print("DEBUG - Chain output:", output)
316
+ quiz_data = output # Already parsed JSON from JsonOutputParser.
317
+ except Exception as e:
318
+ return f"Quiz generation failed: {str(e)}", [], "Error occurred."
319
+ if not quiz_data or len(quiz_data) == 0:
320
+ return "⚠️ No quiz questions returned by the model.", [], ""
321
+
322
+ # Initialize quiz state with an additional 'answered' flag.
323
+ quiz_state = {
324
+ "questions": quiz_data,
325
+ "current_index": 0,
326
+ "score": 0,
327
+ "streak": 0, # New: Track consecutive correct answers.
328
+ "answered": False # New: Flag to indicate if the current question is answered.
329
+ }
330
+
331
+ first_question = quiz_data[0]
332
+ return first_question["question"], first_question["options"], ""
333
+
334
+ # --- Function to Evaluate Answer (without advancing to next question) ---
335
+ def select_answer(index: int):
336
+ global quiz_state
337
+ if not quiz_state or "questions" not in quiz_state:
338
+ return "No quiz generated. Please generate a quiz first.", "N/A", "N/A", "N/A", "N/A", "⚠️", "Score: 0 | Streak: 0"
339
+
340
+ # Prevent re-answering if the question was already answered.
341
+ if quiz_state.get("answered", False):
342
+ current_question = quiz_state["questions"][quiz_state["current_index"]]
343
+ options = current_question.get("options", [])
344
+ btn_labels = [options[i] if i < len(options) else "N/A" for i in range(4)]
345
+ return (current_question["question"], btn_labels[0], btn_labels[1], btn_labels[2], btn_labels[3],
346
+ "You have already answered. Click 'Next Question' to continue.",
347
+ f"Score: {quiz_state.get('score', 0)} | Streak: {quiz_state.get('streak', 0)}")
348
+
349
+ current_question = quiz_state["questions"][quiz_state["current_index"]]
350
+ options = current_question.get("options", [])
351
+ if index >= len(options):
352
+ return "Invalid option selected.", "N/A", "N/A", "N/A", "N/A", "Error", f"Score: {quiz_state.get('score', 0)} | Streak: {quiz_state.get('streak', 0)}"
353
+
354
+ selected_option = options[index]
355
+
356
+ # Check answer and update score and streak.
357
+ if selected_option == current_question["answer"]:
358
+ feedback = "Correct!"
359
+ quiz_state["score"] += 1
360
+ quiz_state["streak"] += 1
361
+ else:
362
+ feedback = f"Incorrect. The correct answer was: {current_question['answer']}."
363
+ quiz_state["streak"] = 0
364
+
365
+ quiz_state["answered"] = True # Mark the question as answered.
366
+ btn_labels = [options[i] if i < len(options) else "N/A" for i in range(4)]
367
+ score_text = f"Score: {quiz_state['score']} | Streak: {quiz_state['streak']}"
368
+ return (current_question["question"], btn_labels[0], btn_labels[1], btn_labels[2], btn_labels[3],
369
+ feedback, score_text)
370
+
371
+ # --- Function to Advance to the Next Question ---
372
+ def advance_to_next_question():
373
+ global quiz_state
374
+ if not quiz_state or "questions" not in quiz_state:
375
+ return "No quiz generated. Please generate a quiz first.", "N/A", "N/A", "N/A", "N/A", "⚠️", "Score: 0 | Streak: 0"
376
+
377
+ if not quiz_state.get("answered", False):
378
+ return "Please select an answer before proceeding.", "N/A", "N/A", "N/A", "N/A", "⚠️", f"Score: {quiz_state['score']} | Streak: {quiz_state['streak']}"
379
+
380
+ quiz_state["current_index"] += 1
381
+ quiz_state["answered"] = False # Reset the answered flag.
382
+ if quiz_state["current_index"] < len(quiz_state["questions"]):
383
+ next_q = quiz_state["questions"][quiz_state["current_index"]]
384
+ options = next_q.get("options", [])
385
+ btn_labels = [options[i] if i < len(options) else "N/A" for i in range(4)]
386
+ return (next_q["question"], btn_labels[0], btn_labels[1], btn_labels[2], btn_labels[3],
387
+ "", f"Score: {quiz_state['score']} | Streak: {quiz_state['streak']}")
388
+ else:
389
+ score = quiz_state["score"]
390
+ total = len(quiz_state["questions"])
391
+ percentage = round((score / total) * 100)
392
+ color = "red" if percentage < 60 else "green"
393
+ # Display final score with some HTML styling.
394
+ percent_display = f"<span style='color:{color}; font-weight:bold;'>{percentage}%</span>"
395
+ final_msg = f"Quiz complete! Your final score is {score} out of {total}: {percent_display}."
396
+ quiz_state = None
397
+ return final_msg, "", "", "", "", "", ""
398
+
399
+
400
+ # --- Combined function to update quiz question & button labels on generation ---
401
+ def generate_quiz_and_buttons(transcript: str):
402
+ question, options, feedback = generate_quiz(transcript)
403
+ btn_labels = ["N/A", "N/A", "N/A", "N/A"]
404
+ if isinstance(options, list):
405
+ for i in range(min(len(options), 4)):
406
+ btn_labels[i] = options[i]
407
+ score_text = "Score: 0 | Streak: 0"
408
+ return question, btn_labels[0], btn_labels[1], btn_labels[2], btn_labels[3], feedback, score_text
409
+
410
+ def select_answer_and_update(index: int):
411
+ # (Call our select_answer function.)
412
+ return select_answer(index)
413
+
414
+ def load_transcript(full_text):
415
+ # For now, simply return the same text.
416
+ # Adjust this function based on your intended behavior.
417
+ return full_text
418
+
419
+ def clear_transcript():
420
+ # This dummy implementation clears the transcript and returns a cleared status message.
421
+ return "", "Transcript cleared."
422
+
423
+ def handle_query_request(user_query):
424
+ if not user_query or not user_query.strip():
425
+ return "⚠️ Please enter a valid question about the lecture."
426
+
427
+ # Hypothetical function that uses your indexed transcript + LLM:
428
+ return answer_query_using_rag(user_query)
429
+
430
+ # ------------------ Gradio Interface with Custom Retro Theme ------------------
431
+ with gr.Blocks(
432
+ theme="d8ahazard/material_design_rd",
433
+ css="""
434
+ @import url('https://fonts.cdnfonts.com/css/minecraft-4');
435
+ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
436
+
437
+ /* Universal (normal) font styles */
438
+ body,
439
+ .gradio-container,
440
+ .gr-button,
441
+ .gr-markdown,
442
+ .gr-textbox,
443
+ h1, h2, h3, h4, p {
444
+ font-family: "Arial", sans-serif !important;
445
+ color: #fff !important;
446
+ letter-spacing: 0.05em;
447
+ line-height: 1.6;
448
+ background-color: #101010 !important;
449
+ margin: 0;
450
+ padding: 0;
451
+ }
452
+
453
+ /* Accent color definition for buttons and highlights */
454
+ .accent-bg {
455
+ background-color: #8e44ad !important;
456
+ font-family: 'Press Start 2P', monospace !important;
457
+ color: #000 !important;
458
+ text-shadow: none;
459
+ }
460
+
461
+ .accent-bg:hover {
462
+ background-color: #9c59bd !important;
463
+ }
464
+
465
+ /* Special Minecraft heading class (for header only) */
466
+ .minecraft-heading {
467
+ font-family: 'Minecraft', sans-serif !important;
468
+ letter-spacing: 0.15em;
469
+ font-size: 28px;
470
+ }
471
+
472
+ /* Typewriter effect for header */
473
+ .typewriter {
474
+ overflow: hidden;
475
+ border-right: 0.15em solid #fff;
476
+ white-space: nowrap;
477
+ animation: typing 2.5s steps(30, end), blink-caret 0.75s step-end infinite;
478
+ width: fit-content;
479
+ font-weight: 700;
480
+ line-height: 1.8;
481
+ }
482
+
483
+ @keyframes typing {
484
+ from { width: 0; }
485
+ to { width: 100%; }
486
+ }
487
+
488
+ @keyframes blink-caret {
489
+ from, to { border-color: transparent; }
490
+ 50% { border-color: #fff; }
491
+ }
492
+
493
+ /* Bot logo with bounce animation */
494
+ #bot-logo {
495
+ animation: bounce 1.2s ease infinite !important;
496
+ border-radius: 8px;
497
+ width: 90px;
498
+ height: 90px;
499
+ object-fit: contain;
500
+ margin-right: 8px;
501
+ }
502
+
503
+ @keyframes bounce {
504
+ 0%, 100% { transform: translateY(0); }
505
+ 50% { transform: translateY(-8px); }
506
+ }
507
+
508
+ /* Dark-themed textboxes for consistency */
509
+ .gr-textbox, .gr-textbox textarea, .gr-textbox input {
510
+ background-color: #2b2b2b !important;
511
+ color: #fff !important;
512
+ border: 1px solid #555 !important;
513
+ border-radius: 4px;
514
+ padding: 4px 8px;
515
+ }
516
+
517
+ ::placeholder {
518
+ color: #aaa !important;
519
+ opacity: 1;
520
+ }
521
+
522
+ /* Additional spacing for header and elements */
523
+ #header {
524
+ margin-bottom: 16px;
525
+ }
526
+
527
+ .tab-content {
528
+ padding: 16px;
529
+ }
530
+
531
+ /* Retro-styled scoreboard for Quiz Generator */
532
+ #quiz-scoreboard {
533
+ border: 2px solid #0aff0a;
534
+ padding: 8px;
535
+ margin-bottom: 8px;
536
+ font-family: 'Press Start 2P', monospace;
537
+ color: #0aff0a;
538
+ background-color: #000;
539
+ text-align: right;
540
+ }
541
+
542
+ /* Retro panel style (for prompts or feedback) with green fonts */
543
+ .retro-panel {
544
+ border: 2px solid #ff66ff;
545
+ background-color: #111;
546
+ padding: 8px;
547
+ margin-bottom: 8px;
548
+ font-family: 'Press Start 2P', monospace;
549
+ color: #0aff0a !important;
550
+ text-align: center;
551
+ text-shadow: 0 0 4px #0aff0a, 0 0 8px #0aff0a;
552
+ }
553
+
554
+ /* Neon text glow effect for headings and other elements */
555
+ .neon-text {
556
+ text-shadow: 0 0 4px #ff66ff, 0 0 8px #ff66ff;
557
+ }
558
+
559
+ /* Neon style for tab labels */
560
+ .gradio-container .tabs button {
561
+ font-family: 'Press Start 2P', monospace !important;
562
+ color: #ff66ff !important;
563
+ text-shadow: 0 0 4px #ff66ff, 0 0 8px #ff66ff;
564
+ background-color: transparent !important;
565
+ border: none !important;
566
+ }
567
+
568
+ .gradio-container .tabs button:hover {
569
+ background-color: #333 !important;
570
+ }
571
+
572
+ .gradio-container .tabs button.selected {
573
+ color: #0aff0a !important;
574
+ text-shadow: 0 0 4px #0aff0a, 0 0 8px #0aff0a;
575
+ }
576
+
577
+ /* --- START: Added CSS to hide Audio elements --- */
578
+ .gradio-audio .file-drop svg {
579
+ display: none !important;
580
+ }
581
+
582
+ /* Optional: Hide the "Drop Audio Here" text as well */
583
+ /*
584
+ .gradio-audio .file-drop span {
585
+ display: none !important;
586
+ }
587
+ */
588
+ /* --- END: Added CSS to hide Audio elements --- */
589
+ """
590
+ ) as app:
591
+
592
+
593
+ # Link Minecraft font and Press Start 2P for retro elements
594
+ gr.Markdown('<link href="https://fonts.cdnfonts.com/css/minecraft-4" rel="stylesheet">')
595
+ gr.Markdown('<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">')
596
+
597
+ # -- Header with Minecraft only on the H2 --
598
+ with gr.Row():
599
+ with gr.Column(scale=0, min_width=90):
600
+ # gr.HTML(""" <img src='/kaggle/input/bot1-jpg/Assistant (1).jpg' id='bot-logo'>""")
601
+ gr.Image(value="/kaggle/input/bot1-jpg/Assistant (1).jpg", show_label=False, elem_id="bot-logo", height=90)
602
+ # gr.HTML("""<img id="bot-logo" src="/kaggle/input/bot1-jpg/Assistant (1).jpg" alt="Bot" />""")
603
+ # gr.Markdown("### My Bot is here!")
604
+ with gr.Column(scale=1):
605
+ gr.Markdown(
606
+ """
607
+ <h2 class="minecraft-heading typewriter neon-text" style="margin: 0;">
608
+ Inclusive Classroom Assistant
609
+ </h2>
610
+ <p class="neon-text" style="margin: 4px 0 0 0; font-size: 14px;">
611
+ Upload audio, transcribe, index, and ask anything about your lecture!
612
+ </p>
613
+ """,
614
+ elem_id="header"
615
+ )
616
+
617
+ # ------------------ Tab 1: Transcription & Indexing ------------------
618
+ with gr.Tab("πŸŽ™οΈ Transcription & Indexing") as tab1:
619
+ with gr.Row():
620
+ with gr.Column(scale=1):
621
+ gr.Markdown("<h3 class='neon-text'>Transcription Input</h3>")
622
+ audio_input = gr.Audio(type="filepath", show_label=False)
623
+ transcribe_button = gr.Button("Transcribe Chunk", elem_classes="accent-bg")
624
+ transcription_input_status_textbox = gr.Textbox(label="Transcription Input Status", lines=1, interactive=False)
625
+ latest_chunk_textbox = gr.Textbox(label="Latest Transcript Chunk", lines=10, interactive=False)
626
+ status_textbox = gr.Textbox(label="Status", lines=1, interactive=False)
627
+ with gr.Column(scale=1):
628
+ gr.Markdown("<h3 class='neon-text'>Full Transcript & Indexing</h3>")
629
+ full_transcript_textbox = gr.Textbox(label="Full Lecture Transcript", lines=20, interactive=False)
630
+ with gr.Row():
631
+ index_button = gr.Button("Index Transcript for Search", elem_classes="accent-bg")
632
+ clear_button = gr.Button("Clear Full Transcript", elem_classes="accent-bg")
633
+ indexing_status_display = gr.Textbox(label="Indexing Status", lines=2, interactive=False)
634
+
635
+ # ------------------ Tab 2: Query Lecture Content ------------------
636
+ with gr.Tab("πŸ’¬ Query Lecture Content") as tab2:
637
+ gr.Markdown("<h3 class='neon-text'>Ask a question about the lecture content</h3>")
638
+ with gr.Row():
639
+ query_input_textbox = gr.Textbox(
640
+ label="Ask a question",
641
+ placeholder="E.g., Ask a question related to the lecture?",
642
+ lines=2
643
+ )
644
+ ask_button = gr.Button("Ask Question", elem_classes="accent-bg")
645
+ # Answer display with neon and retro effects
646
+ answer_display = gr.Markdown(
647
+ "πŸ’‘ Answer will appear here...",
648
+ elem_classes="query-answer-box retro-panel neon-text",
649
+ # label="Answer" # Markdown doesn't have a label param like this
650
+ )
651
+
652
+ # ------------------ Tab 3: Quiz Generator ------------------
653
+ with gr.Tab("πŸ“ Quiz Generator") as tab3:
654
+ # Scoreboard only in this tab with retro neon style
655
+ scoreboard = gr.Markdown("Score: 0 | Streak: 0", elem_id="quiz-scoreboard")
656
+ gr.Markdown("<h3 class='neon-text'>Generate Quiz from Transcript</h3>")
657
+ gr.Markdown("<p class='retro-panel neon-text'>Click <strong>Generate Quiz</strong> to start. Answer each question and review your score and correct answer streak after each question.</p>")
658
+ generate_btn = gr.Button("Generate Quiz", elem_classes="accent-bg")
659
+ quiz_question = gr.Markdown("Question will appear here", elem_classes="retro-panel neon-text")
660
+ with gr.Row():
661
+ option_button1 = gr.Button("Option 1", elem_classes="accent-bg")
662
+ option_button2 = gr.Button("Option 2", elem_classes="accent-bg")
663
+ option_button3 = gr.Button("Option 3", elem_classes="accent-bg")
664
+ option_button4 = gr.Button("Option 4", elem_classes="accent-bg")
665
+ feedback_box = gr.Textbox(label="Feedback", interactive=False, elem_classes="retro-panel neon-text")
666
+ next_btn = gr.Button("Next Question", elem_classes="accent-bg")
667
+
668
+ # ------------------ Button Callback Bindings (Placeholder - Add your actual functions) ------------------
669
+
670
+ transcribe_button.click(
671
+ fn=handle_transcription_request,
672
+ inputs=[audio_input],
673
+ outputs=[latest_chunk_textbox, full_transcript_textbox, audio_input, status_textbox, transcription_input_status_textbox]
674
+ )
675
+ index_button.click(
676
+ fn=handle_indexing_request,
677
+ inputs=[full_transcript_textbox],
678
+ outputs=[indexing_status_display]
679
+ )
680
+ clear_button.click(
681
+ fn=clear_transcript_data,
682
+ inputs=None,
683
+ outputs=[full_transcript_textbox, status_textbox]
684
+ )
685
+ ask_button.click(
686
+ fn=handle_query_request,
687
+ inputs=[query_input_textbox],
688
+ outputs=[answer_display]
689
+ )
690
+ generate_btn.click(
691
+ fn=generate_quiz_and_buttons,
692
+ inputs=[full_transcript_textbox],
693
+ outputs=[quiz_question, option_button1, option_button2, option_button3, option_button4, feedback_box, scoreboard]
694
+ )
695
+ option_button1.click(
696
+ fn=lambda: select_answer_and_update(0),
697
+ inputs=[],
698
+ outputs=[quiz_question, option_button1, option_button2, option_button3, option_button4, feedback_box, scoreboard]
699
+ )
700
+ option_button2.click(
701
+ fn=lambda: select_answer_and_update(1),
702
+ inputs=[],
703
+ outputs=[quiz_question, option_button1, option_button2, option_button3, option_button4, feedback_box, scoreboard]
704
+ )
705
+ option_button3.click(
706
+ fn=lambda: select_answer_and_update(2),
707
+ inputs=[],
708
+ outputs=[quiz_question, option_button1, option_button2, option_button3, option_button4, feedback_box, scoreboard]
709
+ )
710
+ option_button4.click(
711
+ fn=lambda: select_answer_and_update(3),
712
+ inputs=[],
713
+ outputs=[quiz_question, option_button1, option_button2, option_button3, option_button4, feedback_box, scoreboard]
714
+ )
715
+ next_btn.click(
716
+ fn=advance_to_next_question,
717
+ inputs=[],
718
+ outputs=[quiz_question, option_button1, option_button2, option_button3, option_button4, feedback_box, scoreboard]
719
+ )
720
+
721
+ app.launch()