mtoft20 commited on
Commit
689fd5a
Β·
verified Β·
1 Parent(s): cb28927

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +309 -216
src/streamlit_app.py CHANGED
@@ -3,6 +3,7 @@ import requests
3
  import pandas as pd
4
  from together import Together
5
  import os
 
6
 
7
  # =============================================================================
8
  # CONFIGURATION - Using Secrets Management
@@ -51,34 +52,57 @@ def get_netflix_content():
51
 
52
  headers = {"xc-token": api_token}
53
  all_content = []
54
- page = 1
55
- page_size = 1000 # Maximum allowed by NocoDB
 
56
 
57
  try:
58
  while True:
59
- offset = (page - 1) * page_size
60
- response = requests.get(
61
- f"{NOCODB_URL}{endpoint_path}?limit={page_size}&offset={offset}",
62
- headers=headers
63
- )
64
 
65
- if response.status_code == 200:
66
- data = response.json()
67
- current_page_content = data.get('list', [])
68
-
69
- if not current_page_content: # No more records
70
- break
 
71
 
72
- all_content.extend(current_page_content)
73
- page += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- # Show progress
76
- st.write(f"Fetched {len(all_content)} records...")
77
- else:
78
- st.error(f"Failed to fetch data: {response.status_code}")
79
  break
80
 
81
- st.write(f"Total records fetched: {len(all_content)}")
 
 
 
 
 
 
 
 
 
 
 
 
82
  return all_content
83
 
84
  except Exception as e:
@@ -193,7 +217,27 @@ def main():
193
 
194
  # Header
195
  st.title("🎬 Netflix Content Explorer")
196
- st.write("Explore Netflix movies and TV shows with AI-powered insights!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
  # Check API credentials
199
  api_token, together_key, endpoint_path = get_api_credentials()
@@ -208,21 +252,15 @@ def main():
208
  st.info("Please set NOCODB_API_TOKEN and NOCODB_ENDPOINT_PATH in the Hugging Face Spaces secrets.")
209
  st.stop()
210
 
211
- # Initialize AI client
212
- try:
213
- client = get_ai_client()
214
- if not client:
215
- st.stop()
216
- except Exception as e:
217
- st.error(f"Failed to initialize Together AI client: {e}")
218
- st.stop()
219
 
220
- # Load all content first
221
- with st.spinner("Loading Netflix content..."):
222
- all_content = get_netflix_content()
223
 
224
  if not all_content:
225
- st.error("Could not load Netflix content. Please check your NocoDB connection.")
226
  st.stop()
227
 
228
  # Extract unique values for filters
@@ -234,15 +272,7 @@ def main():
234
  if genre.strip()
235
  )))
236
 
237
- # Sidebar filters
238
- st.sidebar.header("πŸ” Filter Content")
239
-
240
- content_type = st.sidebar.selectbox(
241
- "Content Type",
242
- options=["All", "Movie", "TV Show"],
243
- index=0
244
- )
245
-
246
  selected_ratings = st.sidebar.multiselect(
247
  "Ratings",
248
  options=all_ratings,
@@ -266,197 +296,260 @@ def main():
266
  )
267
 
268
  # Duration range slider (for movies only)
269
- movie_durations = [
270
- int(c.get('duration', '0 min').split()[0])
271
- for c in all_content
272
- if c.get('type') == 'Movie' and 'min' in c.get('duration', '')
273
- ]
274
- if movie_durations:
275
- min_duration, max_duration = min(movie_durations), max(movie_durations)
276
- duration_range = st.sidebar.slider(
277
- "Movie Duration (minutes)",
278
- min_value=min_duration,
279
- max_value=max_duration,
280
- value=(min_duration, max_duration),
281
- help="This filter only applies to movies"
282
- )
 
 
 
283
  else:
284
- duration_range = (0, 1000) # Fallback values
285
 
286
- # Create filter dictionary
287
- filters = {
288
- 'content_type': content_type if content_type != "All" else None,
289
- 'ratings': selected_ratings,
290
- 'genres': selected_genres,
291
- 'year_range': year_range,
292
- 'duration_range': duration_range
293
- }
294
 
295
- # Apply filters
296
- filtered_content = filter_content(all_content, filters)
 
 
297
 
298
  # Main content area
299
- col1, col2 = st.columns([2, 1])
300
-
301
- with col1:
302
- # Content listings
303
- st.subheader(f"πŸ“‹ Found {len(filtered_content)} Titles")
304
 
305
- if filtered_content:
306
- # Add pagination controls
307
- items_per_page = st.select_slider(
308
- "Items per page",
309
- options=[10, 20, 50, 100],
310
- value=20
311
- )
312
-
313
- total_pages = len(filtered_content) // items_per_page + (1 if len(filtered_content) % items_per_page > 0 else 0)
314
- current_page = st.number_input(
315
- "Page",
316
- min_value=1,
317
- max_value=total_pages,
318
- value=1
319
- )
320
-
321
- start_idx = (current_page - 1) * items_per_page
322
- end_idx = min(start_idx + items_per_page, len(filtered_content))
323
-
324
- st.write(f"Showing {start_idx + 1} to {end_idx} of {len(filtered_content)} titles")
325
-
326
- # Show items for current page
327
- for i, content in enumerate(filtered_content[start_idx:end_idx], start=start_idx + 1):
328
- with st.expander(f"{i}. {content.get('title', 'N/A')} ({content.get('release_year', 'N/A')})"):
329
- # Content details in columns
330
- detail_col1, detail_col2 = st.columns(2)
331
-
332
- with detail_col1:
333
- st.write(f"**🎭 Type:** {content.get('type', 'N/A')}")
334
- st.write(f"**⭐ Rating:** {content.get('rating', 'N/A')}")
335
- st.write(f"**⏱️ Duration:** {content.get('duration', 'N/A')}")
336
-
337
- with detail_col2:
338
- st.write(f"**🎬 Genre:** {content.get('listed_in', 'N/A')}")
339
- # Fix cast handling to prevent NoneType error
340
- cast = content.get('cast')
341
- cast_display = cast[:100] + "..." if cast else "N/A"
342
- st.write(f"**πŸ‘₯ Cast:** {cast_display}")
343
- st.write(f"**πŸ“ Director:** {content.get('director', 'N/A')}")
344
-
345
- # Description
346
- st.write(f"**πŸ“– Description:**")
347
- st.write(content.get('description', 'N/A'))
348
-
349
- # Add page navigation buttons
350
- col1, col2, col3 = st.columns([1, 2, 1])
351
- with col1:
352
- if current_page > 1:
353
- if st.button("⬅️ Previous"):
354
- st.session_state.page = current_page - 1
355
- with col3:
356
- if current_page < total_pages:
357
- if st.button("Next ➑️"):
358
- st.session_state.page = current_page + 1
359
-
360
- # Show page info
361
- with col2:
362
- st.write(f"Page {current_page} of {total_pages}")
363
- else:
364
- st.info("No content matches your current filters. Try adjusting the criteria.")
365
-
366
- with col2:
367
- # AI Chat Section
368
- st.subheader("πŸ€– Ask AI Assistant")
369
- st.write("Ask questions about the Netflix content!")
370
 
371
- # Model selection for Together AI
372
- model_choice = st.selectbox(
373
- "Select AI Model:",
374
- [
375
- "google/gemma-2b-it",
376
- "google/gemma-2-27b-it",
377
- "mistralai/Mistral-7B-Instruct-v0.1",
378
- "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
379
- "mistralai/Mixtral-8x7B-Instruct-v0.1"
380
- ],
381
- help="Select an AI model to answer your questions"
382
- )
383
 
384
- # Example questions
385
- with st.expander("πŸ’‘ Example Questions"):
386
- st.write("β€’ What are the most common genres?")
387
- st.write("β€’ What's the average movie duration?")
388
- st.write("β€’ Which directors have the most content?")
389
- st.write("β€’ What are the trends in content ratings?")
390
- st.write("β€’ Tell me about the release year distribution")
391
 
392
- user_question = st.text_area(
393
- "Your Question:",
394
- placeholder="Ask about genres, ratings, directors, trends...",
395
- height=100
396
- )
397
 
398
- if st.button("πŸ” Ask AI", type="primary"):
399
- if user_question:
400
- with st.spinner("AI is analyzing the content..."):
401
- # Create context from current filtered data
402
- context = create_content_context(filtered_content)
403
-
404
- try:
405
- # Get AI response
406
- ai_response = get_ai_response(client, user_question, context, model_choice)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
- st.success("**AI Assistant Response:**")
409
- st.write(ai_response)
 
 
410
 
411
- # Show debug info
412
- with st.expander("Debug Info"):
413
- st.write(f"Model used: {model_choice}")
414
- st.write(f"Content items analyzed: {len(filtered_content)}")
415
- st.write(f"Context: {context[:150]}...")
 
 
416
 
417
- except Exception as e:
418
- st.error(f"AI Error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
 
420
- # Fallback response with data analysis
421
- st.info("**Fallback Analysis:**")
422
- if filtered_content:
423
- movies = sum(1 for c in filtered_content if c.get('type') == 'Movie')
424
- shows = sum(1 for c in filtered_content if c.get('type') == 'TV Show')
425
- st.write(f"β€’ Found {len(filtered_content)} titles ({movies} movies, {shows} TV shows)")
426
 
427
- # Show top genres
428
- genres = [g.strip() for c in filtered_content for g in c.get('listed_in', '').split(',')]
429
- genre_counts = pd.Series(genres).value_counts()
430
- st.write(f"β€’ Top genres: {', '.join(genre_counts.head(3).index)}")
431
 
432
- # Show year range
433
- years = [int(c.get('release_year', 0)) for c in filtered_content if c.get('release_year')]
434
- if years:
435
- st.write(f"β€’ Release years: {min(years)} - {max(years)}")
436
- else:
437
- st.warning("Please enter a question first!")
438
-
439
- # Footer stats
440
- st.markdown("---")
441
- if all_content:
442
- total_items = len(all_content)
443
- filtered_items = len(filtered_content)
444
-
445
- stat_col1, stat_col2, stat_col3, stat_col4 = st.columns(4)
446
-
447
- with stat_col1:
448
- st.metric("Total Titles", total_items)
449
-
450
- with stat_col2:
451
- st.metric("Filtered Results", filtered_items)
452
-
453
- with stat_col3:
454
- movies = sum(1 for c in filtered_content if c.get('type') == 'Movie')
455
- st.metric("Movies", movies)
 
 
 
456
 
457
- with stat_col4:
458
- shows = sum(1 for c in filtered_content if c.get('type') == 'TV Show')
459
- st.metric("TV Shows", shows)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
  if __name__ == "__main__":
462
  main()
 
3
  import pandas as pd
4
  from together import Together
5
  import os
6
+ import time
7
 
8
  # =============================================================================
9
  # CONFIGURATION - Using Secrets Management
 
52
 
53
  headers = {"xc-token": api_token}
54
  all_content = []
55
+ offset = 0
56
+ page_size = 100 # Smaller page size for better reliability
57
+ max_retries = 3
58
 
59
  try:
60
  while True:
61
+ retries = 0
62
+ success = False
 
 
 
63
 
64
+ while retries < max_retries and not success:
65
+ try:
66
+ response = requests.get(
67
+ f"{NOCODB_URL}{endpoint_path}?limit={page_size}&offset={offset}",
68
+ headers=headers,
69
+ timeout=10 # Add timeout
70
+ )
71
 
72
+ if response.status_code == 200:
73
+ data = response.json()
74
+ current_page_content = data.get('list', [])
75
+ success = True
76
+ else:
77
+ retries += 1
78
+ st.warning(f"Retry {retries}/{max_retries} - Failed to fetch data (status: {response.status_code})")
79
+ time.sleep(1) # Add delay between retries
80
+
81
+ except (requests.exceptions.RequestException, ValueError) as e:
82
+ retries += 1
83
+ st.warning(f"Retry {retries}/{max_retries} - Error: {str(e)}")
84
+ time.sleep(1)
85
+
86
+ if not success:
87
+ st.error(f"Failed to fetch data at offset {offset} after {max_retries} retries")
88
+ break
89
 
90
+ if not current_page_content: # No more records
 
 
 
91
  break
92
 
93
+ all_content.extend(current_page_content)
94
+ offset += len(current_page_content)
95
+
96
+ # Show progress
97
+ progress_text = f"Fetched {len(all_content)} records..."
98
+ st.write(progress_text)
99
+
100
+ # If we got fewer records than the page size, we've reached the end
101
+ if len(current_page_content) < page_size:
102
+ break
103
+
104
+ if all_content:
105
+ st.success(f"Successfully loaded {len(all_content)} titles!")
106
  return all_content
107
 
108
  except Exception as e:
 
217
 
218
  # Header
219
  st.title("🎬 Netflix Content Explorer")
220
+
221
+ # Sidebar filters
222
+ st.sidebar.header("πŸ” Filter Content")
223
+
224
+ # Required filters section
225
+ st.sidebar.subheader("Required Filters")
226
+ st.sidebar.markdown("*Please select at least one filter to start exploring*")
227
+
228
+ # Content Type (Required)
229
+ content_type = st.sidebar.selectbox(
230
+ "Content Type (Required)",
231
+ options=["Select Type...", "All", "Movie", "TV Show"],
232
+ index=0
233
+ )
234
+
235
+ # Initialize filters_selected in session state if not exists
236
+ if 'filters_selected' not in st.session_state:
237
+ st.session_state.filters_selected = False
238
+
239
+ # Optional filters section
240
+ st.sidebar.subheader("Optional Filters")
241
 
242
  # Check API credentials
243
  api_token, together_key, endpoint_path = get_api_credentials()
 
252
  st.info("Please set NOCODB_API_TOKEN and NOCODB_ENDPOINT_PATH in the Hugging Face Spaces secrets.")
253
  st.stop()
254
 
255
+ # Only fetch data if no content is loaded yet and we need it for filter options
256
+ if 'all_content' not in st.session_state:
257
+ with st.spinner("Loading Netflix content..."):
258
+ st.session_state.all_content = get_netflix_content()
 
 
 
 
259
 
260
+ all_content = st.session_state.all_content
 
 
261
 
262
  if not all_content:
263
+ st.error("Could not load Netflix content. Please check your database connection.")
264
  st.stop()
265
 
266
  # Extract unique values for filters
 
272
  if genre.strip()
273
  )))
274
 
275
+ # Optional filters
 
 
 
 
 
 
 
 
276
  selected_ratings = st.sidebar.multiselect(
277
  "Ratings",
278
  options=all_ratings,
 
296
  )
297
 
298
  # Duration range slider (for movies only)
299
+ if content_type in ["All", "Movie"]:
300
+ movie_durations = [
301
+ int(c.get('duration', '0 min').split()[0])
302
+ for c in all_content
303
+ if c.get('type') == 'Movie' and 'min' in c.get('duration', '')
304
+ ]
305
+ if movie_durations:
306
+ min_duration, max_duration = min(movie_durations), max(movie_durations)
307
+ duration_range = st.sidebar.slider(
308
+ "Movie Duration (minutes)",
309
+ min_value=min_duration,
310
+ max_value=max_duration,
311
+ value=(min_duration, max_duration),
312
+ help="This filter only applies to movies"
313
+ )
314
+ else:
315
+ duration_range = (0, 1000) # Fallback values
316
  else:
317
+ duration_range = (0, 1000)
318
 
319
+ # Apply button
320
+ if st.sidebar.button("Apply Filters", type="primary"):
321
+ if content_type == "Select Type...":
322
+ st.sidebar.error("Please select a Content Type to continue")
323
+ else:
324
+ st.session_state.filters_selected = True
 
 
325
 
326
+ # Reset button
327
+ if st.sidebar.button("Reset Filters"):
328
+ st.session_state.filters_selected = False
329
+ st.experimental_rerun()
330
 
331
  # Main content area
332
+ if not st.session_state.filters_selected:
333
+ # Landing page content
334
+ st.markdown("""
335
+ ## Welcome to Netflix Content Explorer! 🍿
 
336
 
337
+ This application helps you discover and explore Netflix's vast library of movies and TV shows.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
+ ### How to Get Started:
340
+ 1. **Select Content Type** (Required)
341
+ - Choose between Movies, TV Shows, or both
 
 
 
 
 
 
 
 
 
342
 
343
+ 2. **Optional Filters**
344
+ - Ratings: Filter by content ratings
345
+ - Genres: Find specific types of content
346
+ - Release Year: Narrow down by time period
347
+ - Duration: Filter movies by length
 
 
348
 
349
+ 3. **Click 'Apply Filters'** to start exploring!
 
 
 
 
350
 
351
+ ### Features:
352
+ - 🎯 Advanced filtering capabilities
353
+ - πŸ€– AI-powered insights and recommendations
354
+ - πŸ“Š Detailed content information
355
+ - πŸ” Easy-to-use search and navigation
356
+
357
+ ### Dataset Overview:
358
+ - Total Titles: {total_titles}
359
+ - Movies: {movies}
360
+ - TV Shows: {shows}
361
+ - Year Range: {min_year} - {max_year}
362
+ - Number of Genres: {genre_count}
363
+ """.format(
364
+ total_titles=len(all_content),
365
+ movies=len([c for c in all_content if c.get('type') == 'Movie']),
366
+ shows=len([c for c in all_content if c.get('type') == 'TV Show']),
367
+ min_year=min_year,
368
+ max_year=max_year,
369
+ genre_count=len(all_genres)
370
+ ))
371
+
372
+ # Show some random popular titles as examples
373
+ st.markdown("### 🌟 Featured Titles")
374
+ popular_titles = pd.DataFrame(all_content).sample(n=5).to_dict('records')
375
+ for title in popular_titles:
376
+ st.markdown(f"- **{title.get('title')}** ({title.get('type')}, {title.get('release_year')})")
377
+
378
+ else:
379
+ # Create filter dictionary
380
+ filters = {
381
+ 'content_type': content_type if content_type != "All" else None,
382
+ 'ratings': selected_ratings,
383
+ 'genres': selected_genres,
384
+ 'year_range': year_range,
385
+ 'duration_range': duration_range
386
+ }
387
+
388
+ # Apply filters
389
+ filtered_content = filter_content(all_content, filters)
390
+
391
+ # Content display section
392
+ col1, col2 = st.columns([2, 1])
393
+
394
+ with col1:
395
+ # Content listings
396
+ st.subheader(f"πŸ“‹ Found {len(filtered_content)} Titles")
397
+
398
+ if filtered_content:
399
+ # Add pagination controls
400
+ items_per_page = st.select_slider(
401
+ "Items per page",
402
+ options=[10, 20, 50, 100],
403
+ value=20
404
+ )
405
+
406
+ total_pages = len(filtered_content) // items_per_page + (1 if len(filtered_content) % items_per_page > 0 else 0)
407
+ current_page = st.number_input(
408
+ "Page",
409
+ min_value=1,
410
+ max_value=total_pages,
411
+ value=1
412
+ )
413
+
414
+ start_idx = (current_page - 1) * items_per_page
415
+ end_idx = min(start_idx + items_page, len(filtered_content))
416
+
417
+ st.write(f"Showing {start_idx + 1} to {end_idx} of {len(filtered_content)} titles")
418
+
419
+ # Show items for current page
420
+ for i, content in enumerate(filtered_content[start_idx:end_idx], start=start_idx + 1):
421
+ with st.expander(f"{i}. {content.get('title', 'N/A')} ({content.get('release_year', 'N/A')})"):
422
+ # Content details in columns
423
+ detail_col1, detail_col2 = st.columns(2)
424
 
425
+ with detail_col1:
426
+ st.write(f"**🎭 Type:** {content.get('type', 'N/A')}")
427
+ st.write(f"**⭐ Rating:** {content.get('rating', 'N/A')}")
428
+ st.write(f"**⏱️ Duration:** {content.get('duration', 'N/A')}")
429
 
430
+ with detail_col2:
431
+ st.write(f"**🎬 Genre:** {content.get('listed_in', 'N/A')}")
432
+ # Fix cast handling to prevent NoneType error
433
+ cast = content.get('cast')
434
+ cast_display = cast[:100] + "..." if cast else "N/A"
435
+ st.write(f"**πŸ‘₯ Cast:** {cast_display}")
436
+ st.write(f"**πŸ“ Director:** {content.get('director', 'N/A')}")
437
 
438
+ # Description
439
+ st.write(f"**πŸ“– Description:**")
440
+ st.write(content.get('description', 'N/A'))
441
+
442
+ # Add page navigation buttons
443
+ col1, col2, col3 = st.columns([1, 2, 1])
444
+ with col1:
445
+ if current_page > 1:
446
+ if st.button("⬅️ Previous"):
447
+ st.session_state.page = current_page - 1
448
+ with col3:
449
+ if current_page < total_pages:
450
+ if st.button("Next ➑️"):
451
+ st.session_state.page = current_page + 1
452
+
453
+ # Show page info
454
+ with col2:
455
+ st.write(f"Page {current_page} of {total_pages}")
456
+ else:
457
+ st.info("No content matches your current filters. Try adjusting the criteria.")
458
+
459
+ with col2:
460
+ # AI Chat Section
461
+ st.subheader("πŸ€– Ask AI Assistant")
462
+ st.write("Ask questions about the Netflix content!")
463
+
464
+ # Model selection for Together AI
465
+ model_choice = st.selectbox(
466
+ "Select AI Model:",
467
+ [
468
+ "google/gemma-2b-it",
469
+ "google/gemma-2-27b-it",
470
+ "mistralai/Mistral-7B-Instruct-v0.1",
471
+ "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
472
+ "mistralai/Mixtral-8x7B-Instruct-v0.1"
473
+ ],
474
+ help="Select an AI model to answer your questions"
475
+ )
476
+
477
+ # Example questions
478
+ with st.expander("πŸ’‘ Example Questions"):
479
+ st.write("β€’ What are the most common genres?")
480
+ st.write("β€’ What's the average movie duration?")
481
+ st.write("β€’ Which directors have the most content?")
482
+ st.write("β€’ What are the trends in content ratings?")
483
+ st.write("β€’ Tell me about the release year distribution")
484
+
485
+ user_question = st.text_area(
486
+ "Your Question:",
487
+ placeholder="Ask about genres, ratings, directors, trends...",
488
+ height=100
489
+ )
490
+
491
+ if st.button("πŸ” Ask AI", type="primary"):
492
+ if user_question:
493
+ with st.spinner("AI is analyzing the content..."):
494
+ # Create context from current filtered data
495
+ context = create_content_context(filtered_content)
496
 
497
+ try:
498
+ # Get AI response
499
+ ai_response = get_ai_response(client, user_question, context, model_choice)
 
 
 
500
 
501
+ st.success("**AI Assistant Response:**")
502
+ st.write(ai_response)
 
 
503
 
504
+ # Show debug info
505
+ with st.expander("Debug Info"):
506
+ st.write(f"Model used: {model_choice}")
507
+ st.write(f"Content items analyzed: {len(filtered_content)}")
508
+ st.write(f"Context: {context[:150]}...")
509
+
510
+ except Exception as e:
511
+ st.error(f"AI Error: {str(e)}")
512
+
513
+ # Fallback response with data analysis
514
+ st.info("**Fallback Analysis:**")
515
+ if filtered_content:
516
+ movies = sum(1 for c in filtered_content if c.get('type') == 'Movie')
517
+ shows = sum(1 for c in filtered_content if c.get('type') == 'TV Show')
518
+ st.write(f"β€’ Found {len(filtered_content)} titles ({movies} movies, {shows} TV shows)")
519
+
520
+ # Show top genres
521
+ genres = [g.strip() for c in filtered_content for g in c.get('listed_in', '').split(',')]
522
+ genre_counts = pd.Series(genres).value_counts()
523
+ st.write(f"β€’ Top genres: {', '.join(genre_counts.head(3).index)}")
524
+
525
+ # Show year range
526
+ years = [int(c.get('release_year', 0)) for c in filtered_content if c.get('release_year')]
527
+ if years:
528
+ st.write(f"β€’ Release years: {min(years)} - {max(years)}")
529
+ else:
530
+ st.warning("Please enter a question first!")
531
 
532
+ # Footer stats
533
+ st.markdown("---")
534
+ if all_content:
535
+ total_items = len(all_content)
536
+ filtered_items = len(filtered_content)
537
+
538
+ stat_col1, stat_col2, stat_col3, stat_col4 = st.columns(4)
539
+
540
+ with stat_col1:
541
+ st.metric("Total Titles", total_items)
542
+
543
+ with stat_col2:
544
+ st.metric("Filtered Results", filtered_items)
545
+
546
+ with stat_col3:
547
+ movies = sum(1 for c in filtered_content if c.get('type') == 'Movie')
548
+ st.metric("Movies", movies)
549
+
550
+ with stat_col4:
551
+ shows = sum(1 for c in filtered_content if c.get('type') == 'TV Show')
552
+ st.metric("TV Shows", shows)
553
 
554
  if __name__ == "__main__":
555
  main()