Update app.py
Browse files
app.py
CHANGED
@@ -151,7 +151,10 @@ def get_embedding_from_db(conn, table_name, crc32_column, crc32_value, model_nam
|
|
151 |
return None
|
152 |
|
153 |
def get_movie_data_from_db(conn, movie_ids):
|
154 |
-
"""
|
|
|
|
|
|
|
155 |
movie_data_dict = {}
|
156 |
try:
|
157 |
with conn.cursor() as cur:
|
@@ -161,14 +164,21 @@ def get_movie_data_from_db(conn, movie_ids):
|
|
161 |
'Название', data->>'name',
|
162 |
'Год', data->>'year',
|
163 |
'Жанры', (SELECT string_agg(genre->>'name', ', ') FROM jsonb_array_elements(data->'genres') AS genre),
|
164 |
-
'Описание', COALESCE(data->>'description', '')
|
|
|
|
|
165 |
) AS prepared_json
|
166 |
FROM "{movies_table}"
|
167 |
WHERE id IN %s
|
168 |
""", (tuple(movie_ids),))
|
169 |
for movie_id, movie_data, prepared_json in cur.fetchall():
|
170 |
-
prepared_string =
|
171 |
-
|
|
|
|
|
|
|
|
|
|
|
172 |
except Exception as e:
|
173 |
logging.error(f"Ошибка при получении данных фильмов из БД: {e}")
|
174 |
return movie_data_dict
|
@@ -207,7 +217,7 @@ def rerank_with_api(query, results, top_k, rerank_top_k=None, api_key=None):
|
|
207 |
|
208 |
documents = []
|
209 |
for movie_id, _ in results:
|
210 |
-
movie_data, prepared_string = movie_data_dict.get(movie_id, (None, None))
|
211 |
if movie_data:
|
212 |
documents.append(prepared_string)
|
213 |
else:
|
@@ -344,14 +354,16 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: Optional[i
|
|
344 |
formatted_results = []
|
345 |
for movie_id, score in reranked_results:
|
346 |
# Находим данные фильма
|
347 |
-
movie_data, _ = movie_data_dict.get(movie_id, (None, None))
|
348 |
if movie_data:
|
349 |
formatted_results.append({
|
350 |
"movie_id": movie_id,
|
351 |
-
"name":
|
352 |
-
"year":
|
353 |
-
"genres": [
|
354 |
-
"description":
|
|
|
|
|
355 |
"relevance_score": score if rerank_success else (
|
356 |
movie_data_dict.get(movie_id, (None, None))[1] if movie_data_dict.get(movie_id,(None, None)) is not None else 0.0) # Сохраняем similarity, если нет реранжировки
|
357 |
})
|
@@ -537,40 +549,80 @@ async def root():
|
|
537 |
gap: 20px;
|
538 |
}
|
539 |
.movie-card {
|
|
|
540 |
border-radius: 10px;
|
541 |
overflow: hidden;
|
542 |
background-color: #111;
|
543 |
transition: transform 0.1s ease;
|
|
|
544 |
}
|
545 |
.movie-card:hover,
|
546 |
.movie-card:focus {
|
547 |
transform: scale(1.05);
|
548 |
}
|
|
|
|
|
|
|
|
|
|
|
549 |
.movie-info {
|
550 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
551 |
}
|
552 |
.movie-title {
|
|
|
|
|
553 |
font-size: 1.1em;
|
554 |
font-weight: bold;
|
555 |
-
|
|
|
|
|
556 |
}
|
557 |
-
.
|
558 |
-
|
|
|
|
|
|
|
|
|
|
|
559 |
color: #ccc;
|
560 |
-
margin-bottom: 5px;
|
561 |
}
|
562 |
-
.movie-
|
563 |
-
|
564 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
565 |
}
|
566 |
-
.movie-description {
|
|
|
567 |
font-size: 0.9em;
|
568 |
line-height: 1.4;
|
569 |
-
margin-
|
570 |
-
}
|
571 |
-
.relevance-score {
|
572 |
-
font-size: 0.8em;
|
573 |
-
color: #ccc;
|
574 |
}
|
575 |
|
576 |
@media (min-width: 769px) {
|
@@ -591,6 +643,9 @@ async def root():
|
|
591 |
.movie-grid {
|
592 |
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
593 |
}
|
|
|
|
|
|
|
594 |
.logo {
|
595 |
font-size: 1.5em;
|
596 |
}
|
@@ -599,6 +654,9 @@ async def root():
|
|
599 |
.movie-grid {
|
600 |
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
601 |
}
|
|
|
|
|
|
|
602 |
.logo {
|
603 |
font-size: 1.3em;
|
604 |
}
|
@@ -621,7 +679,12 @@ async def root():
|
|
621 |
<div class="movie-grid" id="movieGrid"></div>
|
622 |
</main>
|
623 |
</div>
|
624 |
-
|
|
|
|
|
|
|
|
|
|
|
625 |
<script>
|
626 |
document.getElementById('searchForm').addEventListener('submit', function(event) {
|
627 |
event.preventDefault();
|
@@ -641,15 +704,20 @@ async def root():
|
|
641 |
data.results.forEach(movie => {
|
642 |
const movieCard = document.createElement('div');
|
643 |
movieCard.className = 'movie-card';
|
|
|
644 |
movieCard.innerHTML = `
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
<
|
649 |
-
<
|
650 |
-
<div class="relevance-score">Релевантность: ${movie.relevance_score.toFixed(4)}</div>
|
651 |
</div>
|
|
|
|
|
652 |
`;
|
|
|
|
|
|
|
653 |
movieGrid.appendChild(movieCard);
|
654 |
});
|
655 |
} else {
|
@@ -661,6 +729,25 @@ async def root():
|
|
661 |
movieGrid.innerHTML = '<p>Произошла ошибка при поиске.</p>';
|
662 |
});
|
663 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
664 |
</script>
|
665 |
</body>
|
666 |
</html>
|
|
|
151 |
return None
|
152 |
|
153 |
def get_movie_data_from_db(conn, movie_ids):
|
154 |
+
"""
|
155 |
+
Получает данные фильмов из таблицы Movies по списку ID,
|
156 |
+
включая предположительно URL-адрес постера и рейтинг.
|
157 |
+
"""
|
158 |
movie_data_dict = {}
|
159 |
try:
|
160 |
with conn.cursor() as cur:
|
|
|
164 |
'Название', data->>'name',
|
165 |
'Год', data->>'year',
|
166 |
'Жанры', (SELECT string_agg(genre->>'name', ', ') FROM jsonb_array_elements(data->'genres') AS genre),
|
167 |
+
'Описание', COALESCE(data->>'description', ''),
|
168 |
+
'Постер', data->'poster'->'previewUrl',
|
169 |
+
'Рейтинг', data->'rating'->'kp'
|
170 |
) AS prepared_json
|
171 |
FROM "{movies_table}"
|
172 |
WHERE id IN %s
|
173 |
""", (tuple(movie_ids),))
|
174 |
for movie_id, movie_data, prepared_json in cur.fetchall():
|
175 |
+
prepared_string = (
|
176 |
+
f"Название: {prepared_json['Название']}\n"
|
177 |
+
f"Год: {prepared_json['Год']}\n"
|
178 |
+
f"Жанры: {prepared_json['Жанры']}\n"
|
179 |
+
f"Описание: {prepared_json['Описание']}"
|
180 |
+
)
|
181 |
+
movie_data_dict[movie_id] = (movie_data, prepared_string, prepared_json)
|
182 |
except Exception as e:
|
183 |
logging.error(f"Ошибка при получении данных фильмов из БД: {e}")
|
184 |
return movie_data_dict
|
|
|
217 |
|
218 |
documents = []
|
219 |
for movie_id, _ in results:
|
220 |
+
movie_data, prepared_string, _ = movie_data_dict.get(movie_id, (None, None, None))
|
221 |
if movie_data:
|
222 |
documents.append(prepared_string)
|
223 |
else:
|
|
|
354 |
formatted_results = []
|
355 |
for movie_id, score in reranked_results:
|
356 |
# Находим данные фильма
|
357 |
+
movie_data, _, prepared_json = movie_data_dict.get(movie_id, (None, None, None))
|
358 |
if movie_data:
|
359 |
formatted_results.append({
|
360 |
"movie_id": movie_id,
|
361 |
+
"name": prepared_json['Название'],
|
362 |
+
"year": prepared_json['Год'],
|
363 |
+
"genres": prepared_json['Жанры'],
|
364 |
+
"description": prepared_json['Описание'],
|
365 |
+
"poster_preview_url": prepared_json['Постер'],
|
366 |
+
"rating_kp": prepared_json['Рейтинг'],
|
367 |
"relevance_score": score if rerank_success else (
|
368 |
movie_data_dict.get(movie_id, (None, None))[1] if movie_data_dict.get(movie_id,(None, None)) is not None else 0.0) # Сохраняем similarity, если нет реранжировки
|
369 |
})
|
|
|
549 |
gap: 20px;
|
550 |
}
|
551 |
.movie-card {
|
552 |
+
position: relative;
|
553 |
border-radius: 10px;
|
554 |
overflow: hidden;
|
555 |
background-color: #111;
|
556 |
transition: transform 0.1s ease;
|
557 |
+
cursor: pointer;
|
558 |
}
|
559 |
.movie-card:hover,
|
560 |
.movie-card:focus {
|
561 |
transform: scale(1.05);
|
562 |
}
|
563 |
+
.movie-poster {
|
564 |
+
width: 100%;
|
565 |
+
height: 350px;
|
566 |
+
object-fit: cover;
|
567 |
+
}
|
568 |
.movie-info {
|
569 |
+
position: absolute;
|
570 |
+
top: 0;
|
571 |
+
left: 0;
|
572 |
+
width: 100%;
|
573 |
+
height: 100%;
|
574 |
+
display: flex;
|
575 |
+
flex-direction: column;
|
576 |
+
justify-content: space-between;
|
577 |
}
|
578 |
.movie-title {
|
579 |
+
width: 100%;
|
580 |
+
background-color: #111;
|
581 |
font-size: 1.1em;
|
582 |
font-weight: bold;
|
583 |
+
text-align: center;
|
584 |
+
padding: 10px;
|
585 |
+
box-sizing: border-box;
|
586 |
}
|
587 |
+
.top-info {
|
588 |
+
display: flex;
|
589 |
+
justify-content: space-between;
|
590 |
+
padding: 10px;
|
591 |
+
}
|
592 |
+
.movie-year, .movie-rating {
|
593 |
+
font-size: 0.8em;
|
594 |
color: #ccc;
|
|
|
595 |
}
|
596 |
+
.movie-details {
|
597 |
+
display: none;
|
598 |
+
position: fixed;
|
599 |
+
top: 0;
|
600 |
+
left: 0;
|
601 |
+
width: 100%;
|
602 |
+
height: 100%;
|
603 |
+
background-color: rgba(0, 0, 0, 0.9);
|
604 |
+
z-index: 100;
|
605 |
+
overflow-y: auto;
|
606 |
+
}
|
607 |
+
.movie-details-content {
|
608 |
+
padding: 20px;
|
609 |
+
}
|
610 |
+
.close-button {
|
611 |
+
position: absolute;
|
612 |
+
top: 10px;
|
613 |
+
right: 10px;
|
614 |
+
background: none;
|
615 |
+
border: none;
|
616 |
+
color: #fff;
|
617 |
+
font-size: 30px;
|
618 |
+
cursor: pointer;
|
619 |
+
z-index: 101;
|
620 |
}
|
621 |
+
.movie-description-modal {
|
622 |
+
color: #fff;
|
623 |
font-size: 0.9em;
|
624 |
line-height: 1.4;
|
625 |
+
margin-top: 20px;
|
|
|
|
|
|
|
|
|
626 |
}
|
627 |
|
628 |
@media (min-width: 769px) {
|
|
|
643 |
.movie-grid {
|
644 |
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
645 |
}
|
646 |
+
.movie-poster {
|
647 |
+
height: 225px;
|
648 |
+
}
|
649 |
.logo {
|
650 |
font-size: 1.5em;
|
651 |
}
|
|
|
654 |
.movie-grid {
|
655 |
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
656 |
}
|
657 |
+
.movie-poster {
|
658 |
+
height: 180px;
|
659 |
+
}
|
660 |
.logo {
|
661 |
font-size: 1.3em;
|
662 |
}
|
|
|
679 |
<div class="movie-grid" id="movieGrid"></div>
|
680 |
</main>
|
681 |
</div>
|
682 |
+
<div id="movieDetails" class="movie-details">
|
683 |
+
<div class="movie-details-content">
|
684 |
+
<button class="close-button" onclick="closeMovieDetails()">×</button>
|
685 |
+
<div id="movieDescription" class="movie-description-modal"></div>
|
686 |
+
</div>
|
687 |
+
</div>
|
688 |
<script>
|
689 |
document.getElementById('searchForm').addEventListener('submit', function(event) {
|
690 |
event.preventDefault();
|
|
|
704 |
data.results.forEach(movie => {
|
705 |
const movieCard = document.createElement('div');
|
706 |
movieCard.className = 'movie-card';
|
707 |
+
const ratingColor = getRatingColor(movie.rating_kp);
|
708 |
movieCard.innerHTML = `
|
709 |
+
<img src="${movie.poster_preview_url}" alt="${movie.name}" class="movie-poster">
|
710 |
+
<div class="movie-info">
|
711 |
+
<div class="top-info">
|
712 |
+
<span class="movie-year">${movie.year}</span>
|
713 |
+
<span class="movie-rating" style="color: ${ratingColor};">${movie.rating_kp}</span>
|
|
|
714 |
</div>
|
715 |
+
<div class="movie-title">${movie.name}</div>
|
716 |
+
</div>
|
717 |
`;
|
718 |
+
movieCard.addEventListener('click', () => {
|
719 |
+
openMovieDetails(movie.description);
|
720 |
+
});
|
721 |
movieGrid.appendChild(movieCard);
|
722 |
});
|
723 |
} else {
|
|
|
729 |
movieGrid.innerHTML = '<p>Произошла ошибка при поиске.</p>';
|
730 |
});
|
731 |
}
|
732 |
+
function getRatingColor(rating) {
|
733 |
+
if (rating >= 7) {
|
734 |
+
return 'green';
|
735 |
+
} else if (rating >= 5) {
|
736 |
+
return 'orange';
|
737 |
+
} else {
|
738 |
+
return 'red';
|
739 |
+
}
|
740 |
+
}
|
741 |
+
function openMovieDetails(description) {
|
742 |
+
const movieDetails = document.getElementById('movieDetails');
|
743 |
+
const movieDescription = document.getElementById('movieDescription');
|
744 |
+
movieDescription.textContent = description;
|
745 |
+
movieDetails.style.display = 'block';
|
746 |
+
}
|
747 |
+
|
748 |
+
function closeMovieDetails() {
|
749 |
+
document.getElementById('movieDetails').style.display = 'none';
|
750 |
+
}
|
751 |
</script>
|
752 |
</body>
|
753 |
</html>
|