nonsodev commited on
Commit
d2ab819
Β·
1 Parent(s): 441dc65

final touches

Browse files
Files changed (2) hide show
  1. app.py +204 -71
  2. chroma_books/chroma.sqlite3 +1 -1
app.py CHANGED
@@ -2,6 +2,7 @@ import pandas as pd
2
  import gradio as gr
3
  import numpy as np
4
  import os
 
5
  from langchain_chroma import Chroma
6
  from langchain_huggingface import HuggingFaceEmbeddings
7
  from langchain_community.document_loaders import TextLoader
@@ -33,7 +34,7 @@ if not os.path.exists("chroma_books"):
33
  )
34
  documents = text_splitter.split_documents(raw_docs)
35
  print(f"Loaded {len(documents)} documents")
36
-
37
  db_books = Chroma.from_documents(
38
  documents,
39
  embedding=embeddings,
@@ -47,8 +48,8 @@ if not os.path.exists("chroma_books"):
47
  else:
48
  print("Loading existing ChromaDB...")
49
  db_books = Chroma(
50
- persist_directory="chroma_books",
51
- embedding_function=embeddings,
52
  collection_name="books"
53
  )
54
 
@@ -57,11 +58,17 @@ print("Loading books data...")
57
  try:
58
  books = pd.read_csv("final_book_df.csv")
59
  books["large_thumbnail"] = books["thumbnail"] + "&fife=w800"
 
60
  books["large_thumbnail"] = np.where(
61
- books["large_thumbnail"].isna(),
62
- "cover-not-found.jpg",
63
  books["large_thumbnail"]
64
  )
 
 
 
 
 
65
  print(f"Loaded {len(books)} books")
66
  except FileNotFoundError:
67
  print("ERROR: final_book_df.csv not found!")
@@ -72,19 +79,17 @@ def retrieve_semantic_recommendations(
72
  category: str = None,
73
  tone: str = None,
74
  initial_top_k: int = 50,
75
- final_top_k: int = 8, # Reduced for better display
76
  ) -> pd.DataFrame:
77
  """Retrieve semantic recommendations based on query, category, and tone."""
78
-
79
  recs = db_books.similarity_search(query, k=initial_top_k)
80
  books_list = [int(rec.page_content.strip('"').split()[0]) for rec in recs]
81
  book_recs = books[books["isbn13"].isin(books_list)].head(initial_top_k)
82
 
83
  # Filter by category
84
  if category and category != "All":
85
- book_recs = book_recs[book_recs["categories"] == category].head(final_top_k)
86
- else:
87
- book_recs = book_recs.head(final_top_k)
88
 
89
  # Sort by emotional tone
90
  if tone == "Happy":
@@ -98,19 +103,101 @@ def retrieve_semantic_recommendations(
98
  elif tone == "Sad":
99
  book_recs = book_recs.sort_values(by="sadness", ascending=False)
100
 
101
- return book_recs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  def create_book_card_html(row):
104
  """Create an HTML card for a single book with full description, ratings, and download link."""
105
-
106
  # Handle missing description
107
  description = row.get("description", "No description available")
108
  if pd.isna(description):
109
  description = "No description available"
110
-
111
  # Format authors
112
  authors = row.get("authors", "Unknown Author")
113
- if pd.isna(authors):
114
  authors_str = "Unknown Author"
115
  else:
116
  authors_split = str(authors).split(";")
@@ -120,17 +207,17 @@ def create_book_card_html(row):
120
  authors_str = f"{', '.join(authors_split[:-1])}, and {authors_split[-1]}"
121
  else:
122
  authors_str = authors
123
-
124
  # Get other info
125
  title = row.get("title_and_subtitle", "Unknown Title")
126
- thumbnail = row.get("large_thumbnail", "cover-not-found.jpg")
127
  download_url = row.get("url", "")
128
  category = row.get("categories", "Unknown")
129
-
130
  # Handle ratings
131
  average_rating = row.get("average_rating", 0)
132
  ratings_count = row.get("ratings_count", 0)
133
-
134
  # Convert to proper numeric values
135
  try:
136
  avg_rating = float(average_rating) if not pd.isna(average_rating) else 0
@@ -138,14 +225,14 @@ def create_book_card_html(row):
138
  except (ValueError, TypeError):
139
  avg_rating = 0
140
  rating_count = 0
141
-
142
  # Create star rating display
143
  def create_star_rating(rating):
144
  """Create HTML for star rating display."""
145
  full_stars = int(rating)
146
  half_star = 1 if (rating - full_stars) >= 0.5 else 0
147
  empty_stars = 5 - full_stars - half_star
148
-
149
  stars_html = ""
150
  # Full stars
151
  stars_html += "β˜…" * full_stars
@@ -154,9 +241,9 @@ def create_book_card_html(row):
154
  stars_html += "β˜†"
155
  # Empty stars
156
  stars_html += "β˜†" * empty_stars
157
-
158
  return stars_html
159
-
160
  # Format rating display
161
  if avg_rating > 0:
162
  stars = create_star_rating(avg_rating)
@@ -174,56 +261,58 @@ def create_book_card_html(row):
174
  <span style="color: #888888; font-size: 10px;">No ratings</span>
175
  </div>
176
  """
177
-
178
  # Create download button if URL exists
179
  download_button = ""
180
  if download_url and not pd.isna(download_url) and str(download_url).strip():
181
  download_button = f"""
182
  <div style="margin-top: 6px;">
183
- <a href="{download_url}" target="_blank"
184
- style="background-color: #4CAF50; color: white; padding: 6px 12px;
185
- text-decoration: none; border-radius: 4px; font-size: 10px;
186
  display: inline-block; text-align: center;">
187
  πŸ“– Get Book
188
  </a>
189
  </div>
190
  """
191
-
192
- # Create the card HTML with responsive design
193
  card_html = f"""
194
- <div style="border: 1px solid #444; border-radius: 8px; padding: 12px; margin: 10px 0;
195
  background-color: #2b2b2b; box-shadow: 0 2px 4px rgba(0,0,0,0.3);">
196
-
197
  <div style="display: flex; gap: 12px; flex-direction: row;">
198
  <div style="flex-shrink: 0;">
199
- <img src="{thumbnail}" alt="Book cover"
200
- style="width: 80px; height: 120px; object-fit: cover; border-radius: 4px;">
 
 
201
  </div>
202
-
203
  <div style="flex-grow: 1; min-width: 0; display: flex; flex-direction: column;">
204
- <h3 style="margin: 0 0 6px 0; color: #ffffff; font-size: 14px; line-height: 1.2;
205
- word-wrap: break-word; overflow-wrap: break-word;">
206
  {title}
207
  </h3>
208
-
209
  <p style="margin: 0 0 4px 0; color: #cccccc; font-size: 11px; font-style: italic;">
210
  {authors_str}
211
  </p>
212
-
213
  <p style="margin: 0 0 4px 0; color: #aaaaaa; font-size: 10px;">
214
  {category}
215
  </p>
216
-
217
  {rating_display}
218
-
219
  <div style="flex-grow: 1; margin: 6px 0;">
220
- <p style="margin: 0; color: #dddddd; font-size: 11px; line-height: 1.3;
221
- display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
222
  overflow: hidden; text-overflow: ellipsis;">
223
  {description}
224
  </p>
225
  </div>
226
-
227
  {download_button}
228
  </div>
229
  </div>
@@ -231,60 +320,97 @@ def create_book_card_html(row):
231
  """
232
  return card_html
233
 
234
- def recommend_books(query: str, category: str, tone: str):
235
  """Main recommendation function for Gradio interface."""
236
-
237
  if not query.strip():
238
  return "<p>Please enter a search query to get book recommendations.</p>"
239
-
240
  try:
241
- recommendations = retrieve_semantic_recommendations(query, category, tone)
242
-
 
 
 
 
 
243
  if recommendations.empty:
244
  return "<p>No books found matching your criteria. Try adjusting your search terms or filters.</p>"
245
-
246
  # Create HTML for all book cards
247
  html_cards = []
248
  for _, row in recommendations.iterrows():
249
  card_html = create_book_card_html(row)
250
  html_cards.append(card_html)
251
-
252
  # Combine all cards with a header
253
  full_html = f"""
254
  <div style="font-family: Arial, sans-serif; background-color: #1a1a1a; padding: 20px; border-radius: 8px;">
255
  <h2 style="color: #ffffff; margin-bottom: 20px;">
256
- πŸ“š Found {len(recommendations)} recommendations for: "{query}"
257
  </h2>
258
  {''.join(html_cards)}
259
  </div>
260
  """
261
-
262
  return full_html
263
-
264
  except Exception as e:
265
  print(f"Error in recommend_books: {e}")
266
  return f"<p>An error occurred while searching for books: {str(e)}</p>"
267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  # Prepare dropdown options
269
  categories = ["All"] + sorted(books["categories"].unique().tolist())
270
  tones = ["All", "Happy", "Surprising", "Angry", "Suspenseful", "Sad"]
 
271
 
272
  # Create Gradio interface
273
  with gr.Blocks(theme=gr.themes.Soft()) as dashboard:
274
  gr.Markdown("""
275
- # πŸ“š Semantic Book Recommender
276
- ## Find your next favorite book using AI-powered semantic search!
277
-
278
- Enter a description of what you're looking for, and our AI will find books that match your interests using semantic understanding.
 
279
  """)
280
 
281
  with gr.Row():
282
  with gr.Column(scale=2):
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  user_query = gr.Textbox(
284
- label="Describe your ideal book:",
285
- placeholder="e.g., 'A thrilling mystery set in Victorian London with complex characters'",
286
- lines=3,
287
- max_lines=5,
288
  )
289
 
290
  with gr.Column(scale=1):
@@ -301,37 +427,44 @@ with gr.Blocks(theme=gr.themes.Soft()) as dashboard:
301
  submit_button = gr.Button("πŸ” Find Books", variant="primary", size="lg")
302
 
303
  gr.Markdown("---")
304
-
305
- # Use HTML component instead of Gallery for better text display
306
  output = gr.HTML(
307
  label="Book Recommendations",
308
- value="<p>Enter a search query above to get personalized book recommendations!</p>"
309
  )
310
 
311
  # Event handlers
 
 
 
 
 
 
312
  submit_button.click(
313
  fn=recommend_books,
314
- inputs=[user_query, category_dropdown, tone_dropdown],
315
  outputs=output,
316
  )
317
-
318
  # Allow Enter key to submit
319
  user_query.submit(
320
  fn=recommend_books,
321
- inputs=[user_query, category_dropdown, tone_dropdown],
322
  outputs=output,
323
  )
324
 
325
  # Add some usage tips at the bottom
326
  gr.Markdown("""
327
  ### πŸ’‘ Tips for better results:
328
- - Be specific about what you're looking for (genre, setting, themes, etc.)
329
- - Try different emotional tones to discover books with specific moods
330
- - Use category filters to narrow down results
331
- - Experiment with different search terms if you don't find what you're looking for
 
332
  """)
333
 
334
- print("Enhanced app initialized successfully! πŸš€")
335
 
336
  if __name__ == "__main__":
337
  dashboard.launch()
 
2
  import gradio as gr
3
  import numpy as np
4
  import os
5
+ import re
6
  from langchain_chroma import Chroma
7
  from langchain_huggingface import HuggingFaceEmbeddings
8
  from langchain_community.document_loaders import TextLoader
 
34
  )
35
  documents = text_splitter.split_documents(raw_docs)
36
  print(f"Loaded {len(documents)} documents")
37
+
38
  db_books = Chroma.from_documents(
39
  documents,
40
  embedding=embeddings,
 
48
  else:
49
  print("Loading existing ChromaDB...")
50
  db_books = Chroma(
51
+ persist_directory="chroma_books",
52
+ embedding_function=embeddings,
53
  collection_name="books"
54
  )
55
 
 
58
  try:
59
  books = pd.read_csv("final_book_df.csv")
60
  books["large_thumbnail"] = books["thumbnail"] + "&fife=w800"
61
+ # Better fallback image handling
62
  books["large_thumbnail"] = np.where(
63
+ books["large_thumbnail"].isna() | books["thumbnail"].isna(),
64
+ "https://via.placeholder.com/120x180/333333/cccccc?text=No+Cover",
65
  books["large_thumbnail"]
66
  )
67
+ # Ensure 'authors' and 'categories' are string type for literal search
68
+ books['authors'] = books['authors'].astype(str)
69
+ books['categories'] = books['categories'].astype(str)
70
+ books['title_and_subtitle'] = books['title_and_subtitle'].astype(str)
71
+
72
  print(f"Loaded {len(books)} books")
73
  except FileNotFoundError:
74
  print("ERROR: final_book_df.csv not found!")
 
79
  category: str = None,
80
  tone: str = None,
81
  initial_top_k: int = 50,
82
+ final_top_k: int = 8,
83
  ) -> pd.DataFrame:
84
  """Retrieve semantic recommendations based on query, category, and tone."""
85
+
86
  recs = db_books.similarity_search(query, k=initial_top_k)
87
  books_list = [int(rec.page_content.strip('"').split()[0]) for rec in recs]
88
  book_recs = books[books["isbn13"].isin(books_list)].head(initial_top_k)
89
 
90
  # Filter by category
91
  if category and category != "All":
92
+ book_recs = book_recs[book_recs["categories"] == category]
 
 
93
 
94
  # Sort by emotional tone
95
  if tone == "Happy":
 
103
  elif tone == "Sad":
104
  book_recs = book_recs.sort_values(by="sadness", ascending=False)
105
 
106
+ return book_recs.head(final_top_k)
107
+
108
+ def retrieve_literal_recommendations(
109
+ query: str,
110
+ category: str = None,
111
+ tone: str = None,
112
+ final_top_k: int = 8,
113
+ ) -> pd.DataFrame:
114
+ """Retrieve literal recommendations using flexible regex pattern matching."""
115
+ if not query.strip():
116
+ return pd.DataFrame()
117
+
118
+ # Create flexible regex pattern - matches partial words and handles word boundaries
119
+ query_words = query.lower().strip().split()
120
+
121
+ # Create regex patterns for each word that can match anywhere in the text
122
+ patterns = []
123
+ for word in query_words:
124
+ # Escape special regex characters and create flexible pattern
125
+ escaped_word = re.escape(word)
126
+ # Pattern that matches the word with optional word boundaries
127
+ pattern = f".*{escaped_word}.*"
128
+ patterns.append(pattern)
129
+
130
+ # Combine patterns with OR logic for flexible matching
131
+ combined_pattern = "|".join(patterns)
132
+
133
+ try:
134
+ # Search in title, subtitle, and authors using regex
135
+ title_matches = books['title_and_subtitle'].str.contains(
136
+ combined_pattern, case=False, na=False, regex=True
137
+ )
138
+ author_matches = books['authors'].str.contains(
139
+ combined_pattern, case=False, na=False, regex=True
140
+ )
141
+
142
+ # Combine both matches
143
+ literal_recs = books[title_matches | author_matches].copy()
144
+
145
+ # If no results with combined pattern, try individual word patterns
146
+ if literal_recs.empty and len(query_words) > 1:
147
+ for word in query_words:
148
+ escaped_word = re.escape(word.lower())
149
+ pattern = f".*{escaped_word}.*"
150
+
151
+ word_title_matches = books['title_and_subtitle'].str.contains(
152
+ pattern, case=False, na=False, regex=True
153
+ )
154
+ word_author_matches = books['authors'].str.contains(
155
+ pattern, case=False, na=False, regex=True
156
+ )
157
+
158
+ word_matches = books[word_title_matches | word_author_matches].copy()
159
+ literal_recs = pd.concat([literal_recs, word_matches]).drop_duplicates()
160
+
161
+ if len(literal_recs) >= final_top_k:
162
+ break
163
+
164
+ except re.error:
165
+ # Fallback to simple string matching if regex fails
166
+ query_lower = query.lower()
167
+ literal_recs = books[
168
+ books['title_and_subtitle'].str.contains(query_lower, case=False, na=False) |
169
+ books['authors'].str.contains(query_lower, case=False, na=False)
170
+ ].copy()
171
+
172
+ # Filter by category
173
+ if category and category != "All":
174
+ literal_recs = literal_recs[literal_recs["categories"] == category]
175
+
176
+ # Sort by emotional tone
177
+ if tone == "Happy":
178
+ literal_recs = literal_recs.sort_values(by="joy", ascending=False)
179
+ elif tone == "Surprising":
180
+ literal_recs = literal_recs.sort_values(by="surprise", ascending=False)
181
+ elif tone == "Angry":
182
+ literal_recs = literal_recs.sort_values(by="anger", ascending=False)
183
+ elif tone == "Suspenseful":
184
+ literal_recs = literal_recs.sort_values(by="fear", ascending=False)
185
+ elif tone == "Sad":
186
+ literal_recs = literal_recs.sort_values(by="sadness", ascending=False)
187
+
188
+ return literal_recs.head(final_top_k)
189
 
190
  def create_book_card_html(row):
191
  """Create an HTML card for a single book with full description, ratings, and download link."""
192
+
193
  # Handle missing description
194
  description = row.get("description", "No description available")
195
  if pd.isna(description):
196
  description = "No description available"
197
+
198
  # Format authors
199
  authors = row.get("authors", "Unknown Author")
200
+ if pd.isna(authors) or authors == "nan":
201
  authors_str = "Unknown Author"
202
  else:
203
  authors_split = str(authors).split(";")
 
207
  authors_str = f"{', '.join(authors_split[:-1])}, and {authors_split[-1]}"
208
  else:
209
  authors_str = authors
210
+
211
  # Get other info
212
  title = row.get("title_and_subtitle", "Unknown Title")
213
+ thumbnail = row.get("large_thumbnail", "https://via.placeholder.com/120x180/333333/cccccc?text=No+Cover")
214
  download_url = row.get("url", "")
215
  category = row.get("categories", "Unknown")
216
+
217
  # Handle ratings
218
  average_rating = row.get("average_rating", 0)
219
  ratings_count = row.get("ratings_count", 0)
220
+
221
  # Convert to proper numeric values
222
  try:
223
  avg_rating = float(average_rating) if not pd.isna(average_rating) else 0
 
225
  except (ValueError, TypeError):
226
  avg_rating = 0
227
  rating_count = 0
228
+
229
  # Create star rating display
230
  def create_star_rating(rating):
231
  """Create HTML for star rating display."""
232
  full_stars = int(rating)
233
  half_star = 1 if (rating - full_stars) >= 0.5 else 0
234
  empty_stars = 5 - full_stars - half_star
235
+
236
  stars_html = ""
237
  # Full stars
238
  stars_html += "β˜…" * full_stars
 
241
  stars_html += "β˜†"
242
  # Empty stars
243
  stars_html += "β˜†" * empty_stars
244
+
245
  return stars_html
246
+
247
  # Format rating display
248
  if avg_rating > 0:
249
  stars = create_star_rating(avg_rating)
 
261
  <span style="color: #888888; font-size: 10px;">No ratings</span>
262
  </div>
263
  """
264
+
265
  # Create download button if URL exists
266
  download_button = ""
267
  if download_url and not pd.isna(download_url) and str(download_url).strip():
268
  download_button = f"""
269
  <div style="margin-top: 6px;">
270
+ <a href="{download_url}" target="_blank"
271
+ style="background-color: #4CAF50; color: white; padding: 6px 12px;
272
+ text-decoration: none; border-radius: 4px; font-size: 10px;
273
  display: inline-block; text-align: center;">
274
  πŸ“– Get Book
275
  </a>
276
  </div>
277
  """
278
+
279
+ # Create the card HTML with responsive design and better image fallback
280
  card_html = f"""
281
+ <div style="border: 1px solid #444; border-radius: 8px; padding: 12px; margin: 10px 0;
282
  background-color: #2b2b2b; box-shadow: 0 2px 4px rgba(0,0,0,0.3);">
283
+
284
  <div style="display: flex; gap: 12px; flex-direction: row;">
285
  <div style="flex-shrink: 0;">
286
+ <img src="{thumbnail}" alt="Book cover"
287
+ style="width: 80px; height: 120px; object-fit: cover; border-radius: 4px;
288
+ background-color: #333; border: 1px solid #555;"
289
+ onerror="this.src='https://via.placeholder.com/120x180/333333/cccccc?text=No+Cover';">
290
  </div>
291
+
292
  <div style="flex-grow: 1; min-width: 0; display: flex; flex-direction: column;">
293
+ <h3 style="margin: 0 0 6px 0; color: #ffffff; font-size: 14px; line-height: 1.2;
294
+ word-wrap: break-word; overflow-wrap: break-word;">
295
  {title}
296
  </h3>
297
+
298
  <p style="margin: 0 0 4px 0; color: #cccccc; font-size: 11px; font-style: italic;">
299
  {authors_str}
300
  </p>
301
+
302
  <p style="margin: 0 0 4px 0; color: #aaaaaa; font-size: 10px;">
303
  {category}
304
  </p>
305
+
306
  {rating_display}
307
+
308
  <div style="flex-grow: 1; margin: 6px 0;">
309
+ <p style="margin: 0; color: #dddddd; font-size: 11px; line-height: 1.3;
310
+ display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
311
  overflow: hidden; text-overflow: ellipsis;">
312
  {description}
313
  </p>
314
  </div>
315
+
316
  {download_button}
317
  </div>
318
  </div>
 
320
  """
321
  return card_html
322
 
323
+ def recommend_books(query: str, category: str, tone: str, search_type: str):
324
  """Main recommendation function for Gradio interface."""
325
+
326
  if not query.strip():
327
  return "<p>Please enter a search query to get book recommendations.</p>"
328
+
329
  try:
330
+ if search_type == "Semantic Search":
331
+ recommendations = retrieve_semantic_recommendations(query, category, tone)
332
+ elif search_type == "Literal Search":
333
+ recommendations = retrieve_literal_recommendations(query, category, tone)
334
+ else:
335
+ return "<p>Invalid search type selected.</p>"
336
+
337
  if recommendations.empty:
338
  return "<p>No books found matching your criteria. Try adjusting your search terms or filters.</p>"
339
+
340
  # Create HTML for all book cards
341
  html_cards = []
342
  for _, row in recommendations.iterrows():
343
  card_html = create_book_card_html(row)
344
  html_cards.append(card_html)
345
+
346
  # Combine all cards with a header
347
  full_html = f"""
348
  <div style="font-family: Arial, sans-serif; background-color: #1a1a1a; padding: 20px; border-radius: 8px;">
349
  <h2 style="color: #ffffff; margin-bottom: 20px;">
350
+ πŸ“š Found {len(recommendations)} recommendations for: "{query}" ({search_type})
351
  </h2>
352
  {''.join(html_cards)}
353
  </div>
354
  """
355
+
356
  return full_html
357
+
358
  except Exception as e:
359
  print(f"Error in recommend_books: {e}")
360
  return f"<p>An error occurred while searching for books: {str(e)}</p>"
361
 
362
+ def update_search_interface(search_type):
363
+ """Update the interface based on search type selection."""
364
+ if search_type == "Literal Search":
365
+ return {
366
+ search_instructions: gr.update(
367
+ value="**Literal Search Mode:** Type book titles or author names directly. Supports partial matching - e.g., 'harry' will find 'Harry Potter', 'tolkien' will find J.R.R. Tolkien books.",
368
+ visible=True
369
+ )
370
+ }
371
+ else:
372
+ return {
373
+ search_instructions: gr.update(
374
+ value="**Semantic Search Mode:** Describe what kind of book you're looking for using natural language - e.g., 'fantasy adventure with magic'.",
375
+ visible=True
376
+ )
377
+ }
378
+
379
  # Prepare dropdown options
380
  categories = ["All"] + sorted(books["categories"].unique().tolist())
381
  tones = ["All", "Happy", "Surprising", "Angry", "Suspenseful", "Sad"]
382
+ search_types = ["Semantic Search", "Literal Search"]
383
 
384
  # Create Gradio interface
385
  with gr.Blocks(theme=gr.themes.Soft()) as dashboard:
386
  gr.Markdown("""
387
+ # πŸ“š Smart Book Recommender
388
+ ## Find your next favorite book using AI-powered semantic search or flexible keyword matching!
389
+
390
+ **Semantic Search:** Describe what you want (e.g., "romantic comedy in Paris")
391
+ **Literal Search:** Type exact titles or authors (e.g., "harry" β†’ Harry Potter books)
392
  """)
393
 
394
  with gr.Row():
395
  with gr.Column(scale=2):
396
+ search_type_radio = gr.Radio(
397
+ choices=search_types,
398
+ value="Semantic Search",
399
+ label="Search Type",
400
+ interactive=True
401
+ )
402
+
403
+ search_instructions = gr.Markdown(
404
+ "**Semantic Search Mode:** Describe what kind of book you're looking for using natural language - e.g., 'fantasy adventure with magic'.",
405
+ visible=True
406
+ )
407
+
408
+ # Single search input for both modes
409
  user_query = gr.Textbox(
410
+ label="Search for books:",
411
+ placeholder="e.g., 'harry potter' or 'thrilling mystery in Victorian London'",
412
+ lines=2,
413
+ max_lines=4
414
  )
415
 
416
  with gr.Column(scale=1):
 
427
  submit_button = gr.Button("πŸ” Find Books", variant="primary", size="lg")
428
 
429
  gr.Markdown("---")
430
+
431
+ # Use HTML component for book display
432
  output = gr.HTML(
433
  label="Book Recommendations",
434
+ value="<p>Select a search type and enter your preferences to get personalized book recommendations!</p>"
435
  )
436
 
437
  # Event handlers
438
+ search_type_radio.change(
439
+ fn=update_search_interface,
440
+ inputs=[search_type_radio],
441
+ outputs=[search_instructions]
442
+ )
443
+
444
  submit_button.click(
445
  fn=recommend_books,
446
+ inputs=[user_query, category_dropdown, tone_dropdown, search_type_radio],
447
  outputs=output,
448
  )
449
+
450
  # Allow Enter key to submit
451
  user_query.submit(
452
  fn=recommend_books,
453
+ inputs=[user_query, category_dropdown, tone_dropdown, search_type_radio],
454
  outputs=output,
455
  )
456
 
457
  # Add some usage tips at the bottom
458
  gr.Markdown("""
459
  ### πŸ’‘ Tips for better results:
460
+ - **Semantic Search:** Be descriptive (e.g., "dark fantasy with dragons", "romance set in medieval times")
461
+ - **Literal Search:** Use partial names (e.g., "tolkien", "stephen king", "harry", "game thrones")
462
+ - **Flexible Matching:** Literal search finds books even with partial words - "potter" finds "Harry Potter"
463
+ - **Combine filters:** Use category and tone filters to narrow down results
464
+ - **Try variations:** If you don't find what you want, try different keywords or switch search modes
465
  """)
466
 
467
+ print("Enhanced app with flexible regex search initialized successfully! πŸš€")
468
 
469
  if __name__ == "__main__":
470
  dashboard.launch()
chroma_books/chroma.sqlite3 CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:63cd7d57fc1a836468777f10e68d40c6ef3d8cf4cb738814120600b094bea4bd
3
  size 34754560
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b007abac0926b25c496417adc8d3d6279831b7b029beb187b9f5028b05d804d2
3
  size 34754560