import os import pandas as pd import numpy as np import gradio as gr from langchain_community.document_loaders import TextLoader from langchain_text_splitters import CharacterTextSplitter from langchain_chroma import Chroma from langchain_huggingface import HuggingFaceEmbeddings # ========================================================= # BASE DIRECTORY — works both locally and on HuggingFace # ========================================================= BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # ========================================================= # EMBEDDING MODEL # ========================================================= embedding_model = HuggingFaceEmbeddings( model_name="sentence-transformers/all-MiniLM-L6-v2" ) # ========================================================= # LOAD BOOK DATASET # ========================================================= books = pd.read_csv(os.path.join(BASE_DIR, "books_with_emotions.csv")) # ========================================================= # CLEAN CATEGORY COLUMN # ========================================================= books["simple_categories"] = ( books["simple_categories"] .fillna("") .astype(str) .str.strip() ) # ========================================================= # HANDLE THUMBNAILS # ========================================================= books["thumbnail"] = books["thumbnail"].fillna("") books["large_thumbnail"] = np.where( books["thumbnail"] != "", books["thumbnail"] + "&fife=w800", "cover-not-found.png" ) # ========================================================= # LOAD EXISTING VECTOR DATABASE # NOTE: ChromaDB files must be inside a "chroma_db" folder # at the root of your HuggingFace Space repo. # Required files inside chroma_db/: # - chroma.sqlite3 # - data_level0.bin # - header.bin # - length.bin # - link_lists.bin # - index_metadata.pickle # ========================================================= CHROMA_PATH = os.path.join(BASE_DIR, "chroma_db") db_books = Chroma( persist_directory=CHROMA_PATH, embedding_function=embedding_model ) # ========================================================= # RETRIEVE SEMANTIC RECOMMENDATIONS # ========================================================= def retrieve_semantic_recommendations( query: str, category: str, tone: str = "All", initial_top_k: int = 50, final_top_k: int = 16 ) -> pd.DataFrame: recs = db_books.similarity_search(query, k=initial_top_k) books_list = [] for rec in recs: try: isbn = int(rec.page_content.strip('"').split()[0]) books_list.append(isbn) except Exception: continue # Preserve vector search order and deduplicate seen = set() valid_isbns = [] for isbn in books_list: if isbn in books["isbn13"].values and isbn not in seen: valid_isbns.append(isbn) seen.add(isbn) if len(valid_isbns) == 0: return pd.DataFrame() book_recs = ( books .set_index("isbn13") .loc[valid_isbns] .reset_index() ) # Category filter if category != "All": book_recs = book_recs[ book_recs["simple_categories"].str.lower() == category.strip().lower() ] # Emotional tone filter tone_map = { "Happy": "joy", "Surprising": "surprise", "Angry": "anger", "Suspenseful": "fear", "Sad": "sadness", } if tone in tone_map: book_recs = book_recs.sort_values( by=tone_map[tone], ascending=False ) return book_recs.head(final_top_k) # ========================================================= # GENERATE HTML BOOK CARDS # ========================================================= def recommend_books(query: str, category: str, tone: str): if not query.strip(): return """