agnixcode commited on
Commit
e34ac27
·
verified ·
1 Parent(s): 1d8e642

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -61
app.py CHANGED
@@ -6,7 +6,12 @@ import gradio as gr
6
  import numpy as np
7
  import faiss
8
 
9
- from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled, NoTranscriptFound
 
 
 
 
 
10
  from langchain_text_splitters import RecursiveCharacterTextSplitter
11
  from sentence_transformers import SentenceTransformer
12
  from huggingface_hub import InferenceClient
@@ -22,7 +27,6 @@ full_transcript = ""
22
 
23
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
24
  LLM_MODEL = "mistralai/Mistral-7B-Instruct-v0.3"
25
-
26
  inference_client = InferenceClient(model=LLM_MODEL, token=HF_TOKEN or None)
27
 
28
  # ---------------------------------------------------------------------------
@@ -39,30 +43,55 @@ def _extract_video_id(url: str) -> str:
39
  match = re.search(pattern, url)
40
  if match:
41
  return match.group(1)
42
- raise ValueError(f"Could not extract a valid video ID from URL: {url}")
43
 
44
  # ---------------------------------------------------------------------------
45
  # 1. Fetch transcript
 
 
 
46
  # ---------------------------------------------------------------------------
47
  def get_transcript(url: str) -> str:
48
  video_id = _extract_video_id(url)
 
 
49
  try:
50
- transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  except TranscriptsDisabled:
52
  raise ValueError("Transcripts are disabled for this video.")
53
- except NoTranscriptFound:
54
- try:
55
- transcript_list = (
56
- YouTubeTranscriptApi.list_transcripts(video_id)
57
- .find_generated_transcript(["en", "en-US", "en-GB"])
58
- .fetch()
59
- )
60
- except Exception as inner_exc:
61
- raise ValueError(f"No transcript found. Details: {inner_exc}")
62
  except Exception as exc:
63
- raise ValueError(f"Failed to retrieve transcript: {exc}")
64
-
65
- return " ".join(seg["text"] for seg in transcript_list)
66
 
67
  # ---------------------------------------------------------------------------
68
  # 2. Process video
@@ -74,10 +103,18 @@ def process_video(url: str):
74
  chunk_store = []
75
  full_transcript = ""
76
 
 
 
 
77
  try:
78
  transcript = get_transcript(url)
79
  except ValueError as exc:
80
- return str(exc), ""
 
 
 
 
 
81
 
82
  full_transcript = transcript
83
 
@@ -88,7 +125,7 @@ def process_video(url: str):
88
  )
89
  chunks = splitter.split_text(transcript)
90
  if not chunks:
91
- return "Transcript was fetched but produced no text chunks.", transcript
92
 
93
  chunk_store = chunks
94
 
@@ -105,7 +142,7 @@ def process_video(url: str):
105
  f" • Chunks created : {len(chunks)}\n"
106
  f" • Embedding dim : {dim}\n"
107
  f" • FAISS vectors : {index.ntotal}\n\n"
108
- f"Switch to the Chat with Video tab to start asking questions."
109
  )
110
  return status, transcript
111
 
@@ -120,9 +157,9 @@ def retrieve_context(query: str, top_k: int = 3) -> str:
120
  query_vec = np.array(query_vec, dtype="float32")
121
 
122
  k = min(top_k, len(chunk_store))
123
- distances, indices = faiss_index.search(query_vec, k)
124
 
125
- retrieved = [chunk_store[i] for i in indices[0] if i < len(chunk_store)]
126
  return "\n\n".join(retrieved)
127
 
128
  # ---------------------------------------------------------------------------
@@ -131,20 +168,20 @@ def retrieve_context(query: str, top_k: int = 3) -> str:
131
  def generate_answer(query: str) -> str:
132
  if faiss_index is None:
133
  return (
134
- "⚠️ No video has been processed yet. "
135
- "Please go to the Process Video tab and load a YouTube URL first."
136
  )
137
 
138
  context = retrieve_context(query, top_k=3)
139
  if not context:
140
- return "⚠️ Could not retrieve any relevant context for your question."
141
 
142
  system_prompt = (
143
- "You are a helpful assistant that answers questions strictly based on "
144
- "the provided transcript context. "
145
- "If the answer is not contained in the context, say: "
146
  "'I could not find this information in the video transcript.' "
147
- "Do NOT make up information."
148
  )
149
 
150
  user_prompt = (
@@ -154,57 +191,53 @@ def generate_answer(query: str) -> str:
154
  f"Answer:"
155
  )
156
 
157
- messages = [
158
- {"role": "system", "content": system_prompt},
159
- {"role": "user", "content": user_prompt},
160
- ]
161
-
162
  try:
163
  response = inference_client.chat_completion(
164
- messages=messages,
 
 
 
165
  max_tokens=512,
166
  temperature=0.2,
167
  top_p=0.9,
168
  )
169
- answer = response.choices[0].message.content.strip()
170
  except Exception as exc:
171
- answer = (
172
- f"❌ Model inference failed: {exc}\n\n"
173
- "Make sure HF_TOKEN is set and the model endpoint is available."
174
  )
175
 
176
- return answer
177
-
178
  # ---------------------------------------------------------------------------
179
  # 5. Chat helper
 
180
  # ---------------------------------------------------------------------------
181
  def chat(user_message: str, history: list):
182
  if not user_message.strip():
183
- history.append([user_message, "Please enter a question."])
184
  return history, ""
185
-
186
  answer = generate_answer(user_message)
187
- history.append([user_message, answer])
188
  return history, ""
189
 
190
  # ---------------------------------------------------------------------------
191
- # 6. Gradio UI – fully Gradio 6.0 compatible (no deprecated args)
192
  # ---------------------------------------------------------------------------
193
  with gr.Blocks(title="YouTube RAG Chatbot") as app:
194
 
195
  gr.Markdown(
196
  """
197
  # 🎬 YouTube RAG Chatbot
198
- **Process any YouTube video and chat with its transcript using RAG.**
199
- > Set your `HF_TOKEN` as a Space secret for LLM inference to work.
200
  """
201
  )
202
 
203
  with gr.Tabs():
204
 
205
- # ── Tab 1 ──────────────────────────────────────────────────────────
206
  with gr.TabItem("📥 Process Video"):
207
- gr.Markdown("Paste a YouTube URL and click **Process**.")
208
 
209
  with gr.Row():
210
  url_input = gr.Textbox(
@@ -220,7 +253,7 @@ with gr.Blocks(title="YouTube RAG Chatbot") as app:
220
  interactive=False,
221
  )
222
  transcript_output = gr.Textbox(
223
- label="Transcript (read-only)",
224
  lines=15,
225
  interactive=False,
226
  )
@@ -231,42 +264,53 @@ with gr.Blocks(title="YouTube RAG Chatbot") as app:
231
  outputs=[status_output, transcript_output],
232
  )
233
 
234
- # ── Tab 2 ──────────────────────────────────────────────────────────
235
  with gr.TabItem("💬 Chat with Video"):
236
- gr.Markdown("Ask any question about the processed video.")
237
 
238
- chatbot = gr.Chatbot(
239
- label="Conversation",
240
- height=450,
241
- )
242
 
243
  with gr.Row():
244
  query_input = gr.Textbox(
245
  label="Your question",
246
- placeholder="What is the main topic discussed in this video?",
247
  scale=5,
248
  )
249
  send_btn = gr.Button("Send 🚀", variant="primary", scale=1)
250
 
251
- clear_btn = gr.Button("🗑️ Clear conversation", variant="secondary")
 
 
252
  chat_history = gr.State([])
253
 
254
  send_btn.click(
255
  fn=chat,
256
  inputs=[query_input, chat_history],
257
  outputs=[chatbot, query_input],
258
- ).then(fn=lambda h: h, inputs=[chatbot], outputs=[chat_history])
 
 
 
 
259
 
260
  query_input.submit(
261
  fn=chat,
262
  inputs=[query_input, chat_history],
263
  outputs=[chatbot, query_input],
264
- ).then(fn=lambda h: h, inputs=[chatbot], outputs=[chat_history])
 
 
 
 
265
 
266
- clear_btn.click(fn=lambda: ([], []), outputs=[chatbot, chat_history])
 
 
 
267
 
268
  # ---------------------------------------------------------------------------
269
- # Launch
270
  # ---------------------------------------------------------------------------
271
  if __name__ == "__main__":
272
  app.launch()
 
6
  import numpy as np
7
  import faiss
8
 
9
+ from youtube_transcript_api import (
10
+ YouTubeTranscriptApi,
11
+ TranscriptsDisabled,
12
+ NoTranscriptFound,
13
+ VideoUnavailable,
14
+ )
15
  from langchain_text_splitters import RecursiveCharacterTextSplitter
16
  from sentence_transformers import SentenceTransformer
17
  from huggingface_hub import InferenceClient
 
27
 
28
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
29
  LLM_MODEL = "mistralai/Mistral-7B-Instruct-v0.3"
 
30
  inference_client = InferenceClient(model=LLM_MODEL, token=HF_TOKEN or None)
31
 
32
  # ---------------------------------------------------------------------------
 
43
  match = re.search(pattern, url)
44
  if match:
45
  return match.group(1)
46
+ raise ValueError(f"Could not extract a valid video ID from: {url}")
47
 
48
  # ---------------------------------------------------------------------------
49
  # 1. Fetch transcript
50
+ # Confirmed from source: ALL methods are CLASS methods.
51
+ # get_transcript() returns list of dicts: [{"text": str, "start": float, "duration": float}]
52
+ # Access text with snippet["text"] not snippet.text
53
  # ---------------------------------------------------------------------------
54
  def get_transcript(url: str) -> str:
55
  video_id = _extract_video_id(url)
56
+
57
+ # Primary: try English directly
58
  try:
59
+ snippets = YouTubeTranscriptApi.get_transcript(
60
+ video_id, languages=["en", "en-US", "en-GB"]
61
+ )
62
+ return " ".join(s["text"] for s in snippets)
63
+ except (NoTranscriptFound, TranscriptsDisabled):
64
+ pass
65
+ except VideoUnavailable:
66
+ raise ValueError("This video is unavailable or private.")
67
+ except Exception:
68
+ pass
69
+
70
+ # Fallback: list all, pick first available, fetch it
71
+ try:
72
+ transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
73
+ transcript = None
74
+ # prefer any english variant
75
+ for t in transcript_list:
76
+ if t.language_code.startswith("en"):
77
+ transcript = t
78
+ break
79
+ # if no english, take the first one
80
+ if transcript is None:
81
+ for t in transcript_list:
82
+ transcript = t
83
+ break
84
+ if transcript is None:
85
+ raise ValueError("No transcripts are available for this video.")
86
+ # fetch() returns list of dicts [{"text":..., "start":..., "duration":...}]
87
+ snippets = transcript.fetch()
88
+ return " ".join(s["text"] for s in snippets)
89
+ except ValueError:
90
+ raise
91
  except TranscriptsDisabled:
92
  raise ValueError("Transcripts are disabled for this video.")
 
 
 
 
 
 
 
 
 
93
  except Exception as exc:
94
+ raise ValueError(f"Could not retrieve transcript: {exc}")
 
 
95
 
96
  # ---------------------------------------------------------------------------
97
  # 2. Process video
 
103
  chunk_store = []
104
  full_transcript = ""
105
 
106
+ if not url.strip():
107
+ return "⚠️ Please enter a YouTube URL.", ""
108
+
109
  try:
110
  transcript = get_transcript(url)
111
  except ValueError as exc:
112
+ return f"❌ {exc}", ""
113
+ except Exception as exc:
114
+ return f"❌ Unexpected error: {exc}", ""
115
+
116
+ if not transcript.strip():
117
+ return "❌ Transcript is empty for this video.", ""
118
 
119
  full_transcript = transcript
120
 
 
125
  )
126
  chunks = splitter.split_text(transcript)
127
  if not chunks:
128
+ return " Could not split transcript into chunks.", transcript
129
 
130
  chunk_store = chunks
131
 
 
142
  f" • Chunks created : {len(chunks)}\n"
143
  f" • Embedding dim : {dim}\n"
144
  f" • FAISS vectors : {index.ntotal}\n\n"
145
+ f"Switch to the 💬 Chat with Video tab to ask questions."
146
  )
147
  return status, transcript
148
 
 
157
  query_vec = np.array(query_vec, dtype="float32")
158
 
159
  k = min(top_k, len(chunk_store))
160
+ _, indices = faiss_index.search(query_vec, k)
161
 
162
+ retrieved = [chunk_store[i] for i in indices[0] if 0 <= i < len(chunk_store)]
163
  return "\n\n".join(retrieved)
164
 
165
  # ---------------------------------------------------------------------------
 
168
  def generate_answer(query: str) -> str:
169
  if faiss_index is None:
170
  return (
171
+ "⚠️ No video processed yet. "
172
+ "Go to 📥 Process Video tab first."
173
  )
174
 
175
  context = retrieve_context(query, top_k=3)
176
  if not context:
177
+ return "⚠️ Could not retrieve relevant context for your question."
178
 
179
  system_prompt = (
180
+ "You are a helpful assistant that answers questions strictly "
181
+ "based on the provided video transcript context. "
182
+ "If the answer is not in the context, say: "
183
  "'I could not find this information in the video transcript.' "
184
+ "Do NOT hallucinate or make up information."
185
  )
186
 
187
  user_prompt = (
 
191
  f"Answer:"
192
  )
193
 
 
 
 
 
 
194
  try:
195
  response = inference_client.chat_completion(
196
+ messages=[
197
+ {"role": "system", "content": system_prompt},
198
+ {"role": "user", "content": user_prompt},
199
+ ],
200
  max_tokens=512,
201
  temperature=0.2,
202
  top_p=0.9,
203
  )
204
+ return response.choices[0].message.content.strip()
205
  except Exception as exc:
206
+ return (
207
+ f"❌ Inference failed: {exc}\n"
208
+ "Check that HF_TOKEN is set correctly as a Space secret."
209
  )
210
 
 
 
211
  # ---------------------------------------------------------------------------
212
  # 5. Chat helper
213
+ # Gradio 6.x Chatbot uses list of [user, bot] pairs (list of lists)
214
  # ---------------------------------------------------------------------------
215
  def chat(user_message: str, history: list):
216
  if not user_message.strip():
217
+ history = history + [["", "⚠️ Please enter a question."]]
218
  return history, ""
 
219
  answer = generate_answer(user_message)
220
+ history = history + [[user_message, answer]]
221
  return history, ""
222
 
223
  # ---------------------------------------------------------------------------
224
+ # 6. Gradio UI fully compatible with Gradio 6.13
225
  # ---------------------------------------------------------------------------
226
  with gr.Blocks(title="YouTube RAG Chatbot") as app:
227
 
228
  gr.Markdown(
229
  """
230
  # 🎬 YouTube RAG Chatbot
231
+ **Fetch any YouTube transcript and chat with it using RAG + Mistral-7B.**
232
+ > 🔑 Add your `HF_TOKEN` in Space **Settings → Secrets** for the LLM to work.
233
  """
234
  )
235
 
236
  with gr.Tabs():
237
 
238
+ # ── Tab 1: Process ─────────────────────────────────────────────────
239
  with gr.TabItem("📥 Process Video"):
240
+ gr.Markdown("Enter a YouTube URL and click **Process** to index the transcript.")
241
 
242
  with gr.Row():
243
  url_input = gr.Textbox(
 
253
  interactive=False,
254
  )
255
  transcript_output = gr.Textbox(
256
+ label="Transcript",
257
  lines=15,
258
  interactive=False,
259
  )
 
264
  outputs=[status_output, transcript_output],
265
  )
266
 
267
+ # ── Tab 2: Chat ────────────────────────────────────────────────────
268
  with gr.TabItem("💬 Chat with Video"):
269
+ gr.Markdown("Ask questions about the video. Answers are grounded in the transcript.")
270
 
271
+ # Gradio 6.13: Chatbot takes list of [user, bot] pairs
272
+ chatbot = gr.Chatbot(label="Conversation", height=450)
 
 
273
 
274
  with gr.Row():
275
  query_input = gr.Textbox(
276
  label="Your question",
277
+ placeholder="What is the main topic of this video?",
278
  scale=5,
279
  )
280
  send_btn = gr.Button("Send 🚀", variant="primary", scale=1)
281
 
282
+ clear_btn = gr.Button("🗑️ Clear", variant="secondary")
283
+
284
+ # gr.State stores the history list between interactions
285
  chat_history = gr.State([])
286
 
287
  send_btn.click(
288
  fn=chat,
289
  inputs=[query_input, chat_history],
290
  outputs=[chatbot, query_input],
291
+ ).then(
292
+ fn=lambda h: h,
293
+ inputs=[chatbot],
294
+ outputs=[chat_history],
295
+ )
296
 
297
  query_input.submit(
298
  fn=chat,
299
  inputs=[query_input, chat_history],
300
  outputs=[chatbot, query_input],
301
+ ).then(
302
+ fn=lambda h: h,
303
+ inputs=[chatbot],
304
+ outputs=[chat_history],
305
+ )
306
 
307
+ clear_btn.click(
308
+ fn=lambda: ([], []),
309
+ outputs=[chatbot, chat_history],
310
+ )
311
 
312
  # ---------------------------------------------------------------------------
313
+ # Launch — theme passed here in Gradio 6.x
314
  # ---------------------------------------------------------------------------
315
  if __name__ == "__main__":
316
  app.launch()