mtoft20 commited on
Commit
d8790e9
Β·
verified Β·
1 Parent(s): 863f64e

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +408 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,410 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import requests
3
+ import pandas as pd
4
+ from together import Together
5
+ import os
6
+
7
+ # =============================================================================
8
+ # CONFIGURATION - Using Secrets Management
9
+ # =============================================================================
10
+ NOCODB_URL = "https://app.nocodb.com" # Base URL
11
+
12
+ # Get sensitive data from Streamlit secrets or environment variables
13
+ def get_api_credentials():
14
+ """Get API credentials from secrets or environment"""
15
+ try:
16
+ # Try Streamlit secrets first (for Hugging Face Spaces)
17
+ api_token = st.secrets.get("NOCODB_API_TOKEN", os.environ.get("NOCODB_API_TOKEN", ""))
18
+ together_key = st.secrets.get("TOGETHER_API_KEY", os.environ.get("TOGETHER_API_KEY", ""))
19
+ endpoint_path = st.secrets.get("NOCODB_ENDPOINT_PATH", os.environ.get("NOCODB_ENDPOINT_PATH", ""))
20
+
21
+ return api_token, together_key, endpoint_path
22
+ except:
23
+ # Fallback to environment variables
24
+ api_token = os.environ.get("NOCODB_API_TOKEN", "")
25
+ together_key = os.environ.get("TOGETHER_API_KEY", "")
26
+ endpoint_path = os.environ.get("NOCODB_ENDPOINT_PATH", "")
27
+
28
+ return api_token, together_key, endpoint_path
29
+
30
+ # Initialize Together AI client
31
+ @st.cache_resource
32
+ def get_ai_client():
33
+ """Initialize Together AI client"""
34
+ _, together_key, _ = get_api_credentials()
35
+ if not together_key:
36
+ st.error("Together AI API key not found. Please configure it in the secrets.")
37
+ return None
38
+ return Together(api_key=together_key)
39
+
40
+ # =============================================================================
41
+ # HELPER FUNCTIONS
42
+ # =============================================================================
43
+ @st.cache_data(ttl=300) # Cache for 5 minutes
44
+ def get_netflix_content():
45
+ """Fetch Netflix content from NocoDB"""
46
+ api_token, _, endpoint_path = get_api_credentials()
47
+
48
+ if not api_token or not endpoint_path:
49
+ st.error("NocoDB credentials not configured. Please set up your secrets.")
50
+ return []
51
+
52
+ headers = {"xc-token": api_token}
53
+
54
+ try:
55
+ response = requests.get(
56
+ f"{NOCODB_URL}{endpoint_path}?limit=1000",
57
+ headers=headers
58
+ )
59
+
60
+ if response.status_code == 200:
61
+ data = response.json()
62
+ return data.get('list', [])
63
+ else:
64
+ st.error(f"Failed to fetch data: {response.status_code}")
65
+ return []
66
+
67
+ except Exception as e:
68
+ st.error(f"Error connecting to database: {e}")
69
+ return []
70
+
71
+ def filter_content(content_list, filters):
72
+ """Apply filters to Netflix content list"""
73
+ filtered = []
74
+
75
+ for content in content_list:
76
+ # Type filter
77
+ if filters['content_type'] and content.get('type') != filters['content_type']:
78
+ continue
79
+
80
+ # Rating filter
81
+ if filters['ratings'] and content.get('rating') not in filters['ratings']:
82
+ continue
83
+
84
+ # Genre filter
85
+ if filters['genres'] and not any(genre in content.get('listed_in', '') for genre in filters['genres']):
86
+ continue
87
+
88
+ # Release year filter
89
+ try:
90
+ release_year = int(content.get('release_year', 0))
91
+ if release_year < filters['year_range'][0] or release_year > filters['year_range'][1]:
92
+ continue
93
+ except (ValueError, TypeError):
94
+ continue
95
+
96
+ # Duration filter (different handling for movies and shows)
97
+ duration = content.get('duration', '')
98
+ if filters['content_type'] == 'Movie' and 'min' in duration:
99
+ try:
100
+ minutes = int(duration.split()[0])
101
+ if minutes < filters['duration_range'][0] or minutes > filters['duration_range'][1]:
102
+ continue
103
+ except (ValueError, IndexError):
104
+ continue
105
+
106
+ filtered.append(content)
107
+
108
+ return filtered
109
+
110
+ def create_content_context(content_list):
111
+ """Create context string about current content for AI"""
112
+ if not content_list:
113
+ return "No content matches the current filters."
114
+
115
+ total = len(content_list)
116
+ movies = sum(1 for c in content_list if c.get('type') == 'Movie')
117
+ shows = sum(1 for c in content_list if c.get('type') == 'TV Show')
118
+
119
+ context = f"""Currently showing {total} Netflix titles ({movies} movies and {shows} TV shows). """
120
+
121
+ # Add some genre info
122
+ all_genres = []
123
+ for content in content_list[:20]: # Sample from first 20 items
124
+ genres = content.get('listed_in', '').split(', ')
125
+ all_genres.extend(genres)
126
+
127
+ if all_genres:
128
+ genre_counts = pd.Series(all_genres).value_counts()
129
+ top_genres = genre_counts.head(5).index.tolist()
130
+ context += f"Top genres include: {', '.join(top_genres)}. "
131
+
132
+ # Add year range
133
+ years = [int(c.get('release_year', 0)) for c in content_list if c.get('release_year')]
134
+ if years:
135
+ context += f"Release years range from {min(years)} to {max(years)}."
136
+
137
+ return context
138
+
139
+ def get_ai_response(client, question, context, model_name):
140
+ """Get response from Together AI"""
141
+ try:
142
+ prompt = f"""You are a helpful Netflix content expert. Based on the current content data, please answer the user's question accurately and helpfully.
143
+
144
+ Current Content Data Context:
145
+ {context}
146
+
147
+ User Question: {question}
148
+
149
+ Please provide a helpful, accurate response based on the data provided. Keep your answer concise but informative."""
150
+
151
+ response = client.chat.completions.create(
152
+ model=model_name,
153
+ messages=[
154
+ {"role": "system", "content": "You are a helpful Netflix content expert with deep knowledge of movies and TV shows."},
155
+ {"role": "user", "content": prompt}
156
+ ],
157
+ max_tokens=300,
158
+ temperature=0.7,
159
+ )
160
+
161
+ return response.choices[0].message.content
162
+
163
+ except Exception as e:
164
+ raise Exception(f"Together AI Error: {str(e)}")
165
+
166
+ # =============================================================================
167
+ # MAIN APP
168
+ # =============================================================================
169
+ def main():
170
+ # Page config
171
+ st.set_page_config(
172
+ page_title="Netflix Content Explorer",
173
+ page_icon="🎬",
174
+ layout="wide"
175
+ )
176
+
177
+ # Header
178
+ st.title("🎬 Netflix Content Explorer")
179
+ st.write("Explore Netflix movies and TV shows with AI-powered insights!")
180
+
181
+ # Check API credentials
182
+ api_token, together_key, endpoint_path = get_api_credentials()
183
+
184
+ if not together_key:
185
+ st.error("⚠️ Together AI API key not configured!")
186
+ st.info("Please set your TOGETHER_API_KEY in the Hugging Face Spaces secrets.")
187
+ st.stop()
188
+
189
+ if not api_token or not endpoint_path:
190
+ st.error("⚠️ NocoDB credentials not configured!")
191
+ st.info("Please set NOCODB_API_TOKEN and NOCODB_ENDPOINT_PATH in the Hugging Face Spaces secrets.")
192
+ st.stop()
193
+
194
+ # Initialize AI client
195
+ try:
196
+ client = get_ai_client()
197
+ if not client:
198
+ st.stop()
199
+ except Exception as e:
200
+ st.error(f"Failed to initialize Together AI client: {e}")
201
+ st.stop()
202
+
203
+ # Load all content first
204
+ with st.spinner("Loading Netflix content..."):
205
+ all_content = get_netflix_content()
206
+
207
+ if not all_content:
208
+ st.error("Could not load Netflix content. Please check your NocoDB connection.")
209
+ st.stop()
210
+
211
+ # Extract unique values for filters
212
+ all_ratings = sorted(list(set([c.get('rating') for c in all_content if c.get('rating')])))
213
+ all_genres = sorted(list(set(
214
+ genre.strip()
215
+ for c in all_content
216
+ for genre in c.get('listed_in', '').split(',')
217
+ if genre.strip()
218
+ )))
219
+
220
+ # Sidebar filters
221
+ st.sidebar.header("πŸ” Filter Content")
222
+
223
+ content_type = st.sidebar.selectbox(
224
+ "Content Type",
225
+ options=["All", "Movie", "TV Show"],
226
+ index=0
227
+ )
228
+
229
+ selected_ratings = st.sidebar.multiselect(
230
+ "Ratings",
231
+ options=all_ratings,
232
+ default=[]
233
+ )
234
+
235
+ selected_genres = st.sidebar.multiselect(
236
+ "Genres",
237
+ options=all_genres,
238
+ default=[]
239
+ )
240
+
241
+ # Year range slider
242
+ years = [int(c.get('release_year', 0)) for c in all_content if c.get('release_year')]
243
+ min_year, max_year = min(years), max(years)
244
+ year_range = st.sidebar.slider(
245
+ "Release Year",
246
+ min_value=min_year,
247
+ max_value=max_year,
248
+ value=(min_year, max_year)
249
+ )
250
+
251
+ # Duration range slider (for movies only)
252
+ movie_durations = [
253
+ int(c.get('duration', '0 min').split()[0])
254
+ for c in all_content
255
+ if c.get('type') == 'Movie' and 'min' in c.get('duration', '')
256
+ ]
257
+ if movie_durations:
258
+ min_duration, max_duration = min(movie_durations), max(movie_durations)
259
+ duration_range = st.sidebar.slider(
260
+ "Movie Duration (minutes)",
261
+ min_value=min_duration,
262
+ max_value=max_duration,
263
+ value=(min_duration, max_duration),
264
+ help="This filter only applies to movies"
265
+ )
266
+ else:
267
+ duration_range = (0, 1000) # Fallback values
268
+
269
+ # Create filter dictionary
270
+ filters = {
271
+ 'content_type': content_type if content_type != "All" else None,
272
+ 'ratings': selected_ratings,
273
+ 'genres': selected_genres,
274
+ 'year_range': year_range,
275
+ 'duration_range': duration_range
276
+ }
277
+
278
+ # Apply filters
279
+ filtered_content = filter_content(all_content, filters)
280
+
281
+ # Main content area
282
+ col1, col2 = st.columns([2, 1])
283
+
284
+ with col1:
285
+ # Content listings
286
+ st.subheader(f"πŸ“‹ Found {len(filtered_content)} Titles")
287
+
288
+ if filtered_content:
289
+ # Show first 10 items
290
+ for i, content in enumerate(filtered_content[:10]):
291
+ with st.expander(f"{content.get('title', 'N/A')} ({content.get('release_year', 'N/A')})"):
292
+ # Content details in columns
293
+ detail_col1, detail_col2 = st.columns(2)
294
+
295
+ with detail_col1:
296
+ st.write(f"**🎭 Type:** {content.get('type', 'N/A')}")
297
+ st.write(f"**⭐ Rating:** {content.get('rating', 'N/A')}")
298
+ st.write(f"**⏱️ Duration:** {content.get('duration', 'N/A')}")
299
+
300
+ with detail_col2:
301
+ st.write(f"**🎬 Genre:** {content.get('listed_in', 'N/A')}")
302
+ st.write(f"**πŸ‘₯ Cast:** {content.get('cast', 'N/A')[:100]}...")
303
+ st.write(f"**πŸ“ Director:** {content.get('director', 'N/A')}")
304
+
305
+ # Description
306
+ st.write(f"**πŸ“– Description:**")
307
+ st.write(content.get('description', 'N/A'))
308
+
309
+ if len(filtered_content) > 10:
310
+ st.info(f"Showing first 10 of {len(filtered_content)} titles. Adjust filters to narrow results.")
311
+ else:
312
+ st.info("No content matches your current filters. Try adjusting the criteria.")
313
+
314
+ with col2:
315
+ # AI Chat Section
316
+ st.subheader("πŸ€– Ask AI Assistant")
317
+ st.write("Ask questions about the Netflix content!")
318
+
319
+ # Model selection for Together AI
320
+ model_choice = st.selectbox(
321
+ "Select AI Model:",
322
+ [
323
+ "google/gemma-2b-it",
324
+ "google/gemma-2-27b-it",
325
+ "mistralai/Mistral-7B-Instruct-v0.1",
326
+ "NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO",
327
+ "mistralai/Mixtral-8x7B-Instruct-v0.1"
328
+ ],
329
+ help="Select an AI model to answer your questions"
330
+ )
331
+
332
+ # Example questions
333
+ with st.expander("πŸ’‘ Example Questions"):
334
+ st.write("β€’ What are the most common genres?")
335
+ st.write("β€’ What's the average movie duration?")
336
+ st.write("β€’ Which directors have the most content?")
337
+ st.write("β€’ What are the trends in content ratings?")
338
+ st.write("β€’ Tell me about the release year distribution")
339
+
340
+ user_question = st.text_area(
341
+ "Your Question:",
342
+ placeholder="Ask about genres, ratings, directors, trends...",
343
+ height=100
344
+ )
345
+
346
+ if st.button("πŸ” Ask AI", type="primary"):
347
+ if user_question:
348
+ with st.spinner("AI is analyzing the content..."):
349
+ # Create context from current filtered data
350
+ context = create_content_context(filtered_content)
351
+
352
+ try:
353
+ # Get AI response
354
+ ai_response = get_ai_response(client, user_question, context, model_choice)
355
+
356
+ st.success("**AI Assistant Response:**")
357
+ st.write(ai_response)
358
+
359
+ # Show debug info
360
+ with st.expander("Debug Info"):
361
+ st.write(f"Model used: {model_choice}")
362
+ st.write(f"Content items analyzed: {len(filtered_content)}")
363
+ st.write(f"Context: {context[:150]}...")
364
+
365
+ except Exception as e:
366
+ st.error(f"AI Error: {str(e)}")
367
+
368
+ # Fallback response with data analysis
369
+ st.info("**Fallback Analysis:**")
370
+ if filtered_content:
371
+ movies = sum(1 for c in filtered_content if c.get('type') == 'Movie')
372
+ shows = sum(1 for c in filtered_content if c.get('type') == 'TV Show')
373
+ st.write(f"β€’ Found {len(filtered_content)} titles ({movies} movies, {shows} TV shows)")
374
+
375
+ # Show top genres
376
+ genres = [g.strip() for c in filtered_content for g in c.get('listed_in', '').split(',')]
377
+ genre_counts = pd.Series(genres).value_counts()
378
+ st.write(f"β€’ Top genres: {', '.join(genre_counts.head(3).index)}")
379
+
380
+ # Show year range
381
+ years = [int(c.get('release_year', 0)) for c in filtered_content if c.get('release_year')]
382
+ if years:
383
+ st.write(f"β€’ Release years: {min(years)} - {max(years)}")
384
+ else:
385
+ st.warning("Please enter a question first!")
386
+
387
+ # Footer stats
388
+ st.markdown("---")
389
+ if all_content:
390
+ total_items = len(all_content)
391
+ filtered_items = len(filtered_content)
392
+
393
+ stat_col1, stat_col2, stat_col3, stat_col4 = st.columns(4)
394
+
395
+ with stat_col1:
396
+ st.metric("Total Titles", total_items)
397
+
398
+ with stat_col2:
399
+ st.metric("Filtered Results", filtered_items)
400
+
401
+ with stat_col3:
402
+ movies = sum(1 for c in filtered_content if c.get('type') == 'Movie')
403
+ st.metric("Movies", movies)
404
+
405
+ with stat_col4:
406
+ shows = sum(1 for c in filtered_content if c.get('type') == 'TV Show')
407
+ st.metric("TV Shows", shows)
408
 
409
+ if __name__ == "__main__":
410
+ main()