Spaces:
Sleeping
Sleeping
Commit
·
411d2c3
1
Parent(s):
5b2221d
Adding app file
Browse files- .gitignore +1 -0
- app.py +412 -2
- requirements.txt +5 -0
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Models/similarity_matrix.pkl
|
app.py
CHANGED
@@ -1,4 +1,414 @@
|
|
1 |
import streamlit as st
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
+
import requests
|
3 |
+
import pandas as pd
|
4 |
+
import pickle
|
5 |
+
import gdown
|
6 |
+
import os
|
7 |
|
8 |
+
# CSV files URLs as raw data from GitHub repository
|
9 |
+
moviesCSV = "Data/movies.csv"
|
10 |
+
ratingsCSV = "Data/ratings.csv"
|
11 |
+
linksCSV = "Data/links.csv"
|
12 |
+
|
13 |
+
# the folloing code is used to download the similarity matrix from google drive if not exist
|
14 |
+
file_url = 'https://drive.google.com/file/d/1-1bpusE96_Hh0rUxU7YmBo6RiwYLQGVy/view?usp=sharing'
|
15 |
+
output_path = 'Models/similarity_matrix.pkl'
|
16 |
+
|
17 |
+
@st.cache_data
|
18 |
+
def download_model_from_google_drive(file_url, output_path):
|
19 |
+
gdown.download(file_url, output_path, quiet=False)
|
20 |
+
|
21 |
+
|
22 |
+
# # Check if the file already exists
|
23 |
+
if not os.path.exists(output_path):
|
24 |
+
print("Downloading the similarity matrix from Googlr Drive...")
|
25 |
+
# download_model_from_google_drive(file_url, output_path)
|
26 |
+
|
27 |
+
|
28 |
+
# Set page configuration
|
29 |
+
st.set_page_config(page_title="Movie Recommendation", page_icon="🎬", layout="wide")
|
30 |
+
|
31 |
+
# Dummy data for user recommendations
|
32 |
+
user_recommendations = {
|
33 |
+
"1": ["Inception", "The Matrix", "Interstellar"],
|
34 |
+
"2": ["The Amazing Spider-Man", "District 9", "Titanic"]
|
35 |
+
}
|
36 |
+
|
37 |
+
# Function to hash passwords
|
38 |
+
def hash_password(password):
|
39 |
+
return password
|
40 |
+
|
41 |
+
# Dummy user database
|
42 |
+
user_db = {
|
43 |
+
"1": hash_password("password123"),
|
44 |
+
"2": hash_password("mypassword")
|
45 |
+
}
|
46 |
+
|
47 |
+
# Login function
|
48 |
+
def login(email, password):
|
49 |
+
if email in user_db:
|
50 |
+
return True
|
51 |
+
return False
|
52 |
+
|
53 |
+
# Function to fetch movie details from OMDb API
|
54 |
+
def fetch_movie_details(title, api_key="23f109b2"):
|
55 |
+
url = f"http://www.omdbapi.com/?t={title}&apikey={api_key}"
|
56 |
+
response = requests.get(url)
|
57 |
+
return response.json()
|
58 |
+
|
59 |
+
# Display movie details
|
60 |
+
def display_movie_details(movie):
|
61 |
+
if movie['Response'] == 'False':
|
62 |
+
st.write(f"Movie not found: {movie['Error']}")
|
63 |
+
return
|
64 |
+
if movie['imdbRating'] == 'N/A':
|
65 |
+
movie['imdbRating'] = 0
|
66 |
+
imdb_rating = float(movie['imdbRating'])
|
67 |
+
url = f"https://www.imdb.com/title/{movie['imdbID']}/"
|
68 |
+
st.markdown(
|
69 |
+
f"""
|
70 |
+
<div style="
|
71 |
+
background-color: #313131;
|
72 |
+
border-radius: 15px;
|
73 |
+
padding: 10px;
|
74 |
+
margin: 10px 0;
|
75 |
+
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
|
76 |
+
">
|
77 |
+
<div style="display: flex;">
|
78 |
+
<div style="flex: 1;">
|
79 |
+
<BR>
|
80 |
+
<a href="{url}" target="_blank" >
|
81 |
+
<img src="{movie['Poster']}" style="width: 100%; border-radius: 10px;" />
|
82 |
+
</a>
|
83 |
+
</div>
|
84 |
+
<div style="flex: 3; padding-left: 20px;">
|
85 |
+
<h2 style="margin: 0;" anchor="{url}">{movie['Title']}</h2>
|
86 |
+
<p style="color: gray;">
|
87 |
+
<b>Year:</b> {movie['Year']} Rated: {movie['Rated']} <br>
|
88 |
+
<b>Genre:</b> {movie['Genre'].replace(',',' |')} <br>
|
89 |
+
</p>
|
90 |
+
<p>{movie['Plot']}</p>
|
91 |
+
<div style="margin-top: 10px;">
|
92 |
+
<div style="background-color: #e0e0e0; border-radius: 5px; overflow: hidden;">
|
93 |
+
<div style="width: {imdb_rating * 10}%; background-color: #4caf50; padding: 5px 0; text-align: center; color: white;">
|
94 |
+
{imdb_rating}
|
95 |
+
</div>
|
96 |
+
</div>
|
97 |
+
</div>
|
98 |
+
</div>
|
99 |
+
</div>
|
100 |
+
</div>
|
101 |
+
""", unsafe_allow_html=True
|
102 |
+
)
|
103 |
+
|
104 |
+
|
105 |
+
|
106 |
+
|
107 |
+
def print_movie_details(movie):
|
108 |
+
st.markdown(
|
109 |
+
f"""
|
110 |
+
<div class="recommendation">
|
111 |
+
<div style="display: flex;">
|
112 |
+
<div style="flex: 1;">
|
113 |
+
<a href="https://www.imdb.com/title/tt{movie['imdb_id']:07d}/" target="_blank">
|
114 |
+
<img src="{movie['poster_url']}" />
|
115 |
+
</a>
|
116 |
+
</div>
|
117 |
+
<div style="flex: 3; padding-left: 20px;">
|
118 |
+
<h4 style="margin: 0;">{' '.join(movie['title'].split(" ")[:-1])}</h4>
|
119 |
+
<p style="color: gray;">
|
120 |
+
<b>Year:</b> {movie['title'].split(" ")[-1]}<br>
|
121 |
+
<b>Genre:</b> {', '.join(movie['genres'])}<br>
|
122 |
+
<b>Number of Ratings:</b> {movie['num_ratings']}<br>
|
123 |
+
<b>IMDb Rating: </b>{round(movie["imdb_rating"],1)}<br>
|
124 |
+
</p>
|
125 |
+
<div style="margin-top: 10px;">
|
126 |
+
<div style="background-color: #e0e0e0; border-radius: 5px; overflow: hidden;">
|
127 |
+
<div style="width: {movie['avg_rating'] * 20}%; background-color: #4caf50; padding: 5px 0; text-align: center; color: white;">
|
128 |
+
{movie['avg_rating']}
|
129 |
+
</div>
|
130 |
+
</div>
|
131 |
+
</div>
|
132 |
+
</div>
|
133 |
+
</div>
|
134 |
+
</div>
|
135 |
+
""",
|
136 |
+
unsafe_allow_html=True
|
137 |
+
)
|
138 |
+
|
139 |
+
|
140 |
+
|
141 |
+
# Function to load data
|
142 |
+
@st.cache_data
|
143 |
+
def load_data():
|
144 |
+
movies_df = pd.read_csv(moviesCSV)
|
145 |
+
ratings_df = pd.read_csv(ratingsCSV)
|
146 |
+
links_df = pd.read_csv(linksCSV)
|
147 |
+
return movies_df, ratings_df, links_df
|
148 |
+
|
149 |
+
# Function to load similarity matrix
|
150 |
+
@st.cache_data
|
151 |
+
def load_similarity_matrix():
|
152 |
+
with open('Models/similarity_matrix.pkl', 'rb') as f:
|
153 |
+
similarity_df = pickle.load(f)
|
154 |
+
return similarity_df
|
155 |
+
|
156 |
+
# Function to get movie details
|
157 |
+
def get_movie_details(movie_id, df_movies, df_ratings, df_links):
|
158 |
+
try:
|
159 |
+
imdb_id = df_links[df_links['movieId'] == movie_id]['imdbId'].values[0]
|
160 |
+
tmdb_id = df_links[df_links['movieId'] == movie_id]['tmdbId'].values[0]
|
161 |
+
|
162 |
+
movie_data = df_movies[df_movies['movieId'] == movie_id].iloc[0]
|
163 |
+
genres = movie_data['genres'].split('|') if 'genres' in movie_data else []
|
164 |
+
|
165 |
+
avg_rating = df_ratings[df_ratings['movieId'] == movie_id]['rating'].mean()
|
166 |
+
num_ratings = df_ratings[df_ratings['movieId'] == movie_id].shape[0]
|
167 |
+
|
168 |
+
api_key = 'b8c96e534866701532768a313b978c8b'
|
169 |
+
response = requests.get(f'https://api.themoviedb.org/3/movie/{tmdb_id}?api_key={api_key}' )
|
170 |
+
poster_url = response.json().get('poster_path', '')
|
171 |
+
full_poster_url = f'https://image.tmdb.org/t/p/w500{poster_url}' if poster_url else ''
|
172 |
+
imdb_rating = response.json().get('vote_average', 0)
|
173 |
+
|
174 |
+
return {
|
175 |
+
"title": movie_data['title'],
|
176 |
+
"genres": genres,
|
177 |
+
"avg_rating": round(avg_rating, 2),
|
178 |
+
"num_ratings": num_ratings,
|
179 |
+
"imdb_id": imdb_id,
|
180 |
+
"tmdb_id": tmdb_id,
|
181 |
+
"poster_url": full_poster_url,
|
182 |
+
"imdb_rating": imdb_rating
|
183 |
+
}
|
184 |
+
except Exception as e:
|
185 |
+
st.error(f"Error fetching details for movie ID {movie_id}: {e}")
|
186 |
+
return None
|
187 |
+
|
188 |
+
# Function to recommend movies
|
189 |
+
def recommend(movie, similarity_df, movies_df, ratings_df, links_df, k=5):
|
190 |
+
try:
|
191 |
+
index = movies_df[movies_df['title'] == movie].index[0]
|
192 |
+
distances = sorted(list(enumerate(similarity_df.iloc[index])), reverse=True, key=lambda x: x[1])
|
193 |
+
recommended_movies = []
|
194 |
+
for i in distances[1:k+1]:
|
195 |
+
movie_id = movies_df.iloc[i[0]]['movieId']
|
196 |
+
movie_details = get_movie_details(movie_id, movies_df, ratings_df, links_df)
|
197 |
+
if movie_details:
|
198 |
+
recommended_movies.append(movie_details)
|
199 |
+
return recommended_movies
|
200 |
+
except Exception as e:
|
201 |
+
st.error(f"Error generating recommendations: {e}")
|
202 |
+
return []
|
203 |
+
|
204 |
+
# Main app
|
205 |
+
|
206 |
+
def main():
|
207 |
+
st.markdown(
|
208 |
+
"""
|
209 |
+
<style>
|
210 |
+
body {
|
211 |
+
background-image: url("https://repository-images.githubusercontent.com/275336521/20d38e00-6634-11eb-9d1f-6a5232d0f84f");
|
212 |
+
color: #FFFFFF;
|
213 |
+
font-family: 'Arial', sans-serif;
|
214 |
+
}
|
215 |
+
|
216 |
+
.stApp {
|
217 |
+
background: rgba(0, 0, 0, 0.7);
|
218 |
+
border-radius: 15px;
|
219 |
+
padding: 20px;
|
220 |
+
}
|
221 |
+
|
222 |
+
.title {
|
223 |
+
font-size: 3em;
|
224 |
+
text-align: center;
|
225 |
+
margin-bottom: 20px;
|
226 |
+
font-weight: bold;
|
227 |
+
color: #FF0000;
|
228 |
+
}
|
229 |
+
|
230 |
+
.section-title {
|
231 |
+
font-size: 2em;
|
232 |
+
margin-top: 30px;
|
233 |
+
margin-bottom: 20px;
|
234 |
+
text-align: center;
|
235 |
+
color: #FFD700;
|
236 |
+
}
|
237 |
+
|
238 |
+
.recommendation {
|
239 |
+
border: 1px solid #FFD700;
|
240 |
+
padding: 20px;
|
241 |
+
margin-bottom: 20px;
|
242 |
+
border-radius: 15px;
|
243 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
244 |
+
transition: transform 0.2s, box-shadow 0.2s;
|
245 |
+
background-color: rgba(0, 0, 0, 0.8);
|
246 |
+
overflow: hidden;
|
247 |
+
}
|
248 |
+
|
249 |
+
.recommendation:hover {
|
250 |
+
transform: translateY(-10px);
|
251 |
+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.5);
|
252 |
+
}
|
253 |
+
|
254 |
+
.recommendation img {
|
255 |
+
width: 100%;
|
256 |
+
height: 200px;
|
257 |
+
object-fit: cover;
|
258 |
+
border-radius: 10px;
|
259 |
+
margin-bottom: 10px;
|
260 |
+
}
|
261 |
+
|
262 |
+
.movie-details-container {
|
263 |
+
display: flex;
|
264 |
+
align-items: center;
|
265 |
+
margin-bottom: 20px;
|
266 |
+
}
|
267 |
+
|
268 |
+
.movie-details-container .movie-poster {
|
269 |
+
flex: 0 0 auto;
|
270 |
+
width: 30%;
|
271 |
+
margin-right: 20px;
|
272 |
+
}
|
273 |
+
|
274 |
+
.movie-details-container .movie-poster img {
|
275 |
+
width: 100%;
|
276 |
+
border-radius: 10px;
|
277 |
+
}
|
278 |
+
|
279 |
+
.movie-details-container .movie-details {
|
280 |
+
flex: 1 1 auto;
|
281 |
+
}
|
282 |
+
|
283 |
+
.movie-details-container .movie-details p {
|
284 |
+
margin: 5px 0;
|
285 |
+
}
|
286 |
+
|
287 |
+
a {
|
288 |
+
color: #FFD700;
|
289 |
+
text-decoration: none;
|
290 |
+
}
|
291 |
+
|
292 |
+
a:hover {
|
293 |
+
text-decoration: underline;
|
294 |
+
}
|
295 |
+
|
296 |
+
.stSidebar .element-container {
|
297 |
+
background: rgba(0, 0, 0, 0.7);
|
298 |
+
border-radius: 15px;
|
299 |
+
padding: 15px;
|
300 |
+
}
|
301 |
+
|
302 |
+
.stSidebar .stButton button {
|
303 |
+
background-color: #FFD700;
|
304 |
+
color: #000;
|
305 |
+
border: none;
|
306 |
+
border-radius: 10px;
|
307 |
+
padding: 10px;
|
308 |
+
transition: background-color 0.2s, transform 0.2s;
|
309 |
+
}
|
310 |
+
|
311 |
+
.stSidebar .stButton button:hover {
|
312 |
+
background-color: #FFAA00;
|
313 |
+
transform: scale(1.05);
|
314 |
+
}
|
315 |
+
</style>
|
316 |
+
""",
|
317 |
+
unsafe_allow_html=True
|
318 |
+
)
|
319 |
+
|
320 |
+
movies_df, ratings_df, links_df = load_data()
|
321 |
+
similarity_df = load_similarity_matrix()
|
322 |
+
|
323 |
+
st.sidebar.title("Navigation")
|
324 |
+
menu = ["Login", "Movie Similarity"]
|
325 |
+
choice = st.sidebar.selectbox("Select an option", menu)
|
326 |
+
|
327 |
+
if choice == "Login":
|
328 |
+
st.title("Movie Recommendations")
|
329 |
+
st.write("Welcome to the Movie Recommendation App!")
|
330 |
+
st.write("Please login to get personalized movie recommendations. username between (1 and 800)")
|
331 |
+
st.write("leve password blank for now.")
|
332 |
+
|
333 |
+
# Login form
|
334 |
+
st.sidebar.header("Login")
|
335 |
+
email = st.sidebar.text_input("Username")
|
336 |
+
# password = st.sidebar.text_input("Password", type="password")
|
337 |
+
if st.sidebar.button("Login"):
|
338 |
+
if login(email, 'password'):
|
339 |
+
st.sidebar.success("Login successful!")
|
340 |
+
recommendations = user_recommendations.get(email, [])
|
341 |
+
st.write(f"Recommendations for user number {email}:")
|
342 |
+
num_cols = 2
|
343 |
+
cols = st.columns(num_cols)
|
344 |
+
for i, movie_title in enumerate(recommendations):
|
345 |
+
movie = fetch_movie_details(movie_title)
|
346 |
+
if movie['Response'] == 'True':
|
347 |
+
with cols[i % num_cols]:
|
348 |
+
display_movie_details(movie)
|
349 |
+
else:
|
350 |
+
st.write(f"Movie details for '{movie_title}' not found.")
|
351 |
+
else:
|
352 |
+
st.sidebar.error("Invalid email or password")
|
353 |
+
|
354 |
+
elif choice == "Movie Similarity":
|
355 |
+
num_cols = 2
|
356 |
+
cols = st.columns(num_cols)
|
357 |
+
|
358 |
+
# Movie similarity search
|
359 |
+
with cols[0]:
|
360 |
+
st.title("Find Similar Movies")
|
361 |
+
selected_movie = st.selectbox("Type or select a movie from the dropdown", movies_df['title'].unique())
|
362 |
+
k = st.slider("Select the number of recommendations (k)", min_value=1, max_value=50, value=5)
|
363 |
+
button = st.button("Find Similar Movies")
|
364 |
+
with cols[1]:
|
365 |
+
st.title("Choosen Movie Details:")
|
366 |
+
if selected_movie:
|
367 |
+
correct_Name = selected_movie[:-7]
|
368 |
+
movie = fetch_movie_details(correct_Name)
|
369 |
+
if movie['Response'] == 'True':
|
370 |
+
display_movie_details(movie)
|
371 |
+
else:
|
372 |
+
st.write(f"Movie details for '{selected_movie}' not found.")
|
373 |
+
if button:
|
374 |
+
st.write("The rating bar here is token from our dataset and it's between 0 and 5.")
|
375 |
+
if selected_movie:
|
376 |
+
recommendations = recommend(selected_movie, similarity_df, movies_df, ratings_df, links_df, k)
|
377 |
+
if recommendations:
|
378 |
+
st.write(f"Similar movies to '{selected_movie}':")
|
379 |
+
num_cols = 2
|
380 |
+
cols = st.columns(num_cols)
|
381 |
+
|
382 |
+
# movie_id = movies_df[movies_df['title'] == selected_movie]['movieId'].values[0]
|
383 |
+
# movie_details = get_movie_details(movie_id, movies_df, ratings_df, links_df)
|
384 |
+
# if movie_details:
|
385 |
+
# st.markdown(f'<h2 class="section-title">{movie_details["title"]} Details:</h2>', unsafe_allow_html=True)
|
386 |
+
# st.markdown(
|
387 |
+
# f"""
|
388 |
+
# <div class="movie-details-container">
|
389 |
+
# <div class="movie-poster">
|
390 |
+
# <img src="{movie_details['poster_url']}" alt="Movie Poster">
|
391 |
+
# </div>
|
392 |
+
# <div class="movie-details">
|
393 |
+
# <p><b>Genres:</b> {', '.join(movie_details['genres'])}</p>
|
394 |
+
# <p><b>Average Rating:</b> {movie_details['avg_rating']}</p>
|
395 |
+
# <p><b>Number of Ratings:</b> {movie_details['num_ratings']}</p>
|
396 |
+
# <p><b>IMDb :</b> <a href="https://www.imdb.com/title/tt{movie_details['imdb_id']:07d}/" target="_blank">movie link</a></p>
|
397 |
+
# </div>
|
398 |
+
# </div>
|
399 |
+
# """,
|
400 |
+
# unsafe_allow_html=True
|
401 |
+
# )
|
402 |
+
|
403 |
+
|
404 |
+
|
405 |
+
for i, movie in enumerate(recommendations):
|
406 |
+
with cols[i % num_cols]:
|
407 |
+
print_movie_details(movie)
|
408 |
+
else:
|
409 |
+
st.write("No recommendations found.")
|
410 |
+
else:
|
411 |
+
st.write("Please select a movie.")
|
412 |
+
|
413 |
+
if __name__ == "__main__":
|
414 |
+
main()
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit==1.36.0
|
2 |
+
requests==2.31.0
|
3 |
+
pandas==2.2.2
|
4 |
+
pickleshare==0.7.5
|
5 |
+
gdown==5.1.0
|