opex792 commited on
Commit
afe04cd
·
verified ·
1 Parent(s): 930f1da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +244 -55
app.py CHANGED
@@ -216,11 +216,11 @@ def rerank_with_api(query, results, top_k, rerank_top_k=None):
216
  logging.warning("Ответ от API не содержит ключа 'results'.")
217
 
218
  logging.info("Переранжирование завершено.")
219
- return reranked_results
220
 
221
  except requests.exceptions.RequestException as e:
222
  logging.error(f"Ошибка при запросе к API реранжировщика: {e}")
223
- return []
224
 
225
  def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None):
226
  """Внутренняя функция для поиска фильмов по запросу (используется и в Gradio, и в API)."""
@@ -274,12 +274,37 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
274
  conn.close()
275
 
276
  # Переранжируем результаты с помощью API
277
- reranked_results = rerank_with_api(query, results, top_k, rerank_top_k)
 
 
 
 
 
 
278
 
279
  conn = get_db_connection()
280
  movie_ids = [movie_id for movie_id, _ in reranked_results]
281
  movie_data_dict = get_movie_data_from_db(conn, movie_ids)
282
- conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
  formatted_results = []
285
  for movie_id, score in reranked_results:
@@ -292,7 +317,7 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
292
  "year": movie_data['year'],
293
  "genres": [genre['name'] for genre in movie_data['genres']],
294
  "description": movie_data.get('description', ''),
295
- "relevance_score": score
296
  })
297
  else:
298
  logging.warning(f"Данные для фильма с ID {movie_id} не найдены в БД.")
@@ -300,13 +325,22 @@ def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None
300
  search_time = time.time() - start_time
301
  logging.info(f"Поиск выполнен за {search_time:.2f} секунд.")
302
 
303
- return formatted_results, search_time
 
 
 
 
 
 
304
 
305
  except Exception as e:
306
  logging.error(f"Ошибка при выполнении поиска: {e}")
307
- raise
 
 
 
308
 
309
- @app.get("/search/", response_model=List[dict])
310
  async def api_search_movies(query: str = Query(..., description="Поисковый запрос"),
311
  top_k: int = Query(25, description="Количество возвращаемых результатов"),
312
  rerank_top_k: int = Query(None, description="Количество фильмов для передачи в реранкер (если не указано, то top_k*2)")):
@@ -322,84 +356,239 @@ async def api_search_movies(query: str = Query(..., description="Поисков
322
  async def root():
323
  return """
324
  <!DOCTYPE html>
325
- <html>
326
  <head>
327
- <title>Семантический поиск фильмов</title>
 
 
 
328
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  body {
330
- font-family: sans-serif;
331
- margin: 20px;
 
 
 
 
 
 
 
 
332
  }
333
- h1 {
 
 
 
 
 
 
 
 
 
 
 
 
334
  text-align: center;
 
335
  }
336
- label {
337
- display: block;
338
- margin-bottom: 5px;
 
 
 
339
  }
340
- input[type="text"] {
341
- width: 100%;
342
  padding: 10px;
343
- margin-bottom: 10px;
344
- border: 1px solid #ccc;
345
- box-sizing: border-box;
 
 
 
 
346
  }
347
- button {
348
  padding: 10px 20px;
349
- background-color: #4CAF50;
350
- color: white;
351
  border: none;
 
 
352
  cursor: pointer;
 
 
353
  }
354
- #results {
355
- margin-top: 20px;
 
 
356
  }
357
- .movie {
358
- border: 1px solid #ccc;
 
 
 
 
 
 
 
 
 
359
  padding: 10px;
360
- margin-bottom: 10px;
361
  }
362
- .movie h3 {
363
- margin-top: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  }
365
  </style>
 
 
366
  </head>
367
  <body>
368
- <h1>Семантический поиск фильмов</h1>
369
- <label for="query">Введите запрос:</label>
370
- <input type="text" id="query" name="query" placeholder="Введите описание фильма...">
371
- <button onclick="searchMovies()">Искать</button>
372
- <div id="results"></div>
 
 
 
 
 
 
 
 
373
 
374
  <script>
 
 
 
 
 
375
  function searchMovies() {
376
- const query = document.getElementById("query").value;
377
- const resultsDiv = document.getElementById("results");
378
- resultsDiv.innerHTML = "<p>Поиск...</p>";
379
 
380
  fetch(`/search/?query=${encodeURIComponent(query)}`)
381
  .then(response => response.json())
382
  .then(data => {
383
- resultsDiv.innerHTML = "";
384
- if (data.length === 0) {
385
- resultsDiv.innerHTML = "<p>Ничего не найдено.</p>";
386
- } else {
387
- data.forEach(movie => {
388
- const movieDiv = document.createElement("div");
389
- movieDiv.classList.add("movie");
390
- movieDiv.innerHTML = `
391
- <h3>${movie.name} (${movie.year})</h3>
392
- <p><strong>Жанры:</strong> ${movie.genres.join(", ")}</p>
393
- <p><strong>Описание:</strong> ${movie.description}</p>
394
- <p><strong>Релевантность:</strong> ${movie.relevance_score.toFixed(4)}</p>
 
395
  `;
396
- resultsDiv.appendChild(movieDiv);
397
  });
 
 
398
  }
399
  })
400
  .catch(error => {
401
- console.error("Ошибка:", error);
402
- resultsDiv.innerHTML = "<p>Произошла ошибка при поиске.</p>";
403
  });
404
  }
405
  </script>
@@ -414,4 +603,4 @@ async def root():
414
 
415
  # Запускаем FastAPI
416
  if __name__ == "__main__":
417
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
216
  logging.warning("Ответ от API не содержит ключа 'results'.")
217
 
218
  logging.info("Переранжирование завершено.")
219
+ return reranked_results, True
220
 
221
  except requests.exceptions.RequestException as e:
222
  logging.error(f"Ошибка при запросе к API реранжировщика: {e}")
223
+ return results, False
224
 
225
  def search_movies_internal(query: str, top_k: int = 25, rerank_top_k: int = None):
226
  """Внутренняя функция для поиска фильмов по запросу (используется и в Gradio, и в API)."""
 
274
  conn.close()
275
 
276
  # Переранжируем результаты с помощью API
277
+ reranked_results, rerank_success = rerank_with_api(query, results, top_k, rerank_top_k)
278
+
279
+ if not rerank_success:
280
+ logging.warning("Переранжировка не удалась, используются сырые результаты.")
281
+ reranked_results = results[:top_k] # Используем срез для ограничения количества результатов
282
+ else:
283
+ reranked_results = reranked_results[:top_k]
284
 
285
  conn = get_db_connection()
286
  movie_ids = [movie_id for movie_id, _ in reranked_results]
287
  movie_data_dict = get_movie_data_from_db(conn, movie_ids)
288
+
289
+ # Получаем общее количество фильмов в базе данных
290
+ try:
291
+ with conn.cursor() as cur:
292
+ cur.execute(f'SELECT COUNT(*) FROM "{movies_table}"')
293
+ total_movies = cur.fetchone()[0]
294
+ except Exception as e:
295
+ logging.error(f"Ошибка при получении общего количества фильмов: {e}")
296
+ total_movies = 0
297
+
298
+ # Получаем количество фильмов, по которым производился поиск (количество строк в movie_embeddings)
299
+ try:
300
+ with conn.cursor() as cur:
301
+ cur.execute(f'SELECT COUNT(*) FROM "{embeddings_table}"')
302
+ searched_movies = cur.fetchone()[0]
303
+ except Exception as e:
304
+ logging.error(f"Ошибка при получении количества фильмов для поиска: {e}")
305
+ searched_movies = 0
306
+ finally:
307
+ conn.close()
308
 
309
  formatted_results = []
310
  for movie_id, score in reranked_results:
 
317
  "year": movie_data['year'],
318
  "genres": [genre['name'] for genre in movie_data['genres']],
319
  "description": movie_data.get('description', ''),
320
+ "relevance_score": score if rerank_success else (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, если нет реранжировки
321
  })
322
  else:
323
  logging.warning(f"Данные для фильма с ID {movie_id} не найдены в БД.")
 
325
  search_time = time.time() - start_time
326
  logging.info(f"Поиск выполнен за {search_time:.2f} секунд.")
327
 
328
+ return {
329
+ "status": "success",
330
+ "results": formatted_results,
331
+ "search_time": search_time,
332
+ "total_movies": total_movies,
333
+ "searched_movies": searched_movies
334
+ }, search_time
335
 
336
  except Exception as e:
337
  logging.error(f"Ошибка при выполнении поиска: {e}")
338
+ return {
339
+ "status": "error",
340
+ "message": str(e)
341
+ }, 0
342
 
343
+ @app.get("/search/", response_model=dict)
344
  async def api_search_movies(query: str = Query(..., description="Поисковый запрос"),
345
  top_k: int = Query(25, description="Количество возвращаемых результатов"),
346
  rerank_top_k: int = Query(None, description="Количество фильмов для передачи в реранкер (если не указано, то top_k*2)")):
 
356
  async def root():
357
  return """
358
  <!DOCTYPE html>
359
+ <html lang="ru">
360
  <head>
361
+ <meta name="robots" content="noindex, nofollow">
362
+ <meta charset="UTF-8">
363
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
364
+ <title>VooFlex</title>
365
  <style>
366
+ * {
367
+ -webkit-tap-highlight-color: transparent;
368
+ -webkit-touch-callout: none;
369
+ -webkit-user-select: none;
370
+ user-select: none;
371
+ }
372
+
373
+ *:focus {
374
+ outline: none !important;
375
+ -webkit-tap-highlight-color: transparent;
376
+ }
377
+
378
+ input, button, a, div {
379
+ -webkit-tap-highlight-color: rgba(0,0,0,0);
380
+ -webkit-tap-highlight-color: transparent;
381
+ -webkit-user-select: text;
382
+ user-select: text;
383
+ }
384
+
385
+ .movie-card, .search-button, .close-button {
386
+ -webkit-tap-highlight-color: transparent;
387
+ -webkit-touch-callout: none;
388
+ -webkit-user-select: none;
389
+ user-select: none;
390
+ outline: none !important;
391
+ }
392
+
393
+ .search-input {
394
+ -webkit-user-select: text;
395
+ user-select: text;
396
+ }
397
+
398
+ .movie-card:active,
399
+ .movie-card:focus,
400
+ .search-button:active,
401
+ .search-button:focus,
402
+ .close-button:active,
403
+ .close-button:focus {
404
+ outline: none !important;
405
+ -webkit-tap-highlight-color: transparent;
406
+ background-color: inherit;
407
+ }
408
+
409
  body {
410
+ font-family: 'Roboto', sans-serif;
411
+ margin: 0;
412
+ padding: 0;
413
+ background-color: #000;
414
+ color: #fff;
415
+ }
416
+ .container {
417
+ max-width: 1200px;
418
+ margin: 0 auto;
419
+ padding: 20px;
420
  }
421
+ header {
422
+ display: flex;
423
+ justify-content: space-between;
424
+ align-items: center;
425
+ margin-bottom: 30px;
426
+ flex-wrap: wrap;
427
+ }
428
+ .logo {
429
+ font-size: 24px;
430
+ font-weight: bold;
431
+ white-space: nowrap;
432
+ flex: 1;
433
+ min-width: 100%;
434
  text-align: center;
435
+ margin-bottom: 10px;
436
  }
437
+ .search-form {
438
+ display: flex;
439
+ border-radius: 10px;
440
+ overflow: hidden;
441
+ flex: 1;
442
+ min-width: 100%;
443
  }
444
+ .search-input {
 
445
  padding: 10px;
446
+ font-size: 16px;
447
+ border: none;
448
+ background-color: #222;
449
+ color: #fff;
450
+ font-family: 'Roboto', sans-serif;
451
+ outline: none;
452
+ width: 100%;
453
  }
454
+ .search-button {
455
  padding: 10px 20px;
456
+ font-size: 16px;
 
457
  border: none;
458
+ background-color: #333;
459
+ color: #fff;
460
  cursor: pointer;
461
+ font-family: 'Roboto', sans-serif;
462
+ white-space: nowrap;
463
  }
464
+ .movie-grid {
465
+ display: grid;
466
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
467
+ gap: 20px;
468
  }
469
+ .movie-card {
470
+ border-radius: 10px;
471
+ overflow: hidden;
472
+ background-color: #111;
473
+ transition: transform 0.1s ease;
474
+ }
475
+ .movie-card:hover,
476
+ .movie-card:focus {
477
+ transform: scale(1.05);
478
+ }
479
+ .movie-info {
480
  padding: 10px;
 
481
  }
482
+ .movie-title {
483
+ font-size: 1.1em;
484
+ font-weight: bold;
485
+ margin-bottom: 5px;
486
+ }
487
+ .movie-year {
488
+ font-size: 0.9em;
489
+ color: #ccc;
490
+ margin-bottom: 5px;
491
+ }
492
+ .movie-genres {
493
+ font-size: 0.9em;
494
+ margin-bottom: 5px;
495
+ }
496
+ .movie-description {
497
+ font-size: 0.9em;
498
+ line-height: 1.4;
499
+ margin-bottom: 5px;
500
+ }
501
+ .relevance-score {
502
+ font-size: 0.8em;
503
+ color: #ccc;
504
+ }
505
+
506
+ @media (min-width: 769px) {
507
+ .movie-grid {
508
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
509
+ }
510
+ .logo {
511
+ font-size: 2em;
512
+ min-width: auto;
513
+ text-align: left;
514
+ margin-bottom: 0;
515
+ }
516
+ .search-form{
517
+ min-width: 300px;
518
+ }
519
+ }
520
+ @media (max-width: 768px) {
521
+ .movie-grid {
522
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
523
+ }
524
+ .logo {
525
+ font-size: 1.5em;
526
+ }
527
+ }
528
+ @media (max-width: 480px) {
529
+ .movie-grid {
530
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
531
+ }
532
+ .logo {
533
+ font-size: 1.3em;
534
+ }
535
  }
536
  </style>
537
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron&display=swap" rel="stylesheet">
538
+ <link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
539
  </head>
540
  <body>
541
+ <div class="container">
542
+ <header>
543
+ <div class="logo">VooFlex</div>
544
+ <form class="search-form" id="searchForm">
545
+ <input type="text" id="searchInput" placeholder="Поиск фильма..." class="search-input">
546
+ <button type="submit" class="search-button">Поиск</button>
547
+ </form>
548
+ </header>
549
+
550
+ <main>
551
+ <div class="movie-grid" id="movieGrid"></div>
552
+ </main>
553
+ </div>
554
 
555
  <script>
556
+ document.getElementById('searchForm').addEventListener('submit', function(event) {
557
+ event.preventDefault();
558
+ searchMovies();
559
+ });
560
+
561
  function searchMovies() {
562
+ const query = document.getElementById('searchInput').value;
563
+ const movieGrid = document.getElementById('movieGrid');
564
+ movieGrid.innerHTML = '<p>Поиск...</p>';
565
 
566
  fetch(`/search/?query=${encodeURIComponent(query)}`)
567
  .then(response => response.json())
568
  .then(data => {
569
+ movieGrid.innerHTML = '';
570
+ if (data.status === 'success' && data.results.length > 0) {
571
+ data.results.forEach(movie => {
572
+ const movieCard = document.createElement('div');
573
+ movieCard.className = 'movie-card';
574
+ movieCard.innerHTML = `
575
+ <div class="movie-info">
576
+ <div class="movie-title">${movie.name}</div>
577
+ <div class="movie-year">${movie.year}</div>
578
+ <div class="movie-genres">${movie.genres.join(', ')}</div>
579
+ <div class="movie-description">${movie.description}</div>
580
+ <div class="relevance-score">Релевантность: ${movie.relevance_score.toFixed(4)}</div>
581
+ </div>
582
  `;
583
+ movieGrid.appendChild(movieCard);
584
  });
585
+ } else {
586
+ movieGrid.innerHTML = '<p>Ничего не найдено.</p>';
587
  }
588
  })
589
  .catch(error => {
590
+ console.error('Error:', error);
591
+ movieGrid.innerHTML = '<p>Произошла ошибка при поиске.</p>';
592
  });
593
  }
594
  </script>
 
603
 
604
  # Запускаем FastAPI
605
  if __name__ == "__main__":
606
+ uvicorn.run(app, host="0.0.0.0")